Files
OmaPhoto/tests/AdjustmentCanvasTests.cpp
T

124 lines
5.2 KiB
C++

#include "CanvasContractFixtures.h"
// Compositor 1.4's GPU canvas contracts on the CPU canvas: adjustments.
namespace {
// A canvas over a photo and a smaller, translucent layer.
struct Scene : ContractScene {
Scene(int width, int height, int canvasWidth, int canvasHeight) : ContractScene(width, height, canvasWidth, canvasHeight)
{
const QImage photo = noise(width, height, 3);
session.insert(ImportedImage(photo, photo, QStringLiteral("Photo")));
const QImage top = noise(width / 2, height / 2, 5, 160);
session.insert(ImportedImage(top, top, QStringLiteral("Top")));
}
// An adjustment layer of this kind on top, at 80%.
QUuid adjust(const LayerAdjustment &adjustment)
{
session.addAdjustment(adjustment.kind);
session.setAdjustmentEditingID(std::nullopt);
const QUuid id = session.activeLayerID().value();
session.updateAdjustment(id, adjustment);
session.setLayerOpacity(0.8);
return id;
}
};
LayerAdjustment visible(AdjustmentKind kind)
{
LayerAdjustment adjustment{kind};
switch (kind) {
case AdjustmentKind::hsv: adjustment.hsvSettings = HueSaturationSettings(120, 30); break;
case AdjustmentKind::levels: adjustment.levels.ranges[0].outputWhite = 160; break;
case AdjustmentKind::curves: adjustment.curves.channels[0] = {{0, 20}, {120, 170}, {255, 235}}; break;
case AdjustmentKind::exposure: adjustment.setExposure(ExposureSettings{.exposure = 0.8, .gamma = 1.2}); break;
case AdjustmentKind::colorBalance: adjustment.setColorBalance(ColorBalanceSettings{.shadowYellowBlue = -30, .midCyanRed = 40}); break;
case AdjustmentKind::gaussianBlur: adjustment.setGaussianRadius(6); break;
case AdjustmentKind::motionBlur:
adjustment.setResolvedMotionDistance(30);
adjustment.setResolvedMotionAngle(30);
break;
case AdjustmentKind::addNoise:
adjustment.setResolvedNoiseAmount(25);
adjustment.setResolvedNoiseSeed(7);
break;
case AdjustmentKind::grain: adjustment.setGrain(GrainSettings{.amount = 60, .size = 3, .seed = 11}); break;
default: break;
}
return adjustment;
}
}
class AdjustmentCanvasTests : public QObject {
Q_OBJECT
private slots:
void everyAdjustmentShowsAsTheExport_data();
void everyAdjustmentShowsAsTheExport();
void patternsStayWithTheDocument_data();
void patternsStayWithTheDocument();
};
void AdjustmentCanvasTests::everyAdjustmentShowsAsTheExport_data()
{
QTest::addColumn<AdjustmentKind>("kind");
for (const AdjustmentKind kind : allAdjustmentKinds)
QTest::newRow(qPrintable(rawValue(kind))) << kind;
}
// Swift's matchesEveryAdjustment: a mask, 80%, one to one.
void AdjustmentCanvasTests::everyAdjustmentShowsAsTheExport()
{
QFETCH(AdjustmentKind, kind);
Scene scene(60, 50, 60, 50);
const QImage before = scene.exported();
const QUuid id = scene.adjust(visible(kind));
rewrite(scene.session, [&](ProjectSnapshot &snapshot) { setMask(snapshot, id, rampMask(60, 50)); });
scene.session.zoom(1);
QVERIFY(scene.exported() != before);
QVERIFY2(scene.gapToExport() <= 1, qPrintable(QString::number(scene.gapToExport())));
}
void AdjustmentCanvasTests::patternsStayWithTheDocument_data()
{
QTest::addColumn<AdjustmentKind>("kind");
QTest::newRow("addNoise") << AdjustmentKind::addNoise;
QTest::newRow("grain") << AdjustmentKind::grain;
}
// Swift's patternsStayWithTheDocument: wherever it sits, however zoomed.
void AdjustmentCanvasTests::patternsStayWithTheDocument()
{
QFETCH(AdjustmentKind, kind);
Scene scene(60, 50, 140, 120);
scene.adjust(visible(kind));
// One to one, and the crisp path's document pixels.
for (const double zoom : {1.0, 2.0}) {
scene.session.zoom(zoom);
QVERIFY2(scene.gapToExport() <= 1, qPrintable(QStringLiteral("%1: %2").arg(zoom).arg(scene.gapToExport())));
scene.session.viewport.translate(QSizeF(3, -2));
QVERIFY2(scene.gapToExport() <= 1, qPrintable(QStringLiteral("%1 panned: %2").arg(zoom).arg(scene.gapToExport())));
}
// Reduced: a whole-pixel pan moves the pattern with the image.
scene.session.zoom(0.8);
const QImage before = scene.shot();
const QPoint corner = scene.origin().toPoint();
scene.session.viewport.translate(QSizeF(5, 3));
const QImage after = scene.shot();
QCOMPARE(scene.origin().toPoint(), corner + QPoint(5, 3));
int worst = 0;
for (int y = corner.y() + 2; y < corner.y() + 36; ++y) {
for (int x = corner.x() + 2; x < corner.x() + 44; ++x) {
const QRgb was = before.pixel(x, y), now = after.pixel(x + 5, y + 3);
worst = std::max({worst, std::abs(qRed(was) - qRed(now)), std::abs(qGreen(was) - qGreen(now)), std::abs(qBlue(was) - qBlue(now))});
}
}
QVERIFY2(worst <= 1, qPrintable(QString::number(worst)));
// A partial repaint shows the whole's pattern, resampled alike.
const QRect part(corner + QPoint(18, 12), QSize(20, 15));
const QImage piece = scene.canvas.grab(part).toImage().convertToFormat(QImage::Format_ARGB32);
QVERIFY(ContractScene::gap(piece, after.copy(part)) <= 2);
}
QTEST_MAIN(AdjustmentCanvasTests)
#include "AdjustmentCanvasTests.moc"