Files
OmaPhoto/tests/WarpStrokeTests.cpp
T

130 lines
5.4 KiB
C++

#include "BrushFixtures.h"
#include "Document/SmudgeLiquify.h"
#include <cmath>
// 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<int>{200, 0, 0, 255}));
QCOMPARE(pixel(stroke.image(), 21, 10), (std::vector<int>{0, 0, 0, 255}));
QCOMPARE(pixel(stroke.image(), 23, 10), (std::vector<int>{0, 0, 0, 255}));
QCOMPARE(pixel(stroke.image(), 40, 10), (std::vector<int>{0, 0, 0, 255}));
QCOMPARE(pixel(stroke.image(), 41, 10), (std::vector<int>{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<int> 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<QPointF> 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<QPointF>{{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<QPointF>{{0.75, 10}, {2.25, 10}, {3, 10}}));
}
QTEST_GUILESS_MAIN(WarpStrokeTests)
#include "WarpStrokeTests.moc"