Typed text drawn as its pixels; effects kept while typing, painting masks and undoing (12.12a)
This commit is contained in:
1 parent
8fbb104c48
commit
136ee65a48
23 files changed
+1125
-153
No files matched your search
@@ -0,0 +1,159 @@
|
||||
#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(square.mask.value().placement.value()).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"
|
||||
Reference in new issue
Block a user