160 lines
6.9 KiB
C++
160 lines
6.9 KiB
C++
#include "EffectsCanvasFixtures.h"
|
|
|
|
// Swift 1.2.11 (b683acb): a painted mask keeps effects on.
|
|
namespace {
|
|
bool nearly(QColor colour, QColor expected)
|
|
{
|
|
return std::abs(colour.red() - expected.red()) <= 40 && std::abs(colour.green() - expected.green()) <= 40
|
|
&& std::abs(colour.blue() - expected.blue()) <= 40;
|
|
}
|
|
|
|
// Black down the mask, eight wide; a later pass cuts.
|
|
void paintHole(Scene &scene)
|
|
{
|
|
EditorSession &session = scene.session;
|
|
session.selectLayerTarget(session.activeLayerID().value(), true);
|
|
session.selectTool(NavigationTool::brush);
|
|
session.setMaskPaintWhite(false);
|
|
BrushSettings settings = session.brushSettings();
|
|
settings.diameter = 8;
|
|
settings.hardness = 1;
|
|
session.setBrushSettings(settings);
|
|
session.beginBrush(QPointF(30, 2));
|
|
session.continueBrush(QPointF(30, 6));
|
|
scene.shot();
|
|
session.continueBrush(QPointF(30, 35));
|
|
}
|
|
}
|
|
|
|
class MaskEffectsCanvasTests : public QObject {
|
|
Q_OBJECT
|
|
private slots:
|
|
void aPaintedMaskRedoesTheEffects();
|
|
void aPlacedMaskRedoesThemInTheLayersGrid();
|
|
void untouchedTilesKeepTheOldMask();
|
|
void aFinerPlacedMaskMapsIntoTheLayersGrid();
|
|
};
|
|
|
|
void MaskEffectsCanvasTests::aPaintedMaskRedoesTheEffects()
|
|
{
|
|
Scene scene;
|
|
scene.session.setEffects(outline(2));
|
|
scene.session.addLayerMask(true);
|
|
scene.landed();
|
|
QVERIFY(nearly(scene.at(QPoint(27, 20)), QColor(0, 0, 255)));
|
|
paintHole(scene);
|
|
// Mid-stroke the outline follows the hole: its middle is clear.
|
|
QVERIFY2(nearly(scene.at(QPoint(27, 20)), yellow), qPrintable(scene.at(QPoint(27, 20)).name()));
|
|
QVERIFY2(nearly(scene.at(QPoint(33, 20)), yellow), qPrintable(scene.at(QPoint(33, 20)).name()));
|
|
QCOMPARE(scene.at(QPoint(30, 20)), QColor(Qt::white));
|
|
QVERIFY(nearly(scene.at(QPoint(25, 20)), QColor(0, 0, 255)));
|
|
// Committed, the surface stands in until new effects land.
|
|
QVERIFY(scene.session.finishBrushImmediately());
|
|
scene.canvas.synchronizeDisplay();
|
|
QVERIFY(scene.session.effectsPreviews.rendered(scene.square).value().placement);
|
|
QVERIFY(nearly(scene.at(QPoint(27, 20)), yellow));
|
|
QTRY_VERIFY(!scene.session.effectsPreviews.rendered(scene.square).value().placement);
|
|
QVERIFY(nearly(scene.at(QPoint(27, 20)), yellow));
|
|
}
|
|
|
|
void MaskEffectsCanvasTests::aPlacedMaskRedoesThemInTheLayersGrid()
|
|
{
|
|
Scene scene;
|
|
scene.session.setEffects(outline(2));
|
|
scene.session.addLayerMask(true);
|
|
// A white mask in the layer's grid, placed on it.
|
|
rewrite(scene.session, [&](ProjectSnapshot &snapshot) {
|
|
ProjectLayerRecord &square = record(snapshot, scene.square);
|
|
QImage white = BrushRaster::context(10, 10, true);
|
|
white.fill(255);
|
|
snapshot.masks.insert_or_assign(scene.square, ImportedImage(white, white, QStringLiteral("Mask")));
|
|
square.maskPlacement = square.transform;
|
|
square.maskLinked = false;
|
|
});
|
|
scene.session.selectLayer(scene.square);
|
|
QVERIFY(layerWith(scene.session, scene.square).mask.value().placement);
|
|
scene.landed();
|
|
paintHole(scene);
|
|
QVERIFY2(nearly(scene.at(QPoint(27, 20)), yellow), qPrintable(scene.at(QPoint(27, 20)).name()));
|
|
QVERIFY2(nearly(scene.at(QPoint(33, 20)), yellow), qPrintable(scene.at(QPoint(33, 20)).name()));
|
|
QCOMPARE(scene.at(QPoint(30, 20)), QColor(Qt::white));
|
|
QVERIFY(scene.session.finishBrushImmediately());
|
|
scene.canvas.synchronizeDisplay();
|
|
QVERIFY(nearly(scene.at(QPoint(27, 20)), yellow));
|
|
}
|
|
|
|
void MaskEffectsCanvasTests::untouchedTilesKeepTheOldMask()
|
|
{
|
|
// A layer wider than a tile, painted at one end.
|
|
EditorSession session;
|
|
CanvasView canvas{session};
|
|
session.createDocument(700, 40);
|
|
canvas.resize(700, 40);
|
|
session.viewport.resize(QSizeF(700, 40), 1, QSizeF(700, 40));
|
|
session.zoom(1);
|
|
const QImage blue = solid(600, 10, qRgba(0, 0, 255, 255));
|
|
session.insert(ImportedImage(blue, blue, QStringLiteral("Wide")), QPointF(350, 20));
|
|
session.setEffects(outline(2));
|
|
session.addLayerMask(true);
|
|
canvas.grab();
|
|
session.selectLayerTarget(session.activeLayerID().value(), true);
|
|
session.selectTool(NavigationTool::brush);
|
|
session.setMaskPaintWhite(false);
|
|
session.beginBrush(QPointF(100, 20));
|
|
const QImage shot = canvas.grab().toImage();
|
|
const QPointF far = session.viewport.viewPoint(QPointF(600.5, 20.5), QSizeF(700, 40));
|
|
QCOMPARE(shot.pixelColor(far.toPoint()), QColor(0, 0, 255));
|
|
}
|
|
|
|
void MaskEffectsCanvasTests::aFinerPlacedMaskMapsIntoTheLayersGrid()
|
|
{
|
|
// The committed mask's landed effects are the oracle.
|
|
Scene scene;
|
|
scene.session.setEffects(outline(2));
|
|
scene.session.addLayerMask(true);
|
|
rewrite(scene.session, [&](ProjectSnapshot &snapshot) {
|
|
ProjectLayerRecord &square = record(snapshot, scene.square);
|
|
QImage white = BrushRaster::context(600, 600, true);
|
|
white.fill(255);
|
|
snapshot.masks.insert_or_assign(scene.square, ImportedImage(white, white, QStringLiteral("Mask")));
|
|
square.maskPlacement = square.transform;
|
|
square.maskLinked = false;
|
|
});
|
|
scene.session.selectLayer(scene.square);
|
|
scene.landed();
|
|
scene.session.selectLayerTarget(scene.square, true);
|
|
scene.session.selectTool(NavigationTool::brush);
|
|
scene.session.setMaskPaintWhite(false);
|
|
BrushSettings settings = scene.session.brushSettings();
|
|
settings.diameter = 1;
|
|
settings.hardness = 1;
|
|
scene.session.setBrushSettings(settings);
|
|
scene.session.beginBrush(QPointF(32, 16));
|
|
scene.shot();
|
|
scene.session.continueBrush(QPointF(32, 24));
|
|
const QImage painting = scene.shot();
|
|
// The oracle: a whole render through the stroke's mask.
|
|
const ImageLayer square = layerWith(scene.session, scene.square);
|
|
const QImage preview = scene.session.brushStroke()->placedMaskPreview(scene.session.brushStroke()->paintTransform).value();
|
|
const LayerEffectsRenderer::Rendered expected = LayerEffectsRenderer::render(square.asset.value().image(), preview, outline(2));
|
|
int largest = 0;
|
|
for (int y = 10; y < 30; ++y) {
|
|
for (int x = 20; x < 40; ++x) {
|
|
const QPoint at(x - 25 + int(expected.inset), y - 15 + int(expected.inset));
|
|
if (!expected.image.rect().contains(at))
|
|
continue;
|
|
const QColor over = expected.image.pixelColor(at);
|
|
const QColor shown = painting.pixelColor(scene.shown(QPointF(x + 0.5, y + 0.5)));
|
|
// Over white, premultiplied: the channel plus what shows through.
|
|
const auto on = [&](int channel) { return channel + 255 - over.alpha(); };
|
|
largest = std::max({largest, std::abs(shown.red() - on(qRound(over.redF() * over.alphaF() * 255))),
|
|
std::abs(shown.green() - on(qRound(over.greenF() * over.alphaF() * 255))),
|
|
std::abs(shown.blue() - on(qRound(over.blueF() * over.alphaF() * 255)))});
|
|
}
|
|
}
|
|
QVERIFY2(largest <= 2, qPrintable(QString::number(largest)));
|
|
}
|
|
|
|
QTEST_MAIN(MaskEffectsCanvasTests)
|
|
#include "MaskEffectsCanvasTests.moc"
|