#include "Document/BrushStroke.h" #include "Document/EditorSession.h" #include "Document/PixelAdjust.h" #include "Document/Scanlines.h" #include "UI/ColorPaletteControls.h" #include "UI/FilterSheet.h" #include #include #include #include #include #include #include #include extern "C" { #include "DitherPixels.h" } // Swift 1.4.9's Filter › Scanlines, its own filter. namespace { QImage filled(int width, int height, QColor colour) { QImage image = BrushRaster::context(width, height, false); image.fill(colour); return image; } std::vector column(const QImage &image, int x, int channel = 0) { std::vector values; for (int y = 0; y < image.height(); ++y) values.push_back(image.constScanLine(y)[x * 4 + channel]); return values; } std::vector row(const QImage &image, int y, int channel = 0) { std::vector values; for (int x = 0; x < image.width(); ++x) values.push_back(image.constScanLine(y)[x * 4 + channel]); return values; } // Swift's render: no glow unless asked. QImage render(const QImage &image, const std::function &adjust = {}) { ScanlinesSettings settings; if (adjust) adjust(settings); return settings.apply(image); } std::vector gray(int level, double spacing, double glow = 0) { return column(render(filled(16, 32, QColor(level, level, level)), [&](ScanlinesSettings &s) { s.lineSpacing = spacing, s.glow = glow; }), 5); } // The oracle's picture: premultiplied, some clear, some soft. QImage pattern(int width, int height) { QImage image = BrushRaster::context(width, height, false); for (int y = 0; y < height; ++y) { uchar *out = image.scanLine(y); for (int x = 0; x < width; ++x) { const unsigned alpha = (x * 13 + y * 7) % 4 == 0 ? 0 : 128 + (x * 29 + y * 17) % 128; for (int c = 0; c < 3; ++c) out[x * 4 + c] = uchar(((x * 37 + y * 11 + c * 53) % 256) * alpha / 255); out[x * 4 + 3] = uchar(alpha); } } return image; } quint64 fnv(const QImage &image) { quint64 hash = 1469598103934665603ULL; for (int y = 0; y < image.height(); ++y) for (int x = 0; x < image.width() * 4; ++x) hash = (hash ^ image.constScanLine(y)[x]) * 1099511628211ULL; return hash; } // Swift's params: unset fields zero, white lines unless set. ScanlinesParams made(const std::function &set) { ScanlinesParams params{}; params.light[0] = params.light[1] = params.light[2] = 255; set(params); return params; } void rgb(uint8_t *to, uint8_t red, uint8_t green, uint8_t blue) { to[0] = red, to[1] = green, to[2] = blue; } QImage kernel(int width, int height, const ScanlinesParams ¶ms) { QImage image = pattern(width, height); if (scanlines_apply(image.bits(), size_t(width), size_t(height), size_t(image.bytesPerLine()), ¶ms) == 0) throw std::runtime_error("no memory"); return image; } struct Shown { EditorSession session; std::unique_ptr sheet; Shown() { session.createDocument(40, 30); const QImage image = filled(40, 30, QColor(200, 120, 40)); session.insert(ImportedImage(image, image, QStringLiteral("Colour"))); session.beginFilter(FilterKind::scanlines); sheet = std::make_unique(session); sheet->show(); } const ScanlinesSettings &lines() const { return session.filterEdit().value().settings.scanlines; } QStringList rows() const { QStringList titles; QLayout *column = sheet->layout(); for (int index = 0; index < column->count(); ++index) { QWidget *each = column->itemAt(index)->widget(); if (!each || each->isHidden()) continue; for (QLabel *label : each->findChildren(QString(), Qt::FindDirectChildrenOnly)) { if (label->buddy()) titles << label->text() + u'|' + each->toolTip(); } } return titles; } }; } class ScanlinesTests : public QObject { Q_OBJECT private slots: void scanlinesAreLinesOfLightThatBloomWithBrightness(); void linesShowOnSolidWhiteAndBlack(); void glowLightsBetweenTheLines(); void dotsBeadTheLinesBelowTheirLevel(); void wobbleMovesLinesSideways(); void displaceLiftsLinesWhereThePictureIsBright(); void aLiftedLineHidesTheLinesBehindIt(); void thresholdDarkensWhatsBelowIt(); void theKernelDrawsSwiftsOwnBytes(); void theSettingsReachTheKernel(); void normalizingClampsEverySetting(); void clearPixelsStayClear(); void theGlowIsTheBlurredLightEasedNearWhite(); void theFilterIsListedAndPreviewsWhole(); void theSheetShowsSwiftsRows(); void theRowsWriteTheirSettings(); void twoColorsPickTheScreenAndTheLines(); }; void ScanlinesTests::scanlinesAreLinesOfLightThatBloomWithBrightness() { const std::vector white = gray(255, 8), dim = gray(89, 8), black = gray(0, 8); for (int band = 0; band < 4; ++band) { const std::vector rows(white.begin() + band * 8, white.begin() + band * 8 + 8); QVERIFY(rows[3] == 255 && rows[4] == 255); QVERIFY(std::any_of(rows.begin(), rows.end(), [](int value) { return value < 40; })); } QVERIFY(std::accumulate(dim.begin(), dim.end(), 0) * 2 < std::accumulate(white.begin(), white.end(), 0)); QVERIFY(std::all_of(black.begin(), black.end(), [](int value) { return value == 0; })); } void ScanlinesTests::linesShowOnSolidWhiteAndBlack() { const std::vector white = gray(255, 8, 100); QVERIFY(*std::max_element(white.begin(), white.end()) - *std::min_element(white.begin(), white.end()) > 40); const std::vector black = column(render(filled(16, 32, Qt::black), [](ScanlinesSettings &s) { s.lineSpacing = 8, s.blackLevel = 40; }), 8); const std::vector band(black.begin(), black.begin() + 8); QVERIFY(*std::max_element(band.begin(), band.end()) > 60 && *std::min_element(band.begin(), band.end()) < 10); } void ScanlinesTests::glowLightsBetweenTheLines() { const std::vector plain = gray(255, 8), glowing = gray(255, 8, 100); QVERIFY2(glowing[0] > plain[0] + 40, qPrintable(QString::number(glowing[0]))); } void ScanlinesTests::dotsBeadTheLinesBelowTheirLevel() { const auto middle = [](int level) { return row(render(filled(64, 16, QColor(level, level, level)), [](ScanlinesSettings &s) { s.lineSpacing = 8, s.dots = 70; }), 4); }; QCOMPARE(middle(255), std::vector(64, 255)); const std::vector grey = middle(102); QVERIFY(grey[3] > 100 && grey[4] > 100 && grey[0] < 40 && grey[8] < 40); } void ScanlinesTests::wobbleMovesLinesSideways() { QImage edge = filled(64, 64, Qt::black); QPainter(&edge).fillRect(QRect(32, 0, 32, 64), Qt::white); const auto edges = [&edge](double wobble) { const QImage result = render(edge, [wobble](ScanlinesSettings &s) { s.lineSpacing = 8, s.wobble = wobble; }); std::set found; for (int line = 0; line < 8; ++line) { const std::vector middle = row(result, line * 8 + 4); found.insert(int(std::find_if(middle.begin(), middle.end(), [](int value) { return value > 128; }) - middle.begin())); } return found; }; QCOMPARE(edges(0), std::set{32}); QVERIFY(edges(12).size() >= 3); } void ScanlinesTests::displaceLiftsLinesWhereThePictureIsBright() { QImage half = filled(64, 64, Qt::black); QPainter(&half).fillRect(QRect(32, 0, 32, 64), Qt::white); const QImage result = render(half, [](ScanlinesSettings &s) { s.lineSpacing = 16, s.displace = 6, s.colors = DitherColors::original, s.blackLevel = 50; }); const auto brightest = [&result](int x) { const std::vector values = column(result, x); return int(std::max_element(values.begin() + 16, values.begin() + 32) - values.begin()); }; QVERIFY(brightest(48) < 24); // Over black the line keeps its middle, row 24. const std::vector still = column(result, 8); QVERIFY(still[24] > 0 && still[24] == *std::max_element(still.begin() + 16, still.begin() + 32) && still[16] < still[24]); } void ScanlinesTests::aLiftedLineHidesTheLinesBehindIt() { QImage band = filled(64, 80, QColor(64, 64, 64)); QPainter(&band).fillRect(QRect(16, 32, 32, 16), Qt::white); const QImage result = render(band, [](ScanlinesSettings &s) { s.lineSpacing = 16, s.displace = 40, s.smoothness = 0; }); QVERIFY(result.constScanLine(14)[4 * 4] > 20); QVERIFY(result.constScanLine(14)[32 * 4] < 8); QVERIFY(result.constScanLine(2)[32 * 4] > 200); } void ScanlinesTests::thresholdDarkensWhatsBelowIt() { const QImage grey = filled(32, 32, QColor(77, 77, 77)); const std::vector shown = column(render(grey, [](ScanlinesSettings &s) { s.lineSpacing = 8; }), 16); const std::vector hidden = column(render(grey, [](ScanlinesSettings &s) { s.lineSpacing = 8, s.threshold = 60; }), 16); QVERIFY(std::any_of(shown.begin(), shown.end(), [](int value) { return value > 40; })); QVERIFY(std::none_of(hidden.begin(), hidden.end(), [](int value) { return value > 10; })); } // Swift's GCD kernel, built with clang's blocks, gave these. void ScanlinesTests::theKernelDrawsSwiftsOwnBytes() { const QImage a = kernel(37, 53, made([](ScanlinesParams &p) { p.spacing = 4; p.thickness = 0.7f; p.displace = 20; p.smoothness = 0.5f; p.originalColors = 1; })); QCOMPARE(fnv(a), 14473728681183016108ULL); QCOMPARE(QByteArray(reinterpret_cast(a.constScanLine(2)) + 4, 4), QByteArray("\xbd\xbf\x25\xbf", 4)); const QImage b = kernel(37, 53, made([](ScanlinesParams &p) { p.spacing = 6; p.thickness = 0.9f; p.dots = 0.6f; p.wobble = 6; p.displace = -15; p.threshold = 0.3f; p.split = 3; p.density = 0.2f; p.contrast = -0.3f; p.blackLevel = 0.25f; p.smoothness = 0.8f; })); QCOMPARE(fnv(b), 3381563246094821038ULL); const QImage c = kernel(37, 53, made([](ScanlinesParams &p) { p.spacing = 5; p.thickness = 0.4f; p.contrast = 0.6f; p.smoothness = 0.5f; rgb(p.dark, 10, 20, 200); rgb(p.light, 250, 200, 10); })); QCOMPARE(fnv(c), 11894396318800434257ULL); QCOMPARE(QByteArray(reinterpret_cast(c.constScanLine(2)) + 4, 4), QByteArray("\x55\x4d\x8f\xbf", 4)); // Wide and tall: 32 bands of columns, lines and rows. QCOMPARE(fnv(kernel(70, 300, made([](ScanlinesParams &p) { p.spacing = 3; p.thickness = 0.7f; p.dots = 0.3f; p.displace = 30; p.split = 2; p.blackLevel = 0.1f; p.smoothness = 1; p.originalColors = 1; }))), 5889846669877741430ULL); QCOMPARE(fnv(kernel(70, 300, made([](ScanlinesParams &p) { p.spacing = 2; p.thickness = 1; p.wobble = 9; p.displace = -40; p.threshold = 0.5f; p.smoothness = 0.25f; }))), 1843572660750656508ULL); // The glow eases near full brightness. QImage lit = pattern(41, 29), glow = pattern(41, 29); for (int y = 0; y < 29; ++y) for (int x = 0; x < 41 * 4; ++x) glow.scanLine(y)[x] = uchar(255 - glow.constScanLine(y)[x]); dither_glow(lit.bits(), glow.constBits(), 41, 29, size_t(lit.bytesPerLine()), 1.3f); QCOMPARE(fnv(lit), 14026896863046063157ULL); } // Each setting reaches its own field, scaled as Swift's. void ScanlinesTests::theSettingsReachTheKernel() { ScanlinesSettings settings; settings.lineSpacing = 6, settings.thickness = 90, settings.dots = 60, settings.wobble = 6, settings.displace = -15, settings.threshold = 30; settings.split = 3, settings.density = 20, settings.contrast = -30, settings.blackLevel = 25, settings.smoothness = 80; QCOMPARE(settings.apply(pattern(37, 53)), kernel(37, 53, made([](ScanlinesParams &p) { p.spacing = 6; p.thickness = 0.9f; p.dots = 0.6f; p.wobble = 6; p.displace = -15; p.threshold = 0.3f; p.split = 3; p.density = 0.2f; p.contrast = -0.3f; p.blackLevel = 0.25f; p.smoothness = 0.8f; }))); settings = ScanlinesSettings(); settings.lineSpacing = 5, settings.thickness = 40, settings.contrast = 60, settings.colors = DitherColors::twoColors; settings.dark = AdjustmentColor(10 / 255.0, 20 / 255.0, 200 / 255.0), settings.light = AdjustmentColor(250 / 255.0, 200 / 255.0, 10 / 255.0); QCOMPARE(settings.apply(pattern(37, 53)), kernel(37, 53, made([](ScanlinesParams &p) { p.spacing = 5; p.thickness = 0.4f; p.contrast = 0.6f; p.smoothness = 0.5f; rgb(p.dark, 10, 20, 200); rgb(p.light, 250, 200, 10); }))); // Picked colours count only for Two Colors. settings.colors = DitherColors::blackWhite; QCOMPARE(settings.apply(pattern(37, 53)), kernel(37, 53, made([](ScanlinesParams &p) { p.spacing = 5; p.thickness = 0.4f; p.contrast = 0.6f; p.smoothness = 0.5f; }))); settings.colors = DitherColors::original, settings.displace = 20, settings.lineSpacing = 4, settings.thickness = 70, settings.contrast = 0; QCOMPARE(fnv(settings.apply(pattern(37, 53))), 14473728681183016108ULL); } void ScanlinesTests::normalizingClampsEverySetting() { const ScanlinesSettings defaults; QVERIFY(defaults.lineSpacing == 4 && defaults.thickness == 70 && defaults.glow == 0 && defaults.dots == 0 && defaults.wobble == 0); QVERIFY(defaults.displace == 0 && defaults.smoothness == 50 && defaults.threshold == 0 && defaults.split == 0 && defaults.blackLevel == 0); QVERIFY(defaults.colors == DitherColors::blackWhite && defaults.dark == AdjustmentColor(0, 0, 0) && defaults.light == AdjustmentColor(1, 1, 1)); using Field = double ScanlinesSettings::*; // Below, above, a fraction, not a number. const std::tuple fields[] = { {&ScanlinesSettings::lineSpacing, 2, 32, 5.5, 6, 4}, {&ScanlinesSettings::thickness, 5, 100, 33.3, 33.3, 70}, {&ScanlinesSettings::glow, 0, 100, 12.5, 12.5, 0}, {&ScanlinesSettings::dots, 0, 100, 33.3, 33.3, 0}, {&ScanlinesSettings::wobble, 0, 64, 3.5, 3.5, 0}, {&ScanlinesSettings::displace, -100, 100, -7.5, -7.5, 0}, {&ScanlinesSettings::smoothness, 0, 100, 12.5, 12.5, 50}, {&ScanlinesSettings::threshold, 0, 100, 12.5, 12.5, 0}, {&ScanlinesSettings::split, 0, 16, 2.5, 3, 0}, {&ScanlinesSettings::density, -100, 100, 12.5, 12.5, 0}, {&ScanlinesSettings::contrast, -100, 100, -12.5, -12.5, 0}, {&ScanlinesSettings::blackLevel, 0, 100, 12.5, 12.5, 0}}; for (const auto &[field, low, high, typed, kept, fallback] : fields) { ScanlinesSettings settings; settings.*field = low - 1000; QCOMPARE(settings.normalized().*field, low); settings.*field = high + 1000; QCOMPARE(settings.normalized().*field, high); settings.*field = typed; QCOMPARE(settings.normalized().*field, kept); settings.*field = std::nan(""); QCOMPARE(settings.normalized().*field, fallback); } // Applying normalizes first: past the ends draws as the ends. ScanlinesSettings wild; wild.lineSpacing = 5.6, wild.thickness = 500, wild.split = 2.4, wild.displace = 300; QCOMPARE(wild.apply(pattern(37, 53)), wild.normalized().apply(pattern(37, 53))); QCOMPARE(fnv(wild.apply(pattern(37, 53))), fnv(kernel(37, 53, made([](ScanlinesParams &p) { p.spacing = 6; p.thickness = 1; p.split = 2; p.displace = 100; p.smoothness = 0.5f; })))); ScanlinesSettings colours; colours.dark = AdjustmentColor(-1, 2, 0.5), colours.light = AdjustmentColor(3, -2, 0.25); QVERIFY(colours.normalized().dark == AdjustmentColor(0, 1, 0.5) && colours.normalized().light == AdjustmentColor(1, 0, 0.25)); } void ScanlinesTests::clearPixelsStayClear() { QImage image = filled(8, 16, Qt::white); QPainter painter(&image); painter.setCompositionMode(QPainter::CompositionMode_Source); painter.fillRect(QRect(0, 0, 8, 8), Qt::transparent); painter.end(); const QImage result = render(image, [](ScanlinesSettings &s) { s.lineSpacing = 8, s.glow = 100; }); QCOMPARE(column(result, 2, 3), (std::vector{0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255})); QCOMPARE(column(result, 2, 0)[4], 0); } // Swift's Core Image blur: the twin's within a few levels. void ScanlinesTests::theGlowIsTheBlurredLightEasedNearWhite() { QImage image = filled(64, 160, Qt::black); QPainter(&image).fillRect(QRect(0, 0, 32, 40), Qt::white); for (const double glow : {20.0, 100.0}) { const QImage lines = render(image, [](ScanlinesSettings &s) { s.lineSpacing = 8; }); const QImage result = render(image, [glow](ScanlinesSettings &s) { s.lineSpacing = 8, s.glow = glow; }); const QImage blurred = PixelAdjust::gaussianBlur(lines, 8 * 3 + 3, true); const float amount = float(glow / 100 * 2.5); int worst = 0; for (int y = 0; y < image.height(); ++y) { for (int x = 0; x < image.width(); ++x) { float value = lines.constScanLine(y)[x * 4]; const float added = blurred.constScanLine(y)[x * 4] * amount, room = (255 - value) * 0.7f; if (room > 0) value += room * (1 - std::exp(-added / room)); worst = std::max(worst, std::abs(result.constScanLine(y)[x * 4] - int(std::lround(std::min(255.0f, value))))); } } QVERIFY2(worst <= 8, qPrintable(QString::number(worst))); } } void ScanlinesTests::theFilterIsListedAndPreviewsWhole() { QCOMPARE(rawValue(FilterKind::scanlines), QString("Scanlines")); const auto at = std::find(allFilterKinds.begin(), allFilterKinds.end(), FilterKind::scanlines); QVERIFY(at != allFilterKinds.end() && *(at - 1) == FilterKind::dither && *(at + 1) == FilterKind::tonalContrast); QVERIFY(!isImageAdjustment(FilterKind::scanlines) && !isAutomatic(FilterKind::scanlines) && repeatsAsLastFilter(FilterKind::scanlines)); EditorSession session; session.createDocument(3000, 20); const QImage wide = filled(3000, 20, Qt::white); session.insert(ImportedImage(wide, wide, QStringLiteral("Wide"))); session.beginFilter(FilterKind::scanlines); QCOMPARE(session.filterEdit().value().previewScale, 1.0); // Its commit is one step that draws the lines. QVERIFY(session.filterEdit().value().settings.scanlines == ScanlinesSettings()); bool done = false; session.commitFilter([&done] { done = true; }); QTRY_VERIFY(done); QCOMPARE(session.history.undoName(), QString("Scanlines")); const QImage drawn = session.document().value().layers.front().asset.value().image(); // Beam 1.4 round the middle 2: row 0 covers 0.4. QCOMPARE(column(drawn, 5)[0], int(std::lround(255 * 1.35f * 0.4f))); QCOMPARE(column(drawn, 5)[2], 255); } void ScanlinesTests::theSheetShowsSwiftsRows() { Shown shown; QCOMPARE(shown.rows(), (QStringList{"Line Spacing|How far apart the screen's lines are", "Thickness|How much of the gap a bright line fills; dimmer parts draw it thinner", "Glow|Light blooming around the lines, like a CRT's phosphors", "Dots|Break the lines into beads where the picture is darker than this, blending into solid lines where it's brighter", "Displace|Lift the lines where the picture is bright, or lower them when negative, so they swell into its shapes", "Smoothness|How far the brightness is smoothed before it displaces the lines, from sharp ridges to rounded hills", "Threshold|Draw no line where the picture is darker than this, leaving the screen dark", "Wobble|Make the lines waver sideways down the screen, like a CRT losing sync", "Color Split|Move the red and blue apart, for colored fringes on the lines", "Density|Darker or lighter before the lines are drawn", "Contrast|", "Black Level|How bright the lines are where the picture is black, so they show even over black", "Colors|"})); using Field = double ScanlinesSettings::*; const std::tuple sliders[] = { {"lineSpacing", &ScanlinesSettings::lineSpacing, 2, 32, "px"}, {"thickness", &ScanlinesSettings::thickness, 5, 100, "%"}, {"glow", &ScanlinesSettings::glow, 0, 100, "%"}, {"dots", &ScanlinesSettings::dots, 0, 100, "%"}, {"displace", &ScanlinesSettings::displace, -100, 100, "px"}, {"smoothness", &ScanlinesSettings::smoothness, 0, 100, "%"}, {"threshold", &ScanlinesSettings::threshold, 0, 100, "%"}, {"wobble", &ScanlinesSettings::wobble, 0, 64, "px"}, {"colorSplit", &ScanlinesSettings::split, 0, 16, "px"}, {"density", &ScanlinesSettings::density, -100, 100, ""}, {"contrast", &ScanlinesSettings::contrast, -100, 100, ""}, {"blackLevel", &ScanlinesSettings::blackLevel, 0, 100, "%"}}; for (const auto &[name, field, low, high, unit] : sliders) { QSlider *slider = shown.sheet->findChild(QString::fromLatin1(name) + "Slider"); QLineEdit *edit = shown.sheet->findChild(QString::fromLatin1(name) + "Field"); QVERIFY2(slider && edit, name); slider->setValue(slider->maximum()); QCOMPARE(shown.lines().*field, high); slider->setValue(slider->minimum()); QCOMPARE(shown.lines().*field, low); QCOMPARE(edit->parentWidget()->findChildren(QString(), Qt::FindDirectChildrenOnly).last()->text(), unit); } } void ScanlinesTests::theRowsWriteTheirSettings() { Shown shown; const std::tuple rows[] = { {"lineSpacingField", &ScanlinesSettings::lineSpacing, "12", 12}, {"displaceField", &ScanlinesSettings::displace, "-30", -30}, {"colorSplitField", &ScanlinesSettings::split, "5", 5}, {"blackLevelField", &ScanlinesSettings::blackLevel, "40", 40}, {"smoothnessField", &ScanlinesSettings::smoothness, "10", 10}, {"thicknessField", &ScanlinesSettings::thickness, "55", 55}}; for (const auto &[name, field, typed, expected] : rows) { ScanlinesSettings wanted = shown.lines(); QLineEdit *edit = shown.sheet->findChild(QString::fromLatin1(name)); QVERIFY2(edit, name); edit->setFocus(); edit->selectAll(); QTest::keyClicks(edit, QString::fromLatin1(typed)); QTest::keyClick(edit, Qt::Key_Return); wanted.*field = expected; QVERIFY2(shown.lines() == wanted, name); } } void ScanlinesTests::twoColorsPickTheScreenAndTheLines() { Shown shown; auto *colors = shown.sheet->findChild(QStringLiteral("scanlinesColors")); auto *dark = shown.sheet->findChild(QStringLiteral("scanlinesDark")); auto *light = shown.sheet->findChild(QStringLiteral("scanlinesLight")); QVERIFY(colors && dark && light && !dark->isVisible()); QVERIFY(dark->toolTip() == QString("Choose the screen's color") && light->toolTip() == QString("Choose the lines' color")); colors->activated(int(DitherColors::twoColors)); QVERIFY(shown.lines().colors == DitherColors::twoColors && dark->isVisible() && light->isVisible()); // The menu follows the settings, however they change. FilterSettings original = shown.session.filterEdit().value().settings; original.scanlines.colors = DitherColors::original; shown.session.updateFilter(original, true); QVERIFY(colors->currentIndex() == int(DitherColors::original) && !dark->isVisible()); original.scanlines.colors = DitherColors::twoColors; shown.session.updateFilter(original, true); QCOMPARE(colors->currentIndex(), int(DitherColors::twoColors)); // The picker starts from the lines' own colour. FilterSettings settings = shown.session.filterEdit().value().settings; settings.scanlines.light = AdjustmentColor(0, 1, 0), settings.scanlines.dark = AdjustmentColor(0.2, 0, 0); shown.session.updateFilter(settings, true); light->click(); QCOMPARE(shown.session.colorPicker().value().target.title(), QString("Color Picker (Light Color)")); QVERIFY(shown.session.colorPicker().value().original == (PaletteColor{0, 1, 0})); struct Paints : QObject { int count = 0; bool eventFilter(QObject *, QEvent *event) override { return count += event->type() == QEvent::Paint, false; } } paints; QCoreApplication::processEvents(); light->installEventFilter(&paints); shown.session.setColorPickerHSB(PickerHSB(PaletteColor{1, 0, 0})); shown.session.previewDitherColor(); // A new colour repaints the swatch. QTRY_VERIFY(paints.count > 0); QVERIFY(shown.lines().light == AdjustmentColor(1, 0, 0)); QVERIFY(shown.session.filterEdit().value().settings.dither.light == AdjustmentColor(1, 1, 1)); QCOMPARE(light->grab().toImage().pixelColor(12, 12), QColor(255, 0, 0)); shown.session.closeColorPicker(false); QVERIFY(shown.lines().light == AdjustmentColor(0, 1, 0)); dark->click(); QCOMPARE(shown.session.colorPicker().value().target.title(), QString("Color Picker (Dark Color)")); QVERIFY(shown.session.colorPicker().value().original == (PaletteColor{0.2, 0, 0})); shown.session.setColorPickerHSB(PickerHSB(PaletteColor{0, 0, 1})); shown.session.closeColorPicker(true); QVERIFY(shown.lines().dark == AdjustmentColor(0, 0, 1)); } QTEST_MAIN(ScanlinesTests) #include "ScanlinesTests.moc"