13.19d: 2190a1b verified; sizeLabel's ties tested; tests read optionals through value(); two mask tests brought to 13.12

This commit is contained in:
ZacharyZhang-NY committed 2026-10-03 19:16:59 -04:00
1 parent ab34fd6ace
commit 3757eb8910
40 files changed
+233 -220

No files matched your search

+3 -3
View File
@@ -191,14 +191,14 @@ void BlurNoiseAdjustmentTests::theStoreTakesTheKindsFromVersion9()
QCOMPARE(stored(snapshot), std::optional(ProjectError::Kind::invalid));
// Invalid settings refuse it at any version.
snapshot.manifest.version = 9;
snapshot.manifest.layers[1].adjustment->setGaussianRadius(300);
snapshot.manifest.layers[1].adjustment.value().setGaussianRadius(300);
QCOMPARE(stored(snapshot), std::optional(ProjectError::Kind::invalid));
}
// The rule reads the kind: older kinds may carry keys.
ProjectSnapshot older = twoLayers();
older.manifest.version = 7;
older.manifest.layers[1].adjustment = LayerAdjustment{AdjustmentKind::levels};
older.manifest.layers[1].adjustment->setGaussianRadius(5);
older.manifest.layers[1].adjustment.value().setGaussianRadius(5);
QCOMPARE(stored(older), std::nullopt);
}
@@ -211,7 +211,7 @@ void BlurNoiseAdjustmentTests::eachNoiseLayerRollsItsOwnSeed()
session.setAdjustmentEditingID(std::nullopt);
session.addAdjustment(AdjustmentKind::addNoise);
const LayerAdjustment second = session.activeLayer().value().adjustment.value();
QVERIFY(first.noiseSeed && second.noiseSeed && *first.noiseSeed != *second.noiseSeed);
QVERIFY(first.noiseSeed && second.noiseSeed && first.noiseSeed.value() != second.noiseSeed.value());
// Only noise rolls one; the rest keep their defaults.
session.setAdjustmentEditingID(std::nullopt);
session.addAdjustment(AdjustmentKind::gaussianBlur);
+3 -3
View File
@@ -28,11 +28,11 @@ inline QImage preview(const BrushStroke &stroke, QSizeF canvas)
QPainter painter(&context);
const ImageLayer &layer = stroke.layer;
const std::optional<ImportedImage> &asset = layer.asset;
const QImage image = asset && !asset->raster ? asset->image() : QImage();
const QImage image = asset && !asset.value().raster ? asset.value().image() : QImage();
LayerRenderer::drawBrushPreview(image, stroke.paintTransform, stroke.paintTransform.center(), painter,
{.opacity = layer.opacity, .blendMode = layer.blendMode, .mask = layer.mask ? layer.mask->enabledImage().value_or(QImage()) : QImage()},
{.opacity = layer.opacity, .blendMode = layer.blendMode, .mask = layer.mask ? layer.mask.value().enabledImage().value_or(QImage()) : QImage()},
{.patches = stroke.patches(), .pixelWidth = stroke.width, .pixelHeight = stroke.height, .paintingMask = stroke.isMask,
.sourceRect = stroke.sourceRect, .raster = asset ? asset->raster : nullptr});
.sourceRect = stroke.sourceRect, .raster = asset ? asset.value().raster : nullptr});
painter.end();
return context;
}
+1 -1
View File
@@ -151,7 +151,7 @@ void CanvasEditContractTests::aGradientThatCannotFillCancels()
scene.session.selectTool(NavigationTool::gradient);
scene.session.beginGradient(QPointF(10, 30));
QVERIFY(scene.session.gradientEdit());
scene.session.gradientEdit()->raster->pixelLimit = 100;
scene.session.gradientEdit().value().raster->pixelLimit = 100;
scene.session.moveGradient(std::nullopt, QPointF(70, 30));
QVERIFY(!scene.session.gradientEdit() && scene.session.brushError().has_value());
QCOMPARE(scene.session.history.undoCount(), count);
+1 -1
View File
@@ -19,7 +19,7 @@ struct Shown {
explicit Shown(std::optional<QSize> document = QSize(100, 100), QSize size = QSize(400, 300)) : canvas(new CanvasView(session, &window))
{
if (document)
session.createDocument(document->width(), document->height());
session.createDocument(document.value().width(), document.value().height());
window.resize(size);
canvas->setGeometry(QRect(QPoint(0, 0), size));
window.show();
+2 -2
View File
@@ -253,8 +253,8 @@ void EffectsCanvasTests::aSurfaceIsMadeAnewForOtherSettings()
QVERIFY(session.finishBrushImmediately());
// A canvas alone follows nothing: the old surface stays.
LayerEffects cyan = outline(2);
cyan.stroke->red = 0;
cyan.stroke->blue = 1;
cyan.stroke.value().red = 0;
cyan.stroke.value().blue = 1;
session.setEffects(cyan);
session.beginBrush(QPointF(45, 32));
session.continueBrush(QPointF(55, 32));
+23 -23
View File
@@ -146,56 +146,56 @@ void EffectsPanelTests::theSheetEditsItsEffect()
auto &slider = find<QSlider>(sheet, "sizeSlider");
QCOMPARE(size.text(), QString("4"));
type(size, "12");
QCOMPARE(editor.effects().stroke->size, 12.0);
QCOMPARE(editor.effects().stroke.value().size, 12.0);
// The slider spans 0 to 20; typing reaches 500.
QCOMPARE(slider.value(), 600);
// No finite number changes nothing; the field goes back.
type(size, "abc");
QVERIFY(editor.effects().stroke->size == 12 && size.text() == "12");
QVERIFY(editor.effects().stroke.value().size == 12 && size.text() == "12");
type(size, "inf");
QVERIFY(editor.effects().stroke->size == 12 && size.text() == "12");
QVERIFY(editor.effects().stroke.value().size == 12 && size.text() == "12");
// Up steps one, Shift ten; past the range, clamped.
QTest::keyClick(&size, Qt::Key_Up);
QCOMPARE(editor.effects().stroke->size, 13.0);
QCOMPARE(editor.effects().stroke.value().size, 13.0);
QTest::keyClick(&size, Qt::Key_Up, Qt::ShiftModifier);
QCOMPARE(editor.effects().stroke->size, 23.0);
QCOMPARE(editor.effects().stroke.value().size, 23.0);
// Past its end the thumb rests there, writing nothing back.
QCOMPARE(slider.value(), 1000);
// A step replaces pending typing, even where it clamps.
typing(size, "12");
QTest::keyClick(&size, Qt::Key_Up);
QVERIFY(editor.effects().stroke->size == 24 && size.text() == "24" && !size.isModified());
QVERIFY(editor.effects().stroke.value().size == 24 && size.text() == "24" && !size.isModified());
size.clearFocus();
QCOMPARE(editor.effects().stroke->size, 24.0);
QCOMPARE(editor.effects().stroke.value().size, 24.0);
type(size, "900");
QVERIFY(editor.effects().stroke->size == 500 && size.text() == "500");
QVERIFY(editor.effects().stroke.value().size == 500 && size.text() == "500");
typing(size, "12");
QTest::keyClick(&size, Qt::Key_Up);
QVERIFY(editor.effects().stroke->size == 500 && size.text() == "500" && !size.isModified());
QVERIFY(editor.effects().stroke.value().size == 500 && size.text() == "500" && !size.isModified());
size.clearFocus();
QCOMPARE(editor.effects().stroke->size, 500.0);
QCOMPARE(editor.effects().stroke.value().size, 500.0);
// The slider sets any value; the field rounds, ties even.
slider.setValue(525);
QVERIFY(editor.effects().stroke->size == 10.5 && size.text() == "10");
QVERIFY(editor.effects().stroke.value().size == 10.5 && size.text() == "10");
// Leaving an untouched field keeps the exact value.
size.setFocus();
size.clearFocus();
QCOMPARE(editor.effects().stroke->size, 10.5);
QCOMPARE(editor.effects().stroke.value().size, 10.5);
// Typing waits while other changes arrive.
auto &opacity = find<QLineEdit>(sheet, "opacityField");
QCOMPARE(opacity.text(), QString("100"));
typing(size, "3");
find<QSlider>(sheet, "opacitySlider").setValue(500);
QVERIFY(editor.effects().stroke->opacity == 0.5 && opacity.text() == "50" && size.text() == "3");
QVERIFY(editor.effects().stroke.value().opacity == 0.5 && opacity.text() == "50" && size.text() == "3");
size.clearFocus();
QCOMPARE(editor.effects().stroke->size, 3.0);
QCOMPARE(editor.effects().stroke.value().size, 3.0);
// Inside or Outside, as Swift's segments, following an undo.
const QList<QToolButton *> position = find<QWidget>(sheet, "strokePosition").findChildren<QToolButton *>();
QVERIFY(position.size() == 2 && position[0]->text() == "Outside" && position[1]->text() == "Inside" && position[0]->isChecked());
position[1]->click();
QVERIFY(editor.effects().stroke->inside && position[1]->isChecked());
QVERIFY(editor.effects().stroke.value().inside && position[1]->isChecked());
editor.session.undo();
QVERIFY(!editor.effects().stroke->inside && position[0]->isChecked());
QVERIFY(!editor.effects().stroke.value().inside && position[0]->isChecked());
position[1]->click();
// The swatch opens the picker, whose colour shows at once.
auto &swatch = find<QAbstractButton>(sheet, "effectColor");
@@ -207,7 +207,7 @@ void EffectsPanelTests::theSheetEditsItsEffect()
Painted counter(paints);
swatch.installEventFilter(&counter);
editor.session.setColorPickerHSB(PickerHSB(PaletteColor{1, 0, 0}));
QCOMPARE(editor.effects().stroke->color(), (PaletteColor{1, 0, 0}));
QCOMPARE(editor.effects().stroke.value().color(), (PaletteColor{1, 0, 0}));
QTRY_VERIFY(paints > 0);
// A black rim, a white ring inside, then the colour.
const QImage shot = swatch.grab().toImage();
@@ -218,7 +218,7 @@ void EffectsPanelTests::theSheetEditsItsEffect()
// Return commits, then goes on to OK, keeping everything.
typing(size, "7");
QTest::keyClick(&size, Qt::Key_Return);
QVERIFY(!editor.session.effectsEditing() && editor.effects().stroke->size == 7 && editor.effects().stroke->inside);
QVERIFY(!editor.session.effectsEditing() && editor.effects().stroke.value().size == 7 && editor.effects().stroke.value().inside);
QTRY_VERIFY(!editor.sheet());
}
@@ -236,11 +236,11 @@ void EffectsPanelTests::aWheelOnTheSliderReplacesTheTyping()
Qt::NoScrollPhase, false);
QApplication::sendEvent(&slider, &wheel);
const double wheeled = 20.0 * (200 - QApplication::wheelScrollLines()) / 1000;
QCOMPARE(editor.effects().stroke->size, wheeled);
QCOMPARE(editor.effects().stroke.value().size, wheeled);
QVERIFY(size.hasFocus() && size.text() == QString::number(std::lround(wheeled)) && !size.isModified());
// Return then keeps the slider's value, going on to OK.
QTest::keyClick(&size, Qt::Key_Return);
QVERIFY(!editor.session.effectsEditing() && editor.effects().stroke->size == wheeled);
QVERIFY(!editor.session.effectsEditing() && editor.effects().stroke.value().size == wheeled);
}
void EffectsPanelTests::theSheetKeepsSwiftsMeasures()
@@ -355,14 +355,14 @@ void EffectsPanelTests::cancelAndEscapePutTheEffectBack()
auto &cancel = find<QPushButton>(*editor.sheet(), "effectsCancel");
cancel.setFocus();
QTest::keyClick(&cancel, Qt::Key_Return);
QVERIFY(editor.effects().shadow->angle == -45 && !editor.session.effectsEditing());
QVERIFY(editor.effects().shadow.value().angle == -45 && !editor.session.effectsEditing());
// An existing shadow: Cancel puts it back as it was.
editor.session.selectEffect(LayerEffectKind::shadow, editor.layer, true);
QCOMPARE(find<QLineEdit>(*editor.sheet(), "distanceField").text(), QString("20"));
find<QSlider>(*editor.sheet(), "distanceSlider").setValue(400);
QCOMPARE(editor.effects().shadow->distance, 40.0);
QCOMPARE(editor.effects().shadow.value().distance, 40.0);
find<QPushButton>(*editor.sheet(), "effectsCancel").click();
QVERIFY(editor.effects().shadow->distance == 20 && !editor.session.effectsEditing());
QVERIFY(editor.effects().shadow.value().distance == 20 && !editor.session.effectsEditing());
// A new overlay's panel: Escape takes the overlay away again.
editor.session.addEffect(LayerEffectKind::colorOverlay);
QVERIFY(editor.effects().colorOverlay);
+14 -14
View File
@@ -92,7 +92,7 @@ void EffectsPreviewCacheTests::aPreviewLandsAfterItsDelayAsTheRendererDrawsIt()
QCOMPARE(*landings.count, 1);
// No effects, or none shown: nothing, and the entry goes.
ImageLayer hidden = layer;
hidden.effects->stroke->enabled = false;
hidden.effects.value().stroke.value().enabled = false;
QVERIFY(!ask(cache, hidden, landings) && !cache.rendered(layer.id));
QTest::qWait(150);
QVERIFY(!cache.rendered(layer.id));
@@ -110,7 +110,7 @@ void EffectsPreviewCacheTests::movingTheLayerKeepsThePreviewUnlessAMaskIsPlaced(
EffectsPreviewCache cache;
ImageLayer layer = layerOf(filled(20, 10, QColor(0, 0, 255)), outline(3));
layer.mask = LayerMask(LayerMask::assetFrom(gray(20, 10, 255)));
const QImage mask = layer.mask->enabledImage().value();
const QImage mask = layer.mask.value().enabledImage().value();
const Landings landings;
ask(cache, layer, landings, mask);
const qint64 first = landed(cache, layer, landings, 1).image.cacheKey();
@@ -149,7 +149,7 @@ void EffectsPreviewCacheTests::settingsKeepTheLastPreviewButNewPixelsDropIt()
const EffectsPreviewCache::Result second = landed(cache, layer, landings, 2);
QCOMPARE(second.inset, 7.0);
// Another kind shown or hidden: the last preview stands.
layer.effects->colorOverlay = ColorOverlayEffect();
layer.effects.value().colorOverlay = ColorOverlayEffect();
QCOMPARE(ask(cache, layer, landings).value().image.cacheKey(), second.image.cacheKey());
landed(cache, layer, landings, 3);
// New pixels drop it.
@@ -158,10 +158,10 @@ void EffectsPreviewCacheTests::settingsKeepTheLastPreviewButNewPixelsDropIt()
const qint64 red = landed(cache, layer, landings, 4).image.cacheKey();
// A mask added or removed on those pixels keeps it.
layer.mask = LayerMask(LayerMask::assetFrom(gray(20, 10, 255)));
QCOMPARE(ask(cache, layer, landings, layer.mask->enabledImage()).value().image.cacheKey(), red);
QCOMPARE(ask(cache, layer, landings, layer.mask.value().enabledImage()).value().image.cacheKey(), red);
QVERIFY(landed(cache, layer, landings, 5).image.cacheKey() != red);
// Switched off again, the unmasked effects are known: no render.
layer.mask->isEnabled = false;
layer.mask.value().isEnabled = false;
QCOMPARE(ask(cache, layer, landings).value().image.cacheKey(), red);
QTest::qWait(200);
QCOMPARE(*landings.count, 5);
@@ -325,7 +325,7 @@ void EffectsPreviewCacheTests::preparingDropsLayersWithoutEffectsAndSharesTheBud
ask(cache, small, landings);
landed(cache, small, landings, 5);
ImageLayer off = small;
off.effects->stroke->enabled = false;
off.effects.value().stroke.value().enabled = false;
cache.seed(wide.id, filled(4, 4, Qt::green), placedAt(QPointF(0, 0), QSizeF(4, 4)));
cache.prepare({off});
QVERIFY(!cache.rendered(small.id) && !cache.rendered(wide.id));
@@ -374,7 +374,7 @@ void EffectsPreviewCacheTests::aMaskHidesPixelsInTheReducedPreview()
}
QVERIFY(low == 125 && high == 129);
layer.mask = LayerMask(LayerMask::assetFrom(stripes.scaled(6000, 100)));
ask(cache, layer, landings, layer.mask->enabledImage());
ask(cache, layer, landings, layer.mask.value().enabledImage());
QVERIFY(landed(cache, layer, landings, 4).image == row);
}
@@ -397,13 +397,13 @@ void EffectsPreviewCacheTests::aReducedPreviewScalesEveryLength()
// The renderer on the reduced pixels, every length reduced alike.
const double factor = 1528.0 / (6000 + 2 * LayerEffectsRenderer::margin(effects));
const QImage reduced = image.scaled(1518, 25, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
effects.stroke->size *= factor;
effects.shadow->distance *= factor;
effects.shadow->blur *= factor;
effects.innerShadow->distance *= factor;
effects.innerShadow->blur *= factor;
effects.outerGlow->size *= factor;
effects.innerGlow->size *= factor;
effects.stroke.value().size *= factor;
effects.shadow.value().distance *= factor;
effects.shadow.value().blur *= factor;
effects.innerShadow.value().distance *= factor;
effects.innerShadow.value().blur *= factor;
effects.outerGlow.value().size *= factor;
effects.innerGlow.value().size *= factor;
QVERIFY(result.image == LayerEffectsRenderer::render(reduced, std::nullopt, effects).image);
}
+1 -1
View File
@@ -260,7 +260,7 @@ void FilterSessionTests::aCoveringMaskStaysPutUnderABlur()
session->insert(ImportedImage(image, image, QStringLiteral("Quarter")));
const QUuid id = session->activeLayerID().value();
session->applySelection(rectPath(QRectF(0, 0, 20, 20)), SelectionMode::replace, "Select");
session->addMask(false);
session->addMask(true);
session->selectLayerTarget(id, false);
session->beginFilter(FilterKind::gaussianBlur);
QVERIFY(committed(*session));
+6 -6
View File
@@ -276,11 +276,11 @@ void FinishingFilterTests::aPreviewForAnOlderGridShowsOnThatGrid()
int older = 0;
QObject::connect(&session, &EditorSession::changed, &session, [&] {
const std::optional<FilterEdit> &edit = session.filterEdit();
if (!edit || !edit->preparedPreview)
if (!edit || !edit.value().preparedPreview)
return;
if (QSizeF(edit->preparedPreview->size()) != edit->preparedTransform.value().size)
if (QSizeF(edit.value().preparedPreview.value().size()) != edit.value().preparedTransform.value().size)
mismatched = true;
older += edit->preparedPreview->size() != edit->previewSource.size();
older += edit.value().preparedPreview.value().size() != edit.value().previewSource.size();
});
session.updateFilter(FilterSettings{.bloomRadius = 5}, true);
const LayerTransform grid = session.filterEdit().value().grownTransform.value();
@@ -289,7 +289,7 @@ void FinishingFilterTests::aPreviewForAnOlderGridShowsOnThatGrid()
QVERIFY(session.filterEdit().value().grownTransform.value().size.width() > grid.size.width());
QTRY_VERIFY(session.filterEdit().value().preparedPreview && !session.filterEdit().value().preparing);
QVERIFY(!mismatched && older > 0);
QCOMPARE(session.filterEdit().value().preparedPreview->size(), session.filterEdit().value().previewSource.size());
QCOMPARE(session.filterEdit().value().preparedPreview.value().size(), session.filterEdit().value().previewSource.size());
QCOMPARE(session.filterEdit().value().preparedTransform, session.filterEdit().value().grownTransform);
// Preview off: nothing waits; the stale render ends it.
session.updateFilter(FilterSettings{.bloomRadius = 5}, true);
@@ -313,7 +313,7 @@ void FinishingFilterTests::aJobWaitingBehindAnotherEditShowsOnItsGrid()
bool mismatched = false, armed = true;
QObject::connect(&session, &EditorSession::changed, &session, [&] {
const std::optional<FilterEdit> &edit = session.filterEdit();
if (edit && edit->preparedPreview && QSizeF(edit->preparedPreview->size()) != edit->preparedTransform.value().size)
if (edit && edit.value().preparedPreview && QSizeF(edit.value().preparedPreview.value().size()) != edit.value().preparedTransform.value().size)
mismatched = true;
// The waiting job starts: Preview comes back next turn.
if (std::exchange(armed, false))
@@ -322,7 +322,7 @@ void FinishingFilterTests::aJobWaitingBehindAnotherEditShowsOnItsGrid()
QTRY_VERIFY(!armed);
QTRY_VERIFY(session.filterEdit().value().preparedPreview && !session.filterEdit().value().preparing);
QVERIFY(!mismatched);
QCOMPARE(session.filterEdit().value().preparedPreview->size(), session.filterEdit().value().previewSource.size());
QCOMPARE(session.filterEdit().value().preparedPreview.value().size(), session.filterEdit().value().previewSource.size());
}
void FinishingFilterTests::theVignettePickerPreviewsAndPutsBack()
+23 -23
View File
@@ -48,8 +48,8 @@ void HistoryTests::undoAndRedoReturnTheRecordedSnapshots()
DocumentHistory history;
const std::optional<CanvasDocument> before = documentNamed("Before");
std::optional<CanvasDocument> after = before;
after->layers[0].name = "After";
const QUuid selection = before->layers[0].id;
after.value().layers[0].name = "After";
const QUuid selection = before.value().layers[0].id;
history.begin("Rename Layer", before, std::nullopt);
history.end(after, selection);
QCOMPARE(history.undoCount(), 1);
@@ -96,9 +96,9 @@ void HistoryTests::nestedTransactionsRecordOneEntry()
history.redo();
history.begin("Layer Setup", start, std::nullopt);
QVERIFY(!history.canUndo());
edited->layers[0].name = "B";
edited.value().layers[0].name = "B";
history.begin("Inner", edited, std::nullopt);
edited->layers[0].name = "C";
edited.value().layers[0].name = "C";
history.end(edited, std::nullopt);
QCOMPARE(history.undoCount(), 1);
QVERIFY(!history.canUndo());
@@ -114,12 +114,12 @@ void HistoryTests::noOpEditsPreserveRedo()
DocumentHistory history;
const std::optional<CanvasDocument> start = documentNamed("A");
std::optional<CanvasDocument> renamed = start;
renamed->layers[0].name = "B";
renamed.value().layers[0].name = "B";
history.begin("Rename Layer", start, std::nullopt);
history.end(renamed, std::nullopt);
history.undo();
history.begin("Rename Layer", start, std::nullopt);
history.end(start, start->layers[0].id);
history.end(start, start.value().layers[0].id);
QCOMPARE(history.undoCount(), 0);
QVERIFY(history.canRedo());
QVERIFY(!history.isModified());
@@ -203,16 +203,16 @@ void HistoryTests::historyBoundsEntriesAndUniqueRetainedPixels()
{
DocumentHistory history(2, 0);
std::optional<CanvasDocument> doc = CanvasDocument(64, 32);
doc->layers.push_back(ImageLayer(asset(64, 32, "fixture"), QPointF(0, 0)));
doc.value().layers.push_back(ImageLayer(asset(64, 32, "fixture"), QPointF(0, 0)));
for (const QString name : {"A", "B", "C"}) {
history.begin("Rename", doc, doc->layers[0].id);
doc->layers[0].name = name;
history.end(doc, doc->layers[0].id);
history.begin("Rename", doc, doc.value().layers[0].id);
doc.value().layers[0].name = name;
history.end(doc, doc.value().layers[0].id);
}
QCOMPARE(history.undoCount(), 2);
QCOMPARE(history.retainedBytes(doc), 0);
history.begin("Delete", doc, doc->layers[0].id);
doc->layers.clear();
history.begin("Delete", doc, doc.value().layers[0].id);
doc.value().layers.clear();
history.end(doc, std::nullopt);
QCOMPARE(history.undoCount(), 0);
QCOMPARE(history.retainedBytes(doc), 0);
@@ -222,15 +222,15 @@ void HistoryTests::retainedBytesCountEachHistoryOnlyImageOnce()
{
DocumentHistory history;
std::optional<CanvasDocument> doc = CanvasDocument(64, 32);
doc->layers.push_back(ImageLayer(asset(10, 10, "old"), QPointF(0, 0)));
doc.value().layers.push_back(ImageLayer(asset(10, 10, "old"), QPointF(0, 0)));
for (const QString name : {"A", "B"}) {
history.begin("Rename", doc, std::nullopt);
doc->layers[0].name = name;
doc.value().layers[0].name = name;
history.end(doc, std::nullopt);
}
QCOMPARE(history.retainedBytes(doc), 0);
history.begin("Replace", doc, std::nullopt);
doc->layers[0].asset = asset(20, 10, "new");
doc.value().layers[0].asset = asset(20, 10, "new");
history.end(doc, std::nullopt);
const qint64 oldBytes = 10 * 10 * 4 + 8 * 8 * 4;
QCOMPARE(history.retainedBytes(doc), oldBytes);
@@ -242,13 +242,13 @@ void HistoryTests::byteLimitDropsTheOldestPastEntryFirst()
const qint64 small = 10 * 10 * 4 + 8 * 8 * 4;
DocumentHistory history(100, small);
std::optional<CanvasDocument> doc = CanvasDocument(64, 32);
doc->layers.push_back(ImageLayer(asset(10, 10, "x"), QPointF(0, 0)));
doc.value().layers.push_back(ImageLayer(asset(10, 10, "x"), QPointF(0, 0)));
history.begin("First", doc, std::nullopt);
doc->layers[0].asset = asset(10, 10, "y");
doc.value().layers[0].asset = asset(10, 10, "y");
history.end(doc, std::nullopt);
QCOMPARE(history.undoCount(), 1);
history.begin("Second", doc, std::nullopt);
doc->layers[0].asset = asset(10, 10, "z");
doc.value().layers[0].asset = asset(10, 10, "z");
history.end(doc, std::nullopt);
QCOMPARE(history.undoCount(), 1);
QCOMPARE(history.undoName(), QString("Second"));
@@ -260,10 +260,10 @@ void HistoryTests::undoTrimsRedoWhenItsPixelsExceedTheLimit()
const qint64 small = 10 * 10 * 4 + 8 * 8 * 4;
DocumentHistory history(100, small);
std::optional<CanvasDocument> doc = CanvasDocument(64, 32);
doc->layers.push_back(ImageLayer(asset(10, 10, "small"), QPointF(0, 0)));
doc.value().layers.push_back(ImageLayer(asset(10, 10, "small"), QPointF(0, 0)));
const std::optional<CanvasDocument> before = doc;
history.begin("Enlarge", doc, std::nullopt);
doc->layers[0].asset = asset(40, 40, "large");
doc.value().layers[0].asset = asset(40, 40, "large");
history.end(doc, std::nullopt);
QCOMPARE(history.undoCount(), 1);
const DocumentHistory::Snapshot undone = history.undo().value();
@@ -277,12 +277,12 @@ void HistoryTests::undoTrimsThePastBeforeTheRedoStack()
const qint64 small = 10 * 10 * 4 + 8 * 8 * 4;
DocumentHistory history(100, 2 * small);
std::optional<CanvasDocument> doc = CanvasDocument(64, 32);
doc->layers.push_back(ImageLayer(asset(10, 10, "a"), QPointF(0, 0)));
doc.value().layers.push_back(ImageLayer(asset(10, 10, "a"), QPointF(0, 0)));
history.begin("First", doc, std::nullopt);
doc->layers[0].asset = asset(10, 10, "b");
doc.value().layers[0].asset = asset(10, 10, "b");
history.end(doc, std::nullopt);
history.begin("Second", doc, std::nullopt);
doc->layers[0].asset = asset(15, 15, "c");
doc.value().layers[0].asset = asset(15, 15, "c");
history.end(doc, std::nullopt);
QCOMPARE(history.undoCount(), 2);
history.undo();
+1 -1
View File
@@ -244,7 +244,7 @@ void ImageAdjustmentTests::newAdjustmentLayersStartFromThePaletteRenderAndEditIn
session.setAdjustmentEditingID(std::nullopt);
session.addAdjustment(AdjustmentKind::grain);
const std::vector<ImageLayer> &layers = session.document().value().layers;
QCOMPARE(std::count_if(layers.begin(), layers.end(), [](const ImageLayer &layer) { return layer.adjustment && layer.adjustment->kind == AdjustmentKind::grain; }), 2);
QCOMPARE(std::count_if(layers.begin(), layers.end(), [](const ImageLayer &layer) { return layer.adjustment && layer.adjustment.value().kind == AdjustmentKind::grain; }), 2);
QVERIFY2(session.activeLayer().value().adjustment.value().grain().seed != first, "each Grain layer gets its own pattern");
QVERIFY(std::find(allAdjustmentKinds.begin(), allAdjustmentKinds.end(), AdjustmentKind::exposure) != allAdjustmentKinds.end()
&& filterKind(AdjustmentKind::exposure) == FilterKind::exposure);
+2 -2
View File
@@ -135,7 +135,7 @@ void ImageFileDropTests::aPictureIsCopiedToAFileFirst()
sky.image = PSDFixture::colorImage(4, 2, 0, 0, 1);
sky.bounds = QRectF(0, 0, 4, 2);
QMimeData photoshop;
photoshop.setData("image/vnd.adobe.photoshop", PSDFixture::data(PSDDocument{4, 2, 72, {sky}}, *sky.image));
photoshop.setData("image/vnd.adobe.photoshop", PSDFixture::data(PSDDocument{4, 2, 72, {sky}}, sky.image.value()));
photoshop.setData("application/x-other", "ignored");
QCOMPARE(ImageFileDrop::providers(photoshop).front()->formats(), QStringList{"image/vnd.adobe.photoshop"});
photoshop.setData("image/tiff", "tiff first");
@@ -165,7 +165,7 @@ void ImageFileDropTests::aPictureIsCopiedToAFileFirst()
done = false;
ImageFileDrop::importProviders(raw, developed, std::nullopt, nullptr, std::nullopt, [&done] { done = true; });
QTRY_VERIFY(developed.rawDevelop());
developed.finishRawDevelop(developed.rawDevelop()->settings);
developed.finishRawDevelop(developed.rawDevelop().value().settings);
QTRY_VERIFY(done);
QCOMPARE(names(developed), QStringList{"Dropped"});
QCOMPARE(takeCopy(before), QString("Dropped.dng"));
+1 -1
View File
@@ -222,7 +222,7 @@ void ImageTrimTests::theSheetSaysSwiftsWordsAndReturnsItsChoice()
ok.click();
QVERIFY(answer.value() == edges(TrimBasedOn::bottomRightPixelColor, false, true, false, false));
sheet.findChild<QPushButton *>("trimCancel")->click();
QVERIFY(answer && !answer->has_value());
QVERIFY(answer && !answer.value().has_value());
}
void ImageTrimTests::theControllerTrimsThroughTheSheetAsOneStep()
+6 -6
View File
@@ -77,7 +77,7 @@ void InnerGlowTests::innerGlowCodableRoundTrip()
QCOMPARE(ManifestJson::effects(encoded), original);
// Absent `enabled` stays absent, read as shown.
LayerEffects shown = original;
shown.innerGlow->enabled = std::nullopt;
shown.innerGlow.value().enabled = std::nullopt;
QVERIFY(!ManifestJson::encoded(shown).value("innerGlow").toObject().contains("enabled"));
QCOMPARE(ManifestJson::effects(ManifestJson::encoded(shown)), shown);
// A key of the wrong type, or missing, refuses it.
@@ -165,10 +165,10 @@ void InnerGlowTests::itSitsOverTheOverlayAndUnderTheInnerShadow()
const LayerEffectsRenderer::Rendered shadowed = LayerEffectsRenderer::render(square(40, Qt::black), std::nullopt, effects);
QCOMPARE(at(shadowed, 0, 20), at(covered, 0, 20));
// A sideways shadow covers the glow on one edge.
effects.innerShadow->distance = 4;
const int x = effects.innerShadow->offset().width() > 0 ? 0 : 39;
effects.innerShadow.value().distance = 4;
const int x = effects.innerShadow.value().offset().width() > 0 ? 0 : 39;
const QColor edge = at(LayerEffectsRenderer::render(square(40, Qt::black), std::nullopt, effects), x, 20);
QVERIFY(std::abs(effects.innerShadow->offset().width()) > 3.9);
QVERIFY(std::abs(effects.innerShadow.value().offset().width()) > 3.9);
QVERIFY(edge.redF() > 0.9 && edge.greenF() < 0.1);
}
@@ -195,11 +195,11 @@ void InnerGlowTests::theSessionAddsCopiesAndCancelsIt()
session.addEffect(LayerEffectKind::innerGlow);
QCOMPARE(session.history.undoName(), QString("Add Inner Glow"));
QVERIFY(session.activeEffects().innerGlow == InnerGlowEffect());
session.changeEffects([](LayerEffects &effects) { effects.innerGlow->size = 44; });
session.changeEffects([](LayerEffects &effects) { effects.innerGlow.value().size = 44; });
session.finishEffectsEditing(false);
QVERIFY(!session.activeEffects().innerGlow);
session.addEffect(LayerEffectKind::innerGlow);
session.changeEffects([](LayerEffects &effects) { effects.innerGlow->size = 44; });
session.changeEffects([](LayerEffects &effects) { effects.innerGlow.value().size = 44; });
session.finishEffectsEditing(true);
// Adding it again keeps the glow it has.
session.addEffect(LayerEffectKind::innerGlow);
+5 -5
View File
@@ -132,14 +132,14 @@ void LayerAdjustmentTests::validityFollowsSwiftsBounds()
QCOMPARE((LayerAdjustment{.kind = AdjustmentKind::hsv, .hue = hue, .saturation = saturation, .lightness = lightness}.isValid()), valid);
// Each range's values too, and every band's handles finite.
LayerAdjustment ranged = full();
ranged.hsvSettings->adjustments[ColorRange::blues] = {0, 0, 101};
ranged.hsvSettings.value().adjustments[ColorRange::blues] = {0, 0, 101};
QVERIFY(!ranged.isValid());
ranged = full();
ranged.hsvSettings->bands[ColorRange::cyans].falloffEnd = std::numeric_limits<double>::infinity();
ranged.hsvSettings.value().bands[ColorRange::cyans].falloffEnd = std::numeric_limits<double>::infinity();
QVERIFY(!ranged.isValid());
// The legacy fields count only when no range settings exist.
ranged = full();
ranged.hsvSettings->adjustments[ColorRange::reds] = {-360, -100, -100};
ranged.hsvSettings.value().adjustments[ColorRange::reds] = {-360, -100, -100};
QVERIFY(ranged.isValid());
// Levels as normalized, then every kind's own rules.
LayerAdjustment levels{AdjustmentKind::hsv};
@@ -308,7 +308,7 @@ void LayerAdjustmentTests::theStoreKeepsAdjustmentsToVersion7WithoutPixels()
blank.isGroup = true;
QCOMPARE(stored(snapshot), ProjectError::Kind::invalid);
blank.isGroup = std::nullopt;
blank.adjustment->hue = 400;
blank.adjustment.value().hue = 400;
QCOMPARE(stored(snapshot), ProjectError::Kind::invalid);
}
@@ -440,7 +440,7 @@ void LayerAdjustmentTests::adjustmentsTravelWithTheirLayers()
session->duplicateActiveLayer();
QVERIFY(session->activeLayer().value().adjustment == full());
ImageLayer changed = session->activeLayer().value();
changed.adjustment->hue = 11;
changed.adjustment.value().hue = 11;
QVERIFY(!(changed == session->activeLayer().value()));
}
+9 -9
View File
@@ -237,15 +237,15 @@ void LayerEffectRowTests::theEyeShowsOrHidesItsEffect()
LayerEffectRow &stroke = *listed.effects(0)[0];
// One step each way; the choice stays as it was.
listed.click(listed.eye(stroke), QPoint(10, 11));
QVERIFY(!layerWith(listed.session, top).effects->stroke->isEnabled() && listed.session.history.undoName() == "Hide Stroke");
QVERIFY(!layerWith(listed.session, top).effects.value().stroke.value().isEnabled() && listed.session.history.undoName() == "Hide Stroke");
QVERIFY(!listed.chosen() && listed.eye(stroke).accessibleName() == "Show Stroke");
listed.click(listed.eye(stroke), QPoint(10, 11));
QVERIFY(layerWith(listed.session, top).effects->stroke->isEnabled() && listed.session.history.undoName() == "Show Stroke");
QVERIFY(layerWith(listed.session, top).effects.value().stroke.value().isEnabled() && listed.session.history.undoName() == "Show Stroke");
// It rests while layers cannot be edited.
listed.session.setIsProjectBusy(true);
QVERIFY(!listed.eye(stroke).isEnabled());
listed.click(listed.eye(stroke), QPoint(10, 11));
QVERIFY(layerWith(listed.session, top).effects->stroke->isEnabled() && !listed.chosen());
QVERIFY(layerWith(listed.session, top).effects.value().stroke.value().isEnabled() && !listed.chosen());
listed.session.setIsProjectBusy(false);
QVERIFY(listed.eye(stroke).isEnabled());
}
@@ -270,14 +270,14 @@ void LayerEffectRowTests::pressesFollowSwiftsOrder()
listed.session.dropEffectSelection();
listed.press(listed.eye(shadow), QPoint(10, 5), Qt::AltModifier);
listed.release(listed.eye(shadow), QPoint(10, 5), Qt::AltModifier);
QVERIFY(layerWith(listed.session, top).effects->shadow->isEnabled() && listed.chosen() == (LayerEffectSelection{top, LayerEffectKind::shadow}));
QVERIFY(layerWith(listed.session, top).effects.value().shadow.value().isEnabled() && listed.chosen() == (LayerEffectSelection{top, LayerEffectKind::shadow}));
// An Alt double click on the eye hides nothing either.
listed.session.dropEffectSelection();
QTest::mouseDClick(listed.list.windowHandle(), Qt::LeftButton, Qt::AltModifier, listed.at(listed.eye(shadow), QPoint(12, 5)));
QVERIFY(layerWith(listed.session, top).effects->shadow->isEnabled() && listed.chosen() == (LayerEffectSelection{top, LayerEffectKind::shadow}));
QVERIFY(layerWith(listed.session, top).effects.value().shadow.value().isEnabled() && listed.chosen() == (LayerEffectSelection{top, LayerEffectKind::shadow}));
// On the strip, the eye's Alt press clips too.
listed.click(listed.eye(shadow), QPoint(10, 20), Qt::AltModifier);
QVERIFY(!layerWith(listed.session, top).maskSourceID && !listed.chosen() && layerWith(listed.session, top).effects->shadow->isEnabled());
QVERIFY(!layerWith(listed.session, top).maskSourceID && !listed.chosen() && layerWith(listed.session, top).effects.value().shadow.value().isEnabled());
// Its own row's press lets go, keeping the layer.
listed.session.selectEffect(LayerEffectKind::stroke, top);
listed.click(listed.cell(0), QPoint(listed.cell(0).width() - 20, 20));
@@ -295,7 +295,7 @@ void LayerEffectRowTests::pressesFollowSwiftsOrder()
QVERIFY(listed.chosen() == (LayerEffectSelection{top, LayerEffectKind::shadow}));
listed.release(shadow, QPoint(150, 5), Qt::AltModifier | Qt::ControlModifier);
listed.click(listed.eye(shadow), QPoint(10, 5), Qt::AltModifier | Qt::ControlModifier);
QVERIFY(!layerWith(listed.session, top).effects->shadow->isEnabled());
QVERIFY(!layerWith(listed.session, top).effects.value().shadow.value().isEnabled());
// A plain press on another effect chooses it at once.
listed.press(stroke, QPoint(150, 12));
QVERIFY(listed.chosen() == (LayerEffectSelection{top, LayerEffectKind::stroke}));
@@ -412,7 +412,7 @@ void LayerEffectRowTests::effectDropsLandOnTheRowUnderThePointer()
const std::unique_ptr<QMimeData> probe(effectDrag(listed.list, payload));
QCOMPARE(listed.list.dropTarget(*probe, Qt::CopyAction, onMiddle).value(), (LayerDropTarget{.row = 1, .onRow = true, .atBottom = false, .copying = true}));
QVERIFY(listed.drop(effectDrag(listed.list, payload), onMiddle));
QVERIFY(listed.lastAction == Qt::CopyAction && layerWith(listed.session, middle).effects->stroke->size == 12);
QVERIFY(listed.lastAction == Qt::CopyAction && layerWith(listed.session, middle).effects.value().stroke.value().size == 12);
QVERIFY(listed.session.history.undoName() == "Copy Stroke" && listed.chosen() == (LayerEffectSelection{middle, LayerEffectKind::stroke}));
// Refused: its own row, a missing kind, strangers, bad words.
QVERIFY(!listed.drop(effectDrag(listed.list, payload), onTop));
@@ -448,7 +448,7 @@ void LayerEffectRowTests::aRowRebuiltUnderItsOwnPressLivesOn()
QTest::mouseDClick(shadow.data(), Qt::LeftButton, Qt::NoModifier, QPoint(150, 12));
QVERIFY(shadow && !shadow->isVisible());
QVERIFY(listed.session.effectsEditing() == (LayerEffectSelection{top, LayerEffectKind::shadow}) && listed.list.hasFocus());
QVERIFY(!layerWith(listed.session, top).effects->stroke && listed.effects(0).size() == 1);
QVERIFY(!layerWith(listed.session, top).effects.value().stroke && listed.effects(0).size() == 1);
QTRY_VERIFY(!shadow);
}
+25 -25
View File
@@ -86,32 +86,32 @@ QImage replayed(const QImage &pixels, const LayerEffects &effects)
for (int x = 0; x < pixels.width(); ++x)
shader.shape.push_back(float(pixels.constScanLine(y)[x * 4 + 3]) / 255.f);
}
const auto on = [](const auto &effect) { return effect && effect->isEnabled() && effect->opacity > 0; };
const bool stroke = on(effects.stroke) && effects.stroke->size > 0;
const auto on = [](const auto &effect) { return effect && effect.value().isEnabled() && effect.value().opacity > 0; };
const bool stroke = on(effects.stroke) && effects.stroke.value().size > 0;
std::vector<float> ring(shader.shape.size()), cast(shader.shape.size()), inner(shader.shape.size()), glow(shader.shape.size()),
innerGlow(shader.shape.size());
if (stroke) {
const int reach = std::max(1, int(std::lround(effects.stroke->size)));
const std::vector<float> moved = shader.spread(shader.spread(shader.shape, reach, effects.stroke->inside, true), reach, effects.stroke->inside, false);
const int reach = std::max(1, int(std::lround(effects.stroke.value().size)));
const std::vector<float> moved = shader.spread(shader.spread(shader.shape, reach, effects.stroke.value().inside, true), reach, effects.stroke.value().inside, false);
for (size_t index = 0; index < ring.size(); ++index)
ring[index] = std::clamp(effects.stroke->inside ? shader.shape[index] - moved[index] : moved[index] - shader.shape[index], 0.f, 1.f);
ring[index] = std::clamp(effects.stroke.value().inside ? shader.shape[index] - moved[index] : moved[index] - shader.shape[index], 0.f, 1.f);
}
if (on(effects.shadow))
cast = shader.softened(shader.shift(shader.shape, float(effects.shadow->offset().width()), float(effects.shadow->offset().height())),
float(effects.shadow->blur / 2));
const bool glowing = on(effects.outerGlow) && effects.outerGlow->size > 0;
cast = shader.softened(shader.shift(shader.shape, float(effects.shadow.value().offset().width()), float(effects.shadow.value().offset().height())),
float(effects.shadow.value().blur / 2));
const bool glowing = on(effects.outerGlow) && effects.outerGlow.value().size > 0;
if (glowing)
glow = shader.softened(shader.shape, float(effects.outerGlow->size / 2));
const bool glowingInside = on(effects.innerGlow) && effects.innerGlow->size > 0;
glow = shader.softened(shader.shape, float(effects.outerGlow.value().size / 2));
const bool glowingInside = on(effects.innerGlow) && effects.innerGlow.value().size > 0;
if (glowingInside) {
const std::vector<float> softened = shader.softened(shader.shape, float(effects.innerGlow->size / 2));
const std::vector<float> softened = shader.softened(shader.shape, float(effects.innerGlow.value().size / 2));
for (size_t index = 0; index < innerGlow.size(); ++index)
innerGlow[index] = std::clamp(shader.shape[index] * (1.f - softened[index]), 0.f, 1.f);
}
if (on(effects.innerShadow)) {
const std::vector<float> moved = shader.softened(shader.shift(shader.shape, float(effects.innerShadow->offset().width()),
float(effects.innerShadow->offset().height())),
float(effects.innerShadow->blur / 2));
const std::vector<float> moved = shader.softened(shader.shift(shader.shape, float(effects.innerShadow.value().offset().width()),
float(effects.innerShadow.value().offset().height())),
float(effects.innerShadow.value().blur / 2));
for (size_t index = 0; index < inner.size(); ++index)
inner[index] = std::clamp(shader.shape[index] * (1.f - moved[index]), 0.f, 1.f);
}
@@ -127,25 +127,25 @@ QImage replayed(const QImage &pixels, const LayerEffects &effects)
alpha = coverage + alpha * (1.f - coverage);
};
if (on(effects.shadow))
blend(effects.shadow->color(), std::clamp(cast[index] * float(effects.shadow->opacity), 0.f, 1.f));
blend(effects.shadow.value().color(), std::clamp(cast[index] * float(effects.shadow.value().opacity), 0.f, 1.f));
if (glowing)
blend(effects.outerGlow->color(), std::clamp(glow[index] * (1.f - shader.shape[index]) * float(effects.outerGlow->opacity), 0.f, 1.f));
const float strokeCoverage = stroke ? std::clamp(ring[index] * float(effects.stroke->opacity), 0.f, 1.f) : 0.f;
if (stroke && !effects.stroke->inside)
blend(effects.stroke->color(), strokeCoverage);
blend(effects.outerGlow.value().color(), std::clamp(glow[index] * (1.f - shader.shape[index]) * float(effects.outerGlow.value().opacity), 0.f, 1.f));
const float strokeCoverage = stroke ? std::clamp(ring[index] * float(effects.stroke.value().opacity), 0.f, 1.f) : 0.f;
if (stroke && !effects.stroke.value().inside)
blend(effects.stroke.value().color(), strokeCoverage);
const uchar *source = pixels.constScanLine(y) + x * 4;
const float sourceAlpha = float(source[3]) / 255.f;
for (int channel = 0; channel < 3; ++channel)
colour[channel] = float(source[channel]) / 255.f + colour[channel] * (1.f - sourceAlpha);
alpha = sourceAlpha + alpha * (1.f - sourceAlpha);
if (on(effects.colorOverlay))
blend(effects.colorOverlay->color(), std::clamp(shader.shape[index] * float(effects.colorOverlay->opacity), 0.f, 1.f));
blend(effects.colorOverlay.value().color(), std::clamp(shader.shape[index] * float(effects.colorOverlay.value().opacity), 0.f, 1.f));
if (glowingInside)
blend(effects.innerGlow->color(), std::clamp(innerGlow[index] * float(effects.innerGlow->opacity), 0.f, 1.f));
blend(effects.innerGlow.value().color(), std::clamp(innerGlow[index] * float(effects.innerGlow.value().opacity), 0.f, 1.f));
if (on(effects.innerShadow))
blend(effects.innerShadow->color(), std::clamp(inner[index] * float(effects.innerShadow->opacity), 0.f, 1.f));
if (stroke && effects.stroke->inside)
blend(effects.stroke->color(), strokeCoverage);
blend(effects.innerShadow.value().color(), std::clamp(inner[index] * float(effects.innerShadow.value().opacity), 0.f, 1.f));
if (stroke && effects.stroke.value().inside)
blend(effects.stroke.value().color(), strokeCoverage);
uchar *out = result.scanLine(y) + x * 4;
for (int channel = 0; channel < 3; ++channel)
out[channel] = uchar(std::clamp(colour[channel], 0.f, 1.f) * 255.f + 0.5f);
@@ -219,7 +219,7 @@ void LayerEffectsKernelTests::everyEffectMatchesTheShader_data()
all.innerShadow = inner.innerShadow;
all.outerGlow = glow.outerGlow;
all.innerGlow = innerGlow.innerGlow;
all.shadow->blur = 0;
all.shadow.value().blur = 0;
wide.stroke = StrokeEffect{.size = 9, .red = 1, .opacity = 1};
wide.shadow = ShadowEffect{.angle = 200, .distance = 0.5, .blur = 0.01, .opacity = 1};
QTest::newRow("outside stroke") << outside;
+6 -6
View File
@@ -60,11 +60,11 @@ void LayerEffectsRendererTests::theMarginHoldsWhatReachesOut()
LayerEffects cast = stroke(10);
cast.shadow = ShadowEffect{.distance = 5, .blur = 2.5};
QCOMPARE(LayerEffectsRenderer::margin(cast), 15.0);
cast.shadow->blur = 4;
cast.shadow.value().blur = 4;
QCOMPARE(LayerEffectsRenderer::margin(cast), 19.0);
// Switched off, it takes no room.
cast.shadow->enabled = false;
cast.stroke->enabled = false;
cast.shadow.value().enabled = false;
cast.stroke.value().enabled = false;
QCOMPARE(LayerEffectsRenderer::margin(cast), 2.0);
}
@@ -113,11 +113,11 @@ void LayerEffectsRendererTests::renderingPadsMasksThenRunsTheKernel()
QVERIFY(pixel(soft, 2, 10)[3] == 0 && pixel(soft, 65, 10)[3] == 255);
// Switched-off effects are none; invalid or vast ones refuse.
LayerEffects hidden = stroke(3);
hidden.stroke->enabled = false;
hidden.stroke.value().enabled = false;
QCOMPARE(LayerEffectsRenderer::render(image, std::nullopt, hidden).image.size(), QSize(24, 14));
// A switched-off effect is not judged.
LayerEffects wild = stroke(600);
wild.stroke->enabled = false;
wild.stroke.value().enabled = false;
QVERIFY(LayerEffectsRenderer::render(image, std::nullopt, wild).image == LayerEffectsRenderer::render(image, std::nullopt, hidden).image);
try {
LayerEffectsRenderer::render(image, std::nullopt, stroke(501));
@@ -142,7 +142,7 @@ void LayerEffectsRendererTests::theCacheKeepsEightWithinItsBudget()
QVERIFY(!LayerEffectsRenderer::cached(image, std::nullopt, std::nullopt));
QVERIFY(!LayerEffectsRenderer::cached(image, std::nullopt, LayerEffects()));
LayerEffects hidden = stroke(2);
hidden.stroke->enabled = false;
hidden.stroke.value().enabled = false;
QVERIFY(!LayerEffectsRenderer::cached(image, std::nullopt, hidden));
QVERIFY(!LayerEffectsRenderer::cached(image, std::nullopt, stroke(600)));
// The same pixels, mask and effects: the same result.
+22 -22
View File
@@ -83,7 +83,7 @@ void LayerEffectsSessionTests::addingAnEffectTakesItsDefaultsAndOpensItsPanel()
present.shadow = ShadowEffect{.distance = 33};
session.setEffects(present);
session.addEffect(LayerEffectKind::shadow);
QVERIFY(two.effects(two.top).value().shadow->distance == 33);
QVERIFY(two.effects(two.top).value().shadow.value().distance == 33);
session.finishEffectsEditing(true);
const int kept = session.history.undoCount();
session.addEffect(LayerEffectKind::colorOverlay);
@@ -128,15 +128,15 @@ void LayerEffectsSessionTests::cancelPutsBackThisPanelsEffectAlone()
session.setEffects(start);
session.selectEffect(LayerEffectKind::stroke, two.top, true);
QVERIFY(session.effectsEditingOriginal() == start);
session.changeEffects([](LayerEffects &effects) { effects.stroke->size = 9; });
session.changeEffects([](LayerEffects &effects) { effects.stroke.value().size = 9; });
QCOMPARE(session.history.undoName(), QString("Edit Stroke"));
// Another effect changed meanwhile keeps its change.
LayerEffects other = two.effects(two.top).value();
other.shadow->distance = 30;
other.shadow.value().distance = 30;
session.setEffects(other);
session.finishEffectsEditing(false);
const LayerEffects after = two.effects(two.top).value();
QVERIFY(after.stroke->size == 3 && after.shadow->distance == 30);
QVERIFY(after.stroke.value().size == 3 && after.shadow.value().distance == 30);
QCOMPARE(session.history.undoName(), QString("Cancel Stroke"));
QVERIFY(!session.effectsEditing() && !session.effectsEditingOriginal());
// The chosen effect stays chosen while its layer shows it.
@@ -147,26 +147,26 @@ void LayerEffectsSessionTests::cancelPutsBackThisPanelsEffectAlone()
QVERIFY(!two.effects(two.top).value().innerShadow);
QVERIFY(!session.effectSelection());
session.selectEffect(LayerEffectKind::shadow, two.top, true);
session.changeEffects([](LayerEffects &effects) { effects.shadow->blur = 2; });
session.changeEffects([](LayerEffects &effects) { effects.shadow.value().blur = 2; });
const int steps = session.history.undoCount();
session.finishEffectsEditing(true);
QVERIFY(session.history.undoCount() == steps && two.effects(two.top).value().shadow->blur == 2);
QVERIFY(session.history.undoCount() == steps && two.effects(two.top).value().shadow.value().blur == 2);
// Nothing open, nothing to finish.
session.finishEffectsEditing(false);
QCOMPARE(session.history.undoCount(), steps);
// Opening another panel cancels the open one's changes.
session.selectEffect(LayerEffectKind::stroke, two.top, true);
session.changeEffects([](LayerEffects &effects) { effects.stroke->size = 40; });
session.changeEffects([](LayerEffects &effects) { effects.stroke.value().size = 40; });
session.selectEffect(LayerEffectKind::shadow, two.top, true);
QVERIFY(two.effects(two.top).value().stroke->size == 3 && session.history.undoName() == QString("Cancel Stroke"));
QVERIFY(two.effects(two.top).value().stroke.value().size == 3 && session.history.undoName() == QString("Cancel Stroke"));
session.finishEffectsEditing(true);
// An overlay's Cancel puts back the overlay alone.
session.addEffect(LayerEffectKind::colorOverlay);
session.finishEffectsEditing(true);
session.selectEffect(LayerEffectKind::colorOverlay, two.top, true);
session.changeEffects([](LayerEffects &effects) { effects.colorOverlay->opacity = 0.25; });
session.changeEffects([](LayerEffects &effects) { effects.colorOverlay.value().opacity = 0.25; });
session.finishEffectsEditing(false);
QCOMPARE(two.effects(two.top).value().colorOverlay->opacity, 1.0);
QCOMPARE(two.effects(two.top).value().colorOverlay.value().opacity, 1.0);
// Its layer's effects all gone, Cancel has nothing to restore.
session.selectEffect(LayerEffectKind::shadow, two.top, true);
session.setEffects(LayerEffects(), two.top);
@@ -195,7 +195,7 @@ void LayerEffectsSessionTests::setEffectsIsOneStepOnAValidChange()
// The same again, an invalid one, a missing layer: nothing.
session.setEffects(effects, two.bottom);
LayerEffects wild = effects;
wild.stroke->size = 501;
wild.stroke.value().size = 501;
session.setEffects(wild, two.bottom);
session.setEffects(effects, QUuid::createUuid());
QCOMPARE(session.history.undoCount(), steps + 1);
@@ -220,18 +220,18 @@ void LayerEffectsSessionTests::panelEditsStayWithTheirLayer()
EditorSession &session = two.session;
session.addEffect(LayerEffectKind::stroke);
session.selectLayer(two.bottom);
session.changeEffects([](LayerEffects &effects) { effects.stroke->size = 12; });
QVERIFY(two.effects(two.top).value().stroke->size == 12 && !two.effects(two.bottom));
session.changeEffects([](LayerEffects &effects) { effects.stroke.value().size = 12; });
QVERIFY(two.effects(two.top).value().stroke.value().size == 12 && !two.effects(two.bottom));
// Once its effect is gone, edits have nowhere to go.
LayerEffects without = two.effects(two.top).value();
without.stroke.reset();
without.shadow = ShadowEffect();
session.setEffects(without, two.top);
const int steps = session.history.undoCount();
session.changeEffects([](LayerEffects &effects) { effects.shadow->blur = 1; });
session.changeEffects([](LayerEffects &effects) { effects.shadow.value().blur = 1; });
QCOMPARE(session.history.undoCount(), steps);
session.finishEffectsEditing(true);
session.changeEffects([](LayerEffects &effects) { effects.shadow->blur = 1; });
session.changeEffects([](LayerEffects &effects) { effects.shadow.value().blur = 1; });
QCOMPARE(session.history.undoCount(), steps);
}
@@ -251,7 +251,7 @@ void LayerEffectsSessionTests::copyingAnEffectClosesTheTargetsPanelFirst()
target.stroke = StrokeEffect();
session.setEffects(target, two.bottom);
session.selectEffect(LayerEffectKind::shadow, two.bottom, true);
session.changeEffects([](LayerEffects &effects) { effects.shadow->blur = 7; });
session.changeEffects([](LayerEffects &effects) { effects.shadow.value().blur = 7; });
session.copyEffect(LayerEffectKind::shadow, two.top, two.bottom);
QCOMPARE(session.history.undoName(), QString("Copy Drop Shadow"));
QVERIFY(!session.effectsEditing() && two.effects(two.bottom).value().shadow == source.shadow);
@@ -259,7 +259,7 @@ void LayerEffectsSessionTests::copyingAnEffectClosesTheTargetsPanelFirst()
QVERIFY(session.effectSelection() == (LayerEffectSelection{two.bottom, LayerEffectKind::shadow}) && session.activeLayerID() == two.bottom);
// The open edit was kept: one undo brings it back.
session.undo();
QVERIFY(two.effects(two.bottom).value().shadow->blur == 7);
QVERIFY(two.effects(two.bottom).value().shadow.value().blur == 7);
session.redo();
// No source, a blank or folder target, busy: no copy.
QVERIFY(!session.canCopyEffect(LayerEffectKind::shadow, QUuid::createUuid(), two.bottom));
@@ -278,9 +278,9 @@ void LayerEffectsSessionTests::togglingAndRemovingAnEffect()
session.addEffect(LayerEffectKind::stroke);
session.finishEffectsEditing(true);
session.toggleEffect(LayerEffectKind::stroke, two.top);
QVERIFY(two.effects(two.top).value().stroke->enabled == false && session.history.undoName() == QString("Hide Stroke"));
QVERIFY(two.effects(two.top).value().stroke.value().enabled == false && session.history.undoName() == QString("Hide Stroke"));
session.toggleEffect(LayerEffectKind::stroke, two.top);
QVERIFY(two.effects(two.top).value().stroke->enabled == true && session.history.undoName() == QString("Show Stroke"));
QVERIFY(two.effects(two.top).value().stroke.value().enabled == true && session.history.undoName() == QString("Show Stroke"));
// Toggling a missing effect, or none at all, does nothing.
const int steps = session.history.undoCount();
session.toggleEffect(LayerEffectKind::shadow, two.top);
@@ -381,7 +381,7 @@ void LayerEffectsSessionTests::thePickerEditsTheOpenEffect()
QVERIFY(session.colorPicker());
const int steps = session.history.undoCount();
session.previewEffectColor();
QVERIFY(session.history.undoCount() == steps && two.effects(two.top).value().stroke->red == 1);
QVERIFY(session.history.undoCount() == steps && two.effects(two.top).value().stroke.value().red == 1);
session.openEffectColorPicker(LayerEffectKind::shadow);
QVERIFY(session.colorPicker().value().target.kind == ColorPickerTarget::Kind::palette);
session.closeColorPicker(false);
@@ -408,7 +408,7 @@ void LayerEffectsSessionTests::thePickerEditsTheOpenEffect()
session.setColorPickerHSB(PickerHSB(PaletteColor{1, 1, 0}));
session.previewEffectColor();
session.addEffect(LayerEffectKind::stroke);
QVERIFY(!session.colorPicker() && two.effects(two.top).value().shadow->color() == PaletteColor::black());
QVERIFY(!session.colorPicker() && two.effects(two.top).value().shadow.value().color() == PaletteColor::black());
}
void LayerEffectsSessionTests::everyChangeIsAnnouncedLast()
@@ -424,7 +424,7 @@ void LayerEffectsSessionTests::everyChangeIsAnnouncedLast()
return seen == left ? QString() : seen.join("; ") + " != " + left.join("; ");
};
QCOMPARE(stale([&] { session.addEffect(LayerEffectKind::stroke); }), QString());
QCOMPARE(stale([&] { session.changeEffects([](LayerEffects &effects) { effects.stroke->size = 6; }); }), QString());
QCOMPARE(stale([&] { session.changeEffects([](LayerEffects &effects) { effects.stroke.value().size = 6; }); }), QString());
QCOMPARE(stale([&] { session.openEffectColorPicker(LayerEffectKind::stroke); }), QString());
QCOMPARE(stale([&] { session.setColorPickerHSB(PickerHSB(PaletteColor{1, 0, 0})); }), QString());
QCOMPARE(stale([&] { session.previewEffectColor(); }), QString());
+5 -5
View File
@@ -118,16 +118,16 @@ void LayerEffectsTests::validityFollowsSwiftsBounds()
QVERIFY(!InnerShadowEffect{.angle = -360.5}.isValid() && !InnerShadowEffect{.distance = -1}.isValid() && !InnerShadowEffect{.blur = notANumber}.isValid());
// One bad effect spoils the set.
LayerEffects effects = every();
effects.innerShadow->blur = infinity;
effects.innerShadow.value().blur = infinity;
QVERIFY(!effects.isValid());
effects = every();
effects.stroke->size = 501;
effects.stroke.value().size = 501;
QVERIFY(!effects.isValid());
effects = every();
effects.shadow->distance = -1;
effects.shadow.value().distance = -1;
QVERIFY(!effects.isValid());
effects = every();
effects.colorOverlay->opacity = 2;
effects.colorOverlay.value().opacity = 2;
QVERIFY(!effects.isValid());
}
@@ -163,7 +163,7 @@ void LayerEffectsTests::theHelpersReadAndWriteOneKind()
effects.setEnabled(true, LayerEffectKind::stroke);
effects.setEnabled(false, LayerEffectKind::innerShadow);
effects.setEnabled(true, LayerEffectKind::shadow);
QVERIFY(effects.stroke->enabled == true && effects.innerShadow->enabled == false && !effects.shadow);
QVERIFY(effects.stroke.value().enabled == true && effects.innerShadow.value().enabled == false && !effects.shadow);
// Visible keeps what is switched on.
const LayerEffects shown = effects.visible();
QVERIFY(shown.stroke == effects.stroke && !shown.innerShadow && !shown.shadow && !shown.colorOverlay);
+12 -12
View File
@@ -351,8 +351,8 @@ void LayerMaskTests::maskTransformPrefersThePlacement()
QCOMPARE(layer.maskTransform(), layer.transform);
layer.mask = wideMask();
QCOMPARE(layer.maskTransform(), layer.transform);
layer.mask->placement = LayerTransform{.origin = {5, 6}, .size = {7, 8}};
QCOMPARE(layer.maskTransform(), *layer.mask->placement);
layer.mask.value().placement = LayerTransform{.origin = {5, 6}, .size = {7, 8}};
QCOMPARE(layer.maskTransform(), layer.mask.value().placement.value());
}
void LayerMaskTests::layersCompareAndRecordTheirMasks()
@@ -370,7 +370,7 @@ void LayerMaskTests::layersCompareAndRecordTheirMasks()
QVERIFY(!(masked == layer));
QCOMPARE(masked, ImageLayer(masked));
ImageLayer disabled = masked;
disabled.mask->isEnabled = false;
disabled.mask.value().isEnabled = false;
QVERIFY(!(disabled == masked));
const ProjectLayerRecord hidden = disabled.hierarchyRecord();
QCOMPARE(hidden.maskEnabled, std::optional<bool>(false));
@@ -382,12 +382,12 @@ void LayerMaskTests::layersCompareAndRecordTheirMasks()
QCOMPARE(record.maskPlacement, std::nullopt);
QCOMPARE(record.maskLinked, std::optional<bool>(true));
ImageLayer unlinked = masked;
unlinked.mask->isLinked = false;
unlinked.mask->placement = LayerTransform{.origin = {5, 6}, .size = {7, 8}};
unlinked.mask.value().isLinked = false;
unlinked.mask.value().placement = LayerTransform{.origin = {5, 6}, .size = {7, 8}};
const ProjectLayerRecord moved = unlinked.hierarchyRecord();
QCOMPARE(moved.maskEnabled, std::optional<bool>(true));
QCOMPARE(moved.maskLinked, std::optional<bool>(false));
QCOMPARE(moved.maskPlacement, unlinked.mask->placement);
QCOMPARE(moved.maskPlacement, unlinked.mask.value().placement);
}
void LayerMaskTests::historyCountsMaskPixels()
@@ -395,18 +395,18 @@ void LayerMaskTests::historyCountsMaskPixels()
DocumentHistory history;
std::optional<CanvasDocument> document = CanvasDocument(40, 20);
const ImportedImage pixels(BrushRaster::context(40, 20, false), QImage(), "Photo");
document->layers.push_back(ImageLayer(pixels, QPointF(0, 0)));
document->layers[0].mask = LayerMask(LayerMask::assetFrom(gray(200, 100, 255)));
const qint64 maskBytes = document->layers[0].mask->asset.byteCount()
+ document->layers[0].mask->asset.thumbnail.sizeInBytes();
document.value().layers.push_back(ImageLayer(pixels, QPointF(0, 0)));
document.value().layers[0].mask = LayerMask(LayerMask::assetFrom(gray(200, 100, 255)));
const qint64 maskBytes = document.value().layers[0].mask.value().asset.byteCount()
+ document.value().layers[0].mask.value().asset.thumbnail.sizeInBytes();
QCOMPARE(maskBytes, qint64(200 * 100 + 96 * 48));
history.begin("Disable Layer Mask", document, std::nullopt);
document->layers[0].mask->isEnabled = false;
document.value().layers[0].mask.value().isEnabled = false;
history.end(document, std::nullopt);
QCOMPARE(history.undoCount(), 1);
QCOMPARE(history.retainedBytes(document), 0);
history.begin("Delete Layer Mask", document, std::nullopt);
document->layers[0].mask.reset();
document.value().layers[0].mask.reset();
history.end(document, std::nullopt);
QCOMPARE(history.retainedBytes(document), maskBytes);
}
+1 -1
View File
@@ -29,7 +29,7 @@ void LayerTests::importedLayerTakesItsSizeAndNameFromTheAsset()
QCOMPARE(layer.name, QString("Photo"));
QCOMPARE(layer.origin(), QPointF(40, -10));
QCOMPARE(layer.size(), QSizeF(320, 200));
QCOMPARE(layer.asset->image().size(), QSize(320, 200));
QCOMPARE(layer.asset.value().image().size(), QSize(320, 200));
QVERIFY(layer.isVisible);
QCOMPARE(layer.opacity, 1.0);
QCOMPARE(layer.blendMode, LayerBlendMode::normal);
+2 -2
View File
@@ -326,7 +326,7 @@ void MaskAloneTests::aPendingGradientElsewhereStaysOutOfTheView()
session.selectLayerTarget(shown.id, false);
session.beginGradient(QPointF(0, 80));
session.moveGradient(std::nullopt, QPointF(200, 80));
QVERIFY(session.gradientEdit().has_value() && !session.gradientEdit()->raster->isMask);
QVERIFY(session.gradientEdit().has_value() && !session.gradientEdit().value().raster->isMask);
session.toggleMaskAlone(shown.id);
// The commit is posted: the pixels' raster lingers, unshown.
QVERIFY(session.maskAloneLayer().has_value() && session.gradientEdit().has_value());
@@ -338,7 +338,7 @@ void MaskAloneTests::aPendingGradientElsewhereStaysOutOfTheView()
session.addLayerMask();
session.beginGradient(QPointF(0, 80));
session.moveGradient(std::nullopt, QPointF(200, 80));
QVERIFY(session.gradientEdit().has_value() && session.gradientEdit()->raster->isMask && session.gradientEdit()->raster->layer.id == other);
QVERIFY(session.gradientEdit().has_value() && session.gradientEdit().value().raster->isMask && session.gradientEdit().value().raster->layer.id == other);
session.toggleMaskAlone(shown.id);
QCOMPARE(session.maskAloneLayer().value().id, shown.id);
QVERIFY(session.gradientEdit().has_value());
+5
View File
@@ -284,6 +284,11 @@ void NativeResolutionPaintTests::layersPanelSaysHowFarALayerIsScaled()
QCOMPARE(sizeLabel(layer), QString("500 × 300 px · 50%"));
layer.transform.size = QSizeF(2333, 2000);
QCOMPARE(sizeLabel(layer), QString("2333 × 2000 px · 233.3%"));
// Exact ties go to the even tenth.
layer.transform.size = QSizeF(1062.5, 300);
QCOMPARE(sizeLabel(layer), QString("1063 × 300 px · 106.2%"));
layer.transform.size = QSizeF(1187.5, 300);
QCOMPARE(sizeLabel(layer), QString("1188 × 300 px · 118.8%"));
// A blank layer has no pixels to measure against.
QCOMPARE(sizeLabel(ImageLayer(QStringLiteral("Blank"), QSizeF(30, 20))), QString("30 × 20 px"));
// The row shows it.
+5 -5
View File
@@ -80,7 +80,7 @@ void OuterGlowTests::outerGlowCodableRoundTrip()
QCOMPARE(ManifestJson::effects(encoded), original);
// Absent `enabled` stays absent, read as shown.
LayerEffects shown = original;
shown.outerGlow->enabled = std::nullopt;
shown.outerGlow.value().enabled = std::nullopt;
QVERIFY(!ManifestJson::encoded(shown).value("outerGlow").toObject().contains("enabled"));
QCOMPARE(ManifestJson::effects(ManifestJson::encoded(shown)), shown);
// A key of the wrong type, or missing, refuses it.
@@ -104,7 +104,7 @@ void OuterGlowTests::layerEffectsIntegration()
QVERIFY(effects.color(LayerEffectKind::outerGlow) == (PaletteColor{1, 0, 0}));
QVERIFY((effects.kinds() == std::vector{LayerEffectKind::outerGlow}));
effects.setColor(PaletteColor{0, 1, 0}, LayerEffectKind::outerGlow);
QVERIFY(effects.outerGlow->green == 1 && effects.outerGlow->red == 0);
QVERIFY(effects.outerGlow.value().green == 1 && effects.outerGlow.value().red == 0);
effects.setEnabled(false, LayerEffectKind::outerGlow);
QVERIFY(!effects.isEnabled(LayerEffectKind::outerGlow) && !effects.visible().outerGlow);
effects.remove(LayerEffectKind::outerGlow);
@@ -202,7 +202,7 @@ void OuterGlowTests::theGlowWidensTheMargin()
{
LayerEffects effects = glowing(7.2, 1, 1, 1, 1);
QCOMPARE(LayerEffectsRenderer::margin(effects), 24.0);
effects.outerGlow->enabled = false;
effects.outerGlow.value().enabled = false;
QCOMPARE(LayerEffectsRenderer::margin(effects), 2.0);
}
@@ -215,11 +215,11 @@ void OuterGlowTests::theSessionAddsCopiesAndCancelsIt()
session.addEffect(LayerEffectKind::outerGlow);
QCOMPARE(session.history.undoName(), QString("Add Outer Glow"));
QVERIFY(session.activeEffects().outerGlow == OuterGlowEffect());
session.changeEffects([](LayerEffects &effects) { effects.outerGlow->size = 44; });
session.changeEffects([](LayerEffects &effects) { effects.outerGlow.value().size = 44; });
session.finishEffectsEditing(false);
QVERIFY(!session.activeEffects().outerGlow);
session.addEffect(LayerEffectKind::outerGlow);
session.changeEffects([](LayerEffects &effects) { effects.outerGlow->size = 44; });
session.changeEffects([](LayerEffects &effects) { effects.outerGlow.value().size = 44; });
session.finishEffectsEditing(true);
session.insert(ImportedImage(square(), QImage(), QStringLiteral("Two")));
const QUuid second = session.activeLayerID().value();
+3 -3
View File
@@ -149,7 +149,7 @@ inline QByteArray engine(const TypeBlock &block)
return "<<\n/EngineDict\n<<\n/Editor\n<<\n/Text " + parenthesized(block.engineText.value_or(block.text)) + "\n>>\n/ParagraphRun\n<<\n/RunArray\n[\n<<\n"
+ "/ParagraphSheet\n<<\n/Properties\n<<\n/Justification " + block.justification + "\n>>\n>>\n>>\n]\n>>\n"
+ "/StyleRun\n<<\n/RunArray\n[\n" + runs + "\n]\n>>\n>>\n/ResourceDict\n<<\n/FontSet\n[\n<<\n/Name " + parenthesized(block.font)
+ "\n>>\n" + (block.secondFont ? "<<\n/Name " + parenthesized(*block.secondFont) + "\n>>\n" : QByteArray()) + "]\n>>\n>>";
+ "\n>>\n" + (block.secondFont ? "<<\n/Name " + parenthesized(block.secondFont.value()) + "\n>>\n" : QByteArray()) + "]\n>>\n>>";
}
inline QByteArray tySh(const TypeBlock &block)
@@ -164,9 +164,9 @@ inline QByteArray tySh(const TypeBlock &block)
items.emplace_back(block.wordsKey, textItem(block.text));
items.emplace_back("Ornt", enumItem("Ornt", block.vertical ? "Vrtc" : "Hrzn"));
if (block.bounds)
items.emplace_back("bounds", rectItem(*block.bounds));
items.emplace_back("bounds", rectItem(block.bounds.value()));
if (block.glyphBounds)
items.emplace_back("boundingBox", rectItem(*block.glyphBounds));
items.emplace_back("boundingBox", rectItem(block.glyphBounds.value()));
if (block.withEngine)
items.emplace_back("EngineData", rawItem(engine(block)));
descriptor(data, "TxLr", items);
+8 -8
View File
@@ -128,27 +128,27 @@ inline std::vector<Channel> emptyChannels()
inline Prepared layer(const PSDRecord &record, bool large = false)
{
const int width = record.image ? record.image->width() : 0, height = record.image ? record.image->height() : 0;
const int width = record.image ? record.image.value().width() : 0, height = record.image ? record.image.value().height() : 0;
const int left = int(std::round(record.bounds.left())), top = int(std::round(record.bounds.top()));
Prepared prepared;
prepared.record = record;
if (record.image && width > 0 && height > 0) {
const Planes split = planes(*record.image);
const Planes split = planes(record.image.value());
for (const auto &[id, plane] : {std::pair<qint16, const std::vector<uchar> &>(-1, split.alpha), {0, split.red}, {1, split.green}, {2, split.blue}})
prepared.channels.push_back({id, channelPayload(plane, width, height, large)});
} else {
prepared.channels = emptyChannels();
}
if (record.mask)
prepared.channels.push_back({-2, channelPayload(grayPlane(*record.mask), record.mask->width(), record.mask->height(), large)});
prepared.channels.push_back({-2, channelPayload(grayPlane(record.mask.value()), record.mask.value().width(), record.mask.value().height(), large)});
prepared.top = top;
prepared.left = left;
prepared.bottom = top + height;
prepared.right = left + width;
prepared.maskTop = top;
prepared.maskLeft = left;
prepared.maskBottom = top + (record.mask ? record.mask->height() : 0);
prepared.maskRight = left + (record.mask ? record.mask->width() : 0);
prepared.maskBottom = top + (record.mask ? record.mask.value().height() : 0);
prepared.maskRight = left + (record.mask ? record.mask.value().width() : 0);
return prepared;
}
@@ -170,9 +170,9 @@ inline Prepared emptyLayer(const QString &name, const QString &blendKey, int sec
prepared.isDivider = section == 3;
prepared.channels = emptyChannels();
if (record.mask) {
prepared.channels.push_back({-2, channelPayload(grayPlane(*record.mask), record.mask->width(), record.mask->height(), large)});
prepared.maskBottom = record.mask->height();
prepared.maskRight = record.mask->width();
prepared.channels.push_back({-2, channelPayload(grayPlane(record.mask.value()), record.mask.value().width(), record.mask.value().height(), large)});
prepared.maskBottom = record.mask.value().height();
prepared.maskRight = record.mask.value().width();
}
return prepared;
}
+4 -4
View File
@@ -78,10 +78,10 @@ inline QByteArray file(const std::vector<Record> &records, const QByteArray &res
PSDFixture::Buffer extra;
if (record.mask) {
extra.u32(20);
extra.i32(record.mask->top());
extra.i32(record.mask->left());
extra.i32(record.mask->top() + record.mask->height());
extra.i32(record.mask->left() + record.mask->width());
extra.i32(record.mask.value().top());
extra.i32(record.mask.value().left());
extra.i32(record.mask.value().top() + record.mask.value().height());
extra.i32(record.mask.value().left() + record.mask.value().width());
extra.u8(record.maskDefault);
extra.u8(record.maskFlags);
extra.u16(0);
+5 -5
View File
@@ -437,15 +437,15 @@ void RasterSnapshotTests::historyBudgetNeverFlattensPaintedTiles()
const auto raster = std::make_shared<const RasterSnapshot>(40, 30, QImage(), QRectF(), std::vector<BrushPatch>());
DocumentHistory history;
std::optional<CanvasDocument> document = CanvasDocument(40, 30);
document->layers.push_back(ImageLayer(painted(raster), QPointF(0, 0)));
QVERIFY(document->layers[0] == ImageLayer(document->layers[0]));
document.value().layers.push_back(ImageLayer(painted(raster), QPointF(0, 0)));
QVERIFY(document.value().layers[0] == ImageLayer(document.value().layers[0]));
history.begin("Select Layer", document, std::nullopt);
history.end(document, document->layers[0].id);
history.end(document, document.value().layers[0].id);
QCOMPARE(history.undoCount(), 0);
QVERIFY(!raster->hasMaterializedPixels());
history.begin("Delete Layer", document, std::nullopt);
const qint64 thumbnailBytes = document->layers[0].asset->thumbnail.sizeInBytes();
document->layers.clear();
const qint64 thumbnailBytes = document.value().layers[0].asset.value().thumbnail.sizeInBytes();
document.value().layers.clear();
history.end(document, std::nullopt);
QCOMPARE(history.retainedBytes(document), qint64(40 * 30 * 4) + thumbnailBytes);
QVERIFY(!raster->hasMaterializedPixels());
+7 -7
View File
@@ -72,10 +72,10 @@ void RawDevelopTests::theSheetsSettingsDevelopTheLayer()
bool done = false;
session.importImages({raw, png(folder)}, std::nullopt, [&done] { done = true; });
QTRY_VERIFY(session.rawDevelop());
QCOMPARE(session.rawDevelop()->path, raw.toLocalFile());
QVERIFY(session.rawDevelop()->settings == RawImporter::asShot(raw.toLocalFile()).value());
QCOMPARE(session.rawDevelop().value().path, raw.toLocalFile());
QVERIFY(session.rawDevelop().value().settings == RawImporter::asShot(raw.toLocalFile()).value());
QVERIFY(session.isImporting() && !session.document());
RawDevelopSettings settings = session.rawDevelop()->settings;
RawDevelopSettings settings = session.rawDevelop().value().settings;
settings.boost = 0;
settings.exposure = 1;
// The sheet hears at once that it has gone.
@@ -139,7 +139,7 @@ void RawDevelopTests::whatCannotBeDevelopedIsReported()
QFile broken(gray.toLocalFile());
QVERIFY(broken.open(QIODevice::WriteOnly) && broken.write("gone") == 4);
broken.close();
session.finishRawDevelop(session.rawDevelop()->settings);
session.finishRawDevelop(session.rawDevelop().value().settings);
QTRY_VERIFY(done);
QCOMPARE(session.importError().value(), "Gray.dng: " + unreadable);
QCOMPARE(names(session), QStringList{"Plain"});
@@ -149,7 +149,7 @@ void RawDevelopTests::whatCannotBeDevelopedIsReported()
const QUrl warm = dng(folder, "Warm.dng");
session.importImages({warm}, std::nullopt, [&done] { done = true; });
QTRY_VERIFY(session.rawDevelop());
RawDevelopSettings candle = session.rawDevelop()->settings;
RawDevelopSettings candle = session.rawDevelop().value().settings;
candle.temperature = 2000;
session.finishRawDevelop(candle);
QTRY_VERIFY(done);
@@ -240,7 +240,7 @@ void RawDevelopTests::theSheetPreviewsAndAnswers()
QTest::qWait(50);
QCOMPARE(preview->grab().toImage().pixelColor(200, 170), QColor(139, 139, 139));
QVERIFY(!spinner->isHidden());
RawDevelopSettings dimmer = session.rawDevelop()->settings;
RawDevelopSettings dimmer = session.rawDevelop().value().settings;
dimmer.exposure = -1;
wait->start();
QTRY_COMPARE_WITH_TIMEOUT(preview->grab().toImage().pixelColor(200, 170), centre(RawImporter::develop(path, dimmer)), 1000);
@@ -249,7 +249,7 @@ void RawDevelopTests::theSheetPreviewsAndAnswers()
exposure->setValue(100);
QCOMPARE(label(sheet, "rawExposureValue"), QString("1.00 EV"));
QVERIFY(reset->isEnabled());
RawDevelopSettings brighter = session.rawDevelop()->settings;
RawDevelopSettings brighter = session.rawDevelop().value().settings;
brighter.exposure = 1;
const QColor expected = centre(RawImporter::develop(path, brighter));
QTRY_COMPARE(preview->grab().toImage().pixelColor(200, 170), expected);
+2 -2
View File
@@ -75,12 +75,12 @@ void RemoveBackgroundTests::thePreviewClearsTheBackgroundAndTheCommitMasksIt()
const FilterEdit &edit = session->filterEdit().value();
QVERIFY(edit.previewSource.size() == QSize(400, 300) && !edit.previewError);
const QImage preview = edit.previewImage(id).value();
QVERIFY(preview.pixelColor(0, 0).alpha() == 0 && preview.pixelColor(200, 150) == before.asset->image().pixelColor(200, 150));
QVERIFY(preview.pixelColor(0, 0).alpha() == 0 && preview.pixelColor(200, 150) == before.asset.value().image().pixelColor(200, 150));
const int steps = session->history.undoCount();
QVERIFY(committed(*session));
// The pixels stay; a mask hides the background instead.
const ImageLayer after = layerWith(*session, id);
QVERIFY(after.asset->identity() == before.asset->identity() && after.transform == before.transform);
QVERIFY(after.asset.value().identity() == before.asset.value().identity() && after.transform == before.transform);
const LayerMask &mask = after.mask.value();
QVERIFY(mask.isEnabled && !mask.placement && mask.isLinked);
QVERIFY(mask.asset.image().format() == QImage::Format_Grayscale8 && mask.asset.size() == QSize(400, 300));
+1 -1
View File
@@ -176,7 +176,7 @@ void SelectionFeatherTests::aMaskFromAFeatheredSelectionFades()
EditorSession session;
session.createDocument(60, 20, true);
session.setSelection(DocumentSelection{rectPath(QRectF(20, 0, 20, 20)), true, 6}, "probe");
session.addMask(false);
session.addMask(true);
const QImage mask = session.activeLayer().value().mask.value().asset.image();
QCOMPARE(mask, session.document().value().layers.back().mask.value().asset.image());
QVERIFY2(fading(mask, 10, 0, 1) >= 4, "the mask has a hard edge");
+4
View File
@@ -53,7 +53,11 @@ void SelectionSignalTests::selectionCallsAnnounceAndRefuseInSilence()
QVERIFY(session->selection().has_value());
QVERIFY(announced([&] { session->selectAll(); }));
QVERIFY(!announced([&] { session->selectAll(); }));
// Inverse of everything leaves nothing to deselect.
QVERIFY(announced([&] { session->invertSelection(); }));
QVERIFY(!session->selection().has_value());
QVERIFY(!announced([&] { session->deselect(); }));
QVERIFY(announced([&] { session->selectAll(); }));
QVERIFY(announced([&] { session->deselect(); }));
QVERIFY(announced([&] { session->toggleMarqueeKind(); }));
QVERIFY(announced([&] { session->toggleLassoKind(); }));
+4 -4
View File
@@ -27,10 +27,10 @@ inline QStringList described(const EditorSession &session)
"tool error " + session.brushError().value_or(QStringLiteral("none")),
"renaming " + text(session.renamingLayerID()),
"adjusting " + text(session.adjustmentEditingID()) + (session.adjustmentOriginal().has_value() ? " open" : ""),
"effects " + (session.effectsEditing() ? text(session.effectsEditing()->layerID) + " " + rawValue(session.effectsEditing()->kind) : QStringLiteral("closed"))
+ (session.effectsEditingOriginal() ? " " + QJsonDocument(ManifestJson::encoded(*session.effectsEditingOriginal())).toJson(QJsonDocument::Compact) : QString())
+ " chosen " + (session.effectSelection() ? text(session.effectSelection()->layerID) + " " + rawValue(session.effectSelection()->kind) : QStringLiteral("none")),
"picker " + (session.colorPicker() ? session.colorPicker()->target.title() + " " + session.colorPicker()->color().hex() : QStringLiteral("closed")),
"effects " + (session.effectsEditing() ? text(session.effectsEditing().value().layerID) + " " + rawValue(session.effectsEditing().value().kind) : QStringLiteral("closed"))
+ (session.effectsEditingOriginal() ? " " + QJsonDocument(ManifestJson::encoded(session.effectsEditingOriginal().value())).toJson(QJsonDocument::Compact) : QString())
+ " chosen " + (session.effectSelection() ? text(session.effectSelection().value().layerID) + " " + rawValue(session.effectSelection().value().kind) : QStringLiteral("none")),
"picker " + (session.colorPicker() ? session.colorPicker().value().target.title() + " " + session.colorPicker().value().color().hex() : QStringLiteral("closed")),
QStringLiteral("mask target %1").arg(int(session.isMaskSelected())),
QStringLiteral("history %1%2").arg(int(session.canUndo())).arg(int(session.canRedo())),
QStringLiteral("names %1/%2").arg(session.history.undoName(), session.history.redoName()),
+1 -1
View File
@@ -320,7 +320,7 @@ void ShapeLayerTests::aProjectKeepsLiveShapesOnly()
// Swift's keys; a rectangle writes no line fields.
QCOMPARE(firstShape(snapshot, 1).keys(), (QStringList{"blue", "cornerRadius", "green", "kind", "red"}));
QCOMPARE(firstShape(snapshot, 1).value("kind").toString(), QString("Rectangle"));
QCOMPARE(firstShape(snapshot, 2).value("start").toArray(), (QJsonArray{line.start->x(), line.start->y()}));
QCOMPARE(firstShape(snapshot, 2).value("start").toArray(), (QJsonArray{line.start.value().x(), line.start.value().y()}));
QCOMPARE(firstShape(snapshot, 2).value("lineWidth").toDouble(), 3.0);
const ProjectManifest decoded = ProjectManifest::decoded(snapshot.manifest.encoded());
QCOMPARE(decoded.layers[1].shape, snapshot.manifest.layers[1].shape);
+1 -1
View File
@@ -126,7 +126,7 @@ struct Oracle {
pickUp(point);
return;
}
const QPointF from = *last;
const QPointF from = last.value();
const double distance = std::hypot(point.x() - from.x(), point.y() - from.y());
const double spacing = std::max(1.0, diameter * (smudging ? 0.005 : 0.025));
if (distance < spacing)
+1 -1
View File
@@ -125,7 +125,7 @@ void TextColorModelTests::everyChannelOfARunIsChecked()
session->setPaletteColor(red, false);
QVERIFY(session->finishText());
ProjectSnapshot snapshot = session->projectSnapshot().value();
snapshot.manifest.layers.back().text->colorRuns->front().blue = 2;
snapshot.manifest.layers.back().text.value().colorRuns.value().front().blue = 2;
QTemporaryDir root;
try {
ProjectStore::save(snapshot, root.filePath(QStringLiteral("Bad.comp")));
+1 -1
View File
@@ -220,7 +220,7 @@ void WritingProjectsGuideTests::hsvSettingsWinOverTheLegacyFields()
QJsonObject adjustment = layer.value(QStringLiteral("adjustment")).toObject();
adjustment.insert(QStringLiteral("hue"), legacyHue);
if (masterHue) {
const QJsonObject values{{"hue", *masterHue}, {"saturation", 0}, {"lightness", 0}};
const QJsonObject values{{"hue", masterHue.value()}, {"saturation", 0}, {"lightness", 0}};
adjustment.insert(QStringLiteral("hsvSettings"), QJsonObject{{"range", "Master"}, {"colorize", false}, {"invertRange", false},
{"adjustments", QJsonArray{"Master", values}}, {"bands", QJsonArray{}}});
}