#include "BrushFixtures.h" #include "Document/SmudgeLiquify.h" #include // Swift's MetalWarpTests: Liquify stays sharp, Smudge leaves no ghosts. namespace { WarpStroke warp(const QImage &image, BlurToolMode mode, const BrushSettings &settings) { const ImageLayer layer(ImportedImage(image, image, QStringLiteral("Picture")), QPointF(0, 0)); return WarpStroke(layer, image, layer.transform, QSizeF(image.size()), mode, settings); } } class WarpStrokeTests : public QObject { Q_OBJECT private slots: void liquifyStaysSharp(); void liquifyDrawsFromTheUntouchedLayer(); void smudgeLeavesOneFadingTrail(); void aCommitKeepsATwentiethAndTheLastPoint(); }; // Pushed across and back, the layer ends nearly unchanged. void WarpStrokeTests::liquifyStaysSharp() { // Swift's photo: two ramps, a checkerboard, a half-clear strip. QImage image = BrushRaster::context(300, 200, false); for (int y = 0; y < 200; ++y) { uchar *row = image.scanLine(y); for (int x = 0; x < 300; ++x) { const int a = x < 250 ? 255 : 128; const int check = (x / 12 + y / 12) % 2 == 0 ? 220 : 40; row[x * 4] = uchar(x * 255 / 300 * a / 255); row[x * 4 + 1] = uchar(y * 255 / 200 * a / 255); row[x * 4 + 2] = uchar(check * a / 255); row[x * 4 + 3] = uchar(a); } } WarpStroke stroke = warp(image, BlurToolMode::liquify, brush(60, 0.3, 0, 0, 0, 0.7)); for (double x = 60; x <= 200; x += 4) stroke.append(QPointF(x, 100)); for (double x = 200; x >= 60; x -= 4) stroke.append(QPointF(x, 100)); double change = 0; for (int y = 0; y < 200; ++y) for (int x = 0; x < 300 * 4; ++x) change += std::abs(int(stroke.image().constScanLine(y)[x]) - int(image.constScanLine(y)[x])); change /= 300.0 * 200 * 4; // Swift measured 0.49 sampled once, 2.34 resampled per dab. QVERIFY2(change < 1, qPrintable(QString::number(change))); QVERIFY(change > 0); } // Half-pixel pushes add up to one: stripes stay crisp. void WarpStrokeTests::liquifyDrawsFromTheUntouchedLayer() { QImage image = BrushRaster::context(60, 20, false); for (int x = 0; x < 60; ++x) for (int y = 0; y < 20; ++y) image.setPixel(x, y, qRgba(x % 2 ? 200 : 0, 0, 0, 255)); WarpStroke stroke = warp(image, BlurToolMode::liquify, brush(10, 0.98, 0, 0, 0, 0.5)); stroke.append(QPointF(20, 10)); stroke.append(QPointF(21, 10)); stroke.append(QPointF(22, 10)); // Resampled per dab these read 100; now each, one stripe. QCOMPARE(pixel(stroke.image(), 22, 10), (std::vector{200, 0, 0, 255})); QCOMPARE(pixel(stroke.image(), 21, 10), (std::vector{0, 0, 0, 255})); QCOMPARE(pixel(stroke.image(), 23, 10), (std::vector{0, 0, 0, 255})); QCOMPARE(pixel(stroke.image(), 40, 10), (std::vector{0, 0, 0, 255})); QCOMPARE(pixel(stroke.image(), 41, 10), (std::vector{200, 0, 0, 255})); } void WarpStrokeTests::smudgeLeavesOneFadingTrail() { QImage image = BrushRaster::context(300, 100, false); image.fill(QColor::fromRgbF(0.1, 0.1, 0.1)); { QPainter painter(&image); painter.setRenderHint(QPainter::Antialiasing); painter.setPen(Qt::NoPen); painter.setBrush(Qt::white); painter.drawEllipse(QRectF(52, 42, 16, 16)); } WarpStroke stroke = warp(image, BlurToolMode::smudge, brush(40, 0.5, 0, 0, 0, 0.6)); for (double x = 60; x <= 240; x += 3) stroke.append(QPointF(x, 50)); std::vector row; for (int x = 70; x < 240; ++x) row.push_back(stroke.image().constScanLine(50)[x * 4]); // A ghost is a bump: brighter than two either side. int peaks = 0; for (size_t i = 2; i + 2 < row.size(); ++i) peaks += row[i] > row[i - 2] + 2 && row[i] > row[i + 2] + 2; QCOMPARE(peaks, 0); QVERIFY2(row.front() > row.back() + 20, qPrintable(QString("%1 to %2").arg(row.front()).arg(row.back()))); } // Swift's thinning: a twentieth of the brush apart, last kept. void WarpStrokeTests::aCommitKeepsATwentiethAndTheLastPoint() { QImage image = BrushRaster::context(80, 20, false); image.fill(Qt::red); WarpStroke stroke = warp(image, BlurToolMode::liquify, brush(100, 0.5, 0, 0, 0, 1)); stroke.append(QPointF(0, 10)); stroke.append(QPointF(51, 10)); QCOMPARE(int(stroke.points().size()), 21); const std::vector kept = stroke.committedPoints(); QCOMPARE(int(kept.size()), 8); QCOMPARE(kept.front(), stroke.points().front()); QCOMPARE(kept[1], stroke.points()[3]); QCOMPARE(kept[6], stroke.points()[18]); // Two and a half from the previous, kept as last. QCOMPARE(kept.back(), QPointF(51, 10)); // Exactly the spacing apart is kept: a 40 brush. WarpStroke even = warp(image, BlurToolMode::liquify, brush(40, 0.5, 0, 0, 0, 1)); even.append(QPointF(0, 10)); even.append(QPointF(10, 10)); QCOMPARE(even.committedPoints(), (std::vector{{1, 10}, {3, 10}, {5, 10}, {7, 10}, {9, 10}, {10, 10}})); // A small brush still keeps points a pixel apart. WarpStroke small = warp(image, BlurToolMode::liquify, brush(10, 0.5, 0, 0, 0, 1)); small.append(QPointF(0, 10)); small.append(QPointF(1.5, 10)); small.append(QPointF(3, 10)); QCOMPARE(small.points().size(), size_t(4)); QCOMPARE(small.committedPoints(), (std::vector{{0.75, 10}, {2.25, 10}, {3, 10}})); } QTEST_GUILESS_MAIN(WarpStrokeTests) #include "WarpStrokeTests.moc"