Crop ratios and selection start, Vignette on empty layers, text colour previews (12.10c); mutation runs use 12 cores
This commit is contained in:
1 parent
7a873eb9c7
commit
ffba1753c6
27 files changed
+555
-55
No files matched your search
@@ -35,7 +35,7 @@ void CropControlsTests::theBarShowsTheFrameAndItsRatio()
|
||||
QStringList choices;
|
||||
for (int index = 0; index < ratio.count(); ++index)
|
||||
choices << ratio.itemText(index);
|
||||
QCOMPARE(choices, (QStringList{"Free", "Original", "1:1", "4:3", "16:9"}));
|
||||
QCOMPARE(choices, (QStringList{"Free", "Original", "1:1", "4:3", "3:4", "16:9", "9:16"}));
|
||||
QCOMPARE(ratio.currentText(), QString("Free"));
|
||||
// Labelled as Swift's picker, the two 170 points wide.
|
||||
const QList<QLabel *> labels = bar.findChildren<QLabel *>();
|
||||
|
||||
+66
-1
@@ -1,5 +1,6 @@
|
||||
#include "CanvasFixtures.h"
|
||||
#include "CropFixtures.h"
|
||||
#include "SelectionFixtures.h"
|
||||
#include "Document/Crop.h"
|
||||
#include "IO/ImageExporter.h"
|
||||
#include <QPainter>
|
||||
@@ -11,6 +12,7 @@
|
||||
class CropTests : public QObject {
|
||||
Q_OBJECT
|
||||
private slots:
|
||||
void theFrameStartsAtTheSelection();
|
||||
void dragGeometrySupportsReverseRatioMoveAndEveryHandle();
|
||||
void cropTranslatesWithoutResamplingAndUndoRestoresBounds();
|
||||
void sameSizeOffsetCropAndExpansionUseExactBounds();
|
||||
@@ -279,7 +281,7 @@ void CropTests::ratiosReshapeTheFrameAboutItsMiddle()
|
||||
session.selectTool(NavigationTool::crop);
|
||||
session.setCropRect(QRectF(10, 20, 120, 60));
|
||||
const std::pair<const char *, std::optional<double>> ratios[] = {
|
||||
{"Original", 1.5}, {"1:1", 1.0}, {"4:3", 4.0 / 3}, {"16:9", 16.0 / 9}, {"Free", std::nullopt}};
|
||||
{"Original", 1.5}, {"1:1", 1.0}, {"4:3", 4.0 / 3}, {"3:4", 0.75}, {"16:9", 16.0 / 9}, {"9:16", 9.0 / 16}, {"Free", std::nullopt}};
|
||||
for (const auto &[choice, ratio] : ratios) {
|
||||
session.setCropRatioChoice(QString::fromLatin1(choice));
|
||||
QCOMPARE(session.cropRatio(), ratio);
|
||||
@@ -316,5 +318,68 @@ void CropTests::ratiosReshapeTheFrameAboutItsMiddle()
|
||||
QCOMPARE(empty.visibleCropRect(), std::nullopt);
|
||||
}
|
||||
|
||||
void CropTests::theFrameStartsAtTheSelection()
|
||||
{
|
||||
// Swift 1.2.5 (5ece82b): C, then Return, crops to the selection.
|
||||
EditorSession session;
|
||||
session.createDocument(300, 200);
|
||||
session.applySelection(rectPath(QRectF(10.3, 20.6, 50.2, 30)), SelectionMode::replace, QStringLiteral("Select"));
|
||||
session.selectTool(NavigationTool::crop);
|
||||
// Rounded out to whole pixels.
|
||||
QCOMPARE(session.cropRect(), std::optional(QRectF(10, 20, 51, 31)));
|
||||
QCOMPARE(session.cropRatioChoice(), QString("Free"));
|
||||
// A frame already drawn stays as it is.
|
||||
session.setCropRect(QRectF(0, 0, 40, 40));
|
||||
session.selectTool(NavigationTool::crop);
|
||||
QCOMPARE(session.cropRect(), std::optional(QRectF(0, 0, 40, 40)));
|
||||
session.cancelCrop();
|
||||
// Past the canvas, the frame keeps to it.
|
||||
session.selectTool(NavigationTool::move);
|
||||
session.applySelection(rectPath(QRectF(280, 190, 50, 50)), SelectionMode::replace, QStringLiteral("Select"));
|
||||
session.selectTool(NavigationTool::crop);
|
||||
QCOMPARE(session.cropRect(), std::optional(QRectF(280, 190, 20, 10)));
|
||||
session.cancelCrop();
|
||||
// Nudged partly off the canvas, the frame keeps to it.
|
||||
session.selectTool(NavigationTool::move);
|
||||
session.applySelection(rectPath(QRectF(270, 20, 20, 20)), SelectionMode::replace, QStringLiteral("Select"));
|
||||
session.nudgeSelection(20, 0);
|
||||
QCOMPARE(session.selection().value().path.controlPointRect(), QRectF(290, 20, 20, 20));
|
||||
session.selectTool(NavigationTool::crop);
|
||||
QCOMPARE(session.cropRect(), std::optional(QRectF(290, 20, 10, 20)));
|
||||
session.cancelCrop();
|
||||
// Wholly off it, the frame is the canvas.
|
||||
session.selectTool(NavigationTool::move);
|
||||
session.nudgeSelection(20, 0);
|
||||
session.selectTool(NavigationTool::crop);
|
||||
QCOMPARE(session.cropRect(), std::optional(QRectF(0, 0, 300, 200)));
|
||||
session.cancelCrop();
|
||||
// An empty selection starts at the whole canvas.
|
||||
session.selectTool(NavigationTool::move);
|
||||
session.applySelection(rectPath(QRectF(10, 10, 10, 10)), SelectionMode::replace, QStringLiteral("Select"));
|
||||
session.applySelection(rectPath(QRectF(0, 0, 100, 40)), SelectionMode::subtract, QStringLiteral("Subtract"));
|
||||
QVERIFY(session.selection().value().isEmpty());
|
||||
session.selectTool(NavigationTool::crop);
|
||||
QCOMPARE(session.cropRect(), std::optional(QRectF(0, 0, 300, 200)));
|
||||
session.cancelCrop();
|
||||
// An empty line between rows would round to a row.
|
||||
session.selectTool(NavigationTool::move);
|
||||
QPainterPath line(QPointF(10, 5.5));
|
||||
line.lineTo(20, 5.5);
|
||||
session.setSelection(DocumentSelection{line}, QStringLiteral("Select"));
|
||||
QVERIFY(session.selection().value().isEmpty());
|
||||
session.selectTool(NavigationTool::crop);
|
||||
QCOMPARE(session.cropRect(), std::optional(QRectF(0, 0, 300, 200)));
|
||||
session.cancelCrop();
|
||||
// A turned ellipse: its curve, not its control points.
|
||||
session.selectTool(NavigationTool::move);
|
||||
QPainterPath ellipse;
|
||||
ellipse.addEllipse(QRectF(60, 60, 120, 40));
|
||||
const QTransform turn = QTransform::fromTranslate(120, 80).rotate(45).translate(-120, -80);
|
||||
session.setSelection(DocumentSelection{turn.map(ellipse)}, QStringLiteral("Select"));
|
||||
QVERIFY(session.selection().value().path.controlPointRect().width() > 95);
|
||||
session.selectTool(NavigationTool::crop);
|
||||
QCOMPARE(session.cropRect(), std::optional(QRectF(75, 35, 90, 90)));
|
||||
}
|
||||
|
||||
QTEST_GUILESS_MAIN(CropTests)
|
||||
#include "CropTests.moc"
|
||||
@@ -158,7 +158,8 @@ void FinishingFilterTests::theKernelsTakeTheSettingsAtTheScale()
|
||||
FilterSettings vignette{.vignetteAmount = 70, .vignetteColor = AdjustmentColor(0.2, 0.9, 0.4), .vignetteMidpoint = 30,
|
||||
.vignetteRoundness = -40, .vignetteFeather = 20, .vignetteHighlights = 60};
|
||||
QImage expected = source.copy();
|
||||
adjust_colored_vignette(expected.bits(), 40, 30, size_t(expected.bytesPerLine()), 70, 30, -40, 20, 60, 0.2, 0.9, 0.4);
|
||||
// The layer's own pixels frame it; clear ones stay clear.
|
||||
adjust_colored_vignette(expected.bits(), 40, 30, size_t(expected.bytesPerLine()), 0, 0, 40, 30, 0, 70, 30, -40, 20, 60, 0.2, 0.9, 0.4);
|
||||
QCOMPARE(run(FilterKind::vignette, source, vignette, 3), expected);
|
||||
// Tonal Contrast: its base blurred at radius times scale.
|
||||
const FilterSettings tonal{.tonalAmount = 80, .tonalRadius = 3, .tonalShadows = -30, .tonalMidtones = 70, .tonalHighlights = 45};
|
||||
@@ -181,7 +182,7 @@ void FinishingFilterTests::settingsKeepSwiftsDefaultsAndBounds()
|
||||
{
|
||||
const FilterSettings defaults;
|
||||
QVERIFY(defaults.vignetteAmount == 35 && defaults.vignetteColor == AdjustmentColor(0, 0, 0) && defaults.vignetteMidpoint == 50);
|
||||
QVERIFY(defaults.vignetteRoundness == 0 && defaults.vignetteFeather == 60 && defaults.vignetteHighlights == 25);
|
||||
QVERIFY(defaults.vignetteRoundness == 100 && defaults.vignetteFeather == 60 && defaults.vignetteHighlights == 25);
|
||||
QVERIFY(defaults.bloomAmount == 40 && defaults.bloomRadius == 24);
|
||||
QVERIFY(defaults.tonalAmount == 50 && defaults.tonalRadius == 16 && defaults.tonalShadows == 40 && defaults.tonalMidtones == 60);
|
||||
QCOMPARE(defaults.tonalHighlights, 30.0);
|
||||
|
||||
@@ -257,6 +257,8 @@ compositor_test(FilterControlsTests)
|
||||
compositor_test(FinishingFilterTests)
|
||||
# It checks the filters against their kernels.
|
||||
target_include_directories(FinishingFilterTests PRIVATE ${OMAPHOTO_KERNELS})
|
||||
compositor_test(VignetteCanvasTests)
|
||||
target_include_directories(VignetteCanvasTests PRIVATE ${OMAPHOTO_KERNELS})
|
||||
compositor_test(FloatingSelectionTests)
|
||||
compositor_test(CropTests)
|
||||
compositor_test(GuideTests)
|
||||
|
||||
@@ -43,6 +43,7 @@ private slots:
|
||||
void theButtonsFollowTheDraft();
|
||||
void theBarRestsWithoutADocumentOrWhileBusy();
|
||||
void aTextRowShowsAndOpensItsText();
|
||||
void thePickersColourPreviewsOnOpenText();
|
||||
};
|
||||
|
||||
void TypeControlsTests::theBarShowsTheStyleItEdits()
|
||||
@@ -455,5 +456,19 @@ void TypeControlsTests::aTextRowShowsAndOpensItsText()
|
||||
QVERIFY(!session.renamingLayerID());
|
||||
}
|
||||
|
||||
void TypeControlsTests::thePickersColourPreviewsOnOpenText()
|
||||
{
|
||||
// Swift's onChange: each working colour reaches the draft.
|
||||
Bar shown;
|
||||
shown.session.beginText(QPointF(20, 30));
|
||||
shown.session.openTextColorPicker();
|
||||
PickerHSB hsb = shown.session.colorPicker().value().hsb;
|
||||
hsb.setRGB(PaletteColor{1, 0, 0});
|
||||
shown.session.setColorPickerHSB(hsb);
|
||||
QCOMPARE(shown.session.textDraft().value().style.red, 1.0);
|
||||
shown.session.closeColorPicker(false);
|
||||
QCOMPARE(shown.session.textDraft().value().style.red, 0.0);
|
||||
}
|
||||
|
||||
QTEST_MAIN(TypeControlsTests)
|
||||
#include "TypeControlsTests.moc"
|
||||
@@ -14,6 +14,14 @@ std::unique_ptr<EditorSession> makeSession()
|
||||
return session;
|
||||
}
|
||||
|
||||
// Swift writes `colorPicker.hsb` in place.
|
||||
void pick(EditorSession &session, const PaletteColor &color)
|
||||
{
|
||||
PickerHSB hsb = session.colorPicker().value().hsb;
|
||||
hsb.setRGB(color);
|
||||
session.setColorPickerHSB(hsb);
|
||||
}
|
||||
|
||||
// Swift writes `session.textDraft?.style.content` in place.
|
||||
void type(EditorSession &session, const QString &content)
|
||||
{
|
||||
@@ -50,6 +58,8 @@ private slots:
|
||||
void paragraphBoxAndToolSwitchCommitEditableText();
|
||||
void emptyNewParagraphIsDiscarded();
|
||||
void invalidAndStaleDraftsDoNotChangeDocument();
|
||||
void textColorPickerPreviewsAndRestoresDraft();
|
||||
void theForegroundPickerPreviewsOpenTextToo();
|
||||
};
|
||||
|
||||
void TypeToolTests::createEditCancelAndUndo()
|
||||
@@ -217,5 +227,95 @@ void TypeToolTests::invalidAndStaleDraftsDoNotChangeDocument()
|
||||
QCOMPARE(session->document().value().layers.size(), size_t(1));
|
||||
}
|
||||
|
||||
void TypeToolTests::textColorPickerPreviewsAndRestoresDraft()
|
||||
{
|
||||
const auto session = makeSession();
|
||||
session->beginText(QPointF(30, 40));
|
||||
const LayerTextStyle original = session->textDraft().value().style;
|
||||
session->openTextColorPicker();
|
||||
pick(*session, PaletteColor{1, 0, 0});
|
||||
session->previewTextColor();
|
||||
QCOMPARE(session->textDraft().value().style.red, 1.0);
|
||||
QCOMPARE(session->textDraft().value().style.green, 0.0);
|
||||
QCOMPARE(session->foregroundColor(), PaletteColor::black());
|
||||
session->closeColorPicker(false);
|
||||
QCOMPARE(session->textDraft().value().style, original);
|
||||
QCOMPARE(session->foregroundColor(), PaletteColor::black());
|
||||
session->openTextColorPicker();
|
||||
pick(*session, PaletteColor{0, 0, 1});
|
||||
session->previewTextColor();
|
||||
session->closeColorPicker(true);
|
||||
QCOMPARE(session->textDraft().value().style.blue, 1.0);
|
||||
QCOMPARE(session->foregroundColor(), (PaletteColor{0, 0, 1}));
|
||||
// With no text open, Cancel leaves the next text's colour.
|
||||
session->setTextDraft(std::nullopt);
|
||||
session->openTextColorPicker();
|
||||
pick(*session, PaletteColor{1, 0, 0});
|
||||
session->closeColorPicker(true);
|
||||
session->setForegroundColor(PaletteColor{0, 1, 0});
|
||||
session->openTextColorPicker();
|
||||
session->closeColorPicker(false);
|
||||
QCOMPARE(session->textDefaults().red, 1.0);
|
||||
QCOMPARE(session->textDefaults().green, 0.0);
|
||||
}
|
||||
|
||||
void TypeToolTests::theForegroundPickerPreviewsOpenTextToo()
|
||||
{
|
||||
// Swift 1.2.5 (4e5f4ec): open text follows the foreground picker.
|
||||
const auto session = makeSession();
|
||||
session->beginText(QPointF(30, 40));
|
||||
const auto paint = [&](const PaletteColor &color) {
|
||||
session->changeTextStyle([&](LayerTextStyle &style) {
|
||||
style.red = color.red;
|
||||
style.green = color.green;
|
||||
style.blue = color.blue;
|
||||
});
|
||||
};
|
||||
const auto colour = [&] {
|
||||
const LayerTextStyle style = session->textDraft().value().style;
|
||||
return PaletteColor{style.red, style.green, style.blue};
|
||||
};
|
||||
// Blue text, a black foreground: Cancel restores the text's own.
|
||||
paint(PaletteColor{0, 0, 1});
|
||||
session->openColorPicker(false);
|
||||
pick(*session, PaletteColor{0, 1, 0});
|
||||
session->previewTextColor();
|
||||
QCOMPARE(colour(), (PaletteColor{0, 1, 0}));
|
||||
session->closeColorPicker(false);
|
||||
QCOMPARE(colour(), (PaletteColor{0, 0, 1}));
|
||||
QCOMPARE(session->foregroundColor(), PaletteColor::black());
|
||||
// Another draft set meanwhile is neither previewed nor restored.
|
||||
session->openColorPicker(false);
|
||||
TextDraft other = session->textDraft().value();
|
||||
other.id = QUuid::createUuid();
|
||||
session->setTextDraft(other);
|
||||
paint(PaletteColor{1, 0, 0});
|
||||
pick(*session, PaletteColor{0, 1, 0});
|
||||
session->previewTextColor();
|
||||
QCOMPARE(colour(), (PaletteColor{1, 0, 0}));
|
||||
session->closeColorPicker(false);
|
||||
QCOMPARE(colour(), (PaletteColor{1, 0, 0}));
|
||||
// OK keeps the colour, and the foreground with it.
|
||||
session->openColorPicker(false);
|
||||
pick(*session, PaletteColor{0, 1, 0});
|
||||
session->previewTextColor();
|
||||
session->closeColorPicker(true);
|
||||
QCOMPARE(colour(), (PaletteColor{0, 1, 0}));
|
||||
QCOMPARE(session->foregroundColor(), (PaletteColor{0, 1, 0}));
|
||||
// The background picker leaves text alone.
|
||||
session->openColorPicker(true);
|
||||
pick(*session, PaletteColor{1, 0, 0});
|
||||
session->previewTextColor();
|
||||
QCOMPARE(colour(), (PaletteColor{0, 1, 0}));
|
||||
session->closeColorPicker(false);
|
||||
// The Type bar's picker: a later draft keeps its colour.
|
||||
session->openTextColorPicker();
|
||||
other.id = QUuid::createUuid();
|
||||
session->setTextDraft(other);
|
||||
paint(PaletteColor{1, 0, 0});
|
||||
session->closeColorPicker(false);
|
||||
QCOMPARE(colour(), (PaletteColor{1, 0, 0}));
|
||||
}
|
||||
|
||||
QTEST_MAIN(TypeToolTests)
|
||||
#include "TypeToolTests.moc"
|
||||
@@ -0,0 +1,190 @@
|
||||
#include "CanvasFixtures.h"
|
||||
#include "Document/Filters.h"
|
||||
#include "MenuFixtures.h"
|
||||
#include "SessionFixtures.h"
|
||||
#include <QPainter>
|
||||
|
||||
extern "C" {
|
||||
#include "AdjustPixels.h"
|
||||
}
|
||||
|
||||
// Swift 1.2.5: Vignette round by default, painting an empty layer.
|
||||
namespace {
|
||||
bool prepared(EditorSession &session)
|
||||
{
|
||||
return QTest::qWaitFor([&] { return session.filterEdit() && !session.filterEdit().value().preparing; }, 20'000);
|
||||
}
|
||||
|
||||
bool committed(EditorSession &session)
|
||||
{
|
||||
bool done = false;
|
||||
session.commitFilter([&done] { done = true; });
|
||||
return QTest::qWaitFor([&done] { return done; }, 20'000);
|
||||
}
|
||||
}
|
||||
|
||||
class VignetteCanvasTests : public QObject {
|
||||
Q_OBJECT
|
||||
private slots:
|
||||
void clearPixelsTakeTheColourTowardTheFramesEdge();
|
||||
void theCanvasFramesALayerThatSitsInsideIt();
|
||||
void onlyVignettePaintsAnEmptyLayer();
|
||||
void anEmptyLayerTakesTheVignetteAcrossTheCanvas();
|
||||
void theCanvasShowsThePreviewOnAnEmptyLayer();
|
||||
void aLayerWithPixelsKeepsItsOwnFrameAndClear();
|
||||
void aSmallEmptyLayerGrowsToTheCanvas();
|
||||
void theFrameIsReadTopDown();
|
||||
};
|
||||
|
||||
void VignetteCanvasTests::clearPixelsTakeTheColourTowardTheFramesEdge()
|
||||
{
|
||||
QImage clear = BrushRaster::context(41, 41, false);
|
||||
// Without fillsClear, clear pixels stay as they are.
|
||||
QImage kept = clear.copy();
|
||||
adjust_colored_vignette(kept.bits(), 41, 41, size_t(kept.bytesPerLine()), 0, 0, 41, 41, 0, 100, 50, 100, 60, 25, 1, 0, 0);
|
||||
QCOMPARE(kept, clear);
|
||||
adjust_colored_vignette(clear.bits(), 41, 41, size_t(clear.bytesPerLine()), 0, 0, 41, 41, 1, 100, 50, 100, 60, 25, 1, 0, 0);
|
||||
// The middle stays clear; the corner is red, near opaque.
|
||||
QCOMPARE(clear.pixel(20, 20), qRgba(0, 0, 0, 0));
|
||||
const QRgb corner = clear.pixel(0, 0);
|
||||
QVERIFY(qAlpha(corner) > 240 && qRed(corner) == qAlpha(corner) && qGreen(corner) == 0 && qBlue(corner) == 0);
|
||||
}
|
||||
|
||||
void VignetteCanvasTests::theCanvasFramesALayerThatSitsInsideIt()
|
||||
{
|
||||
// A 20-pixel layer at (10, 10) of a 40-pixel canvas.
|
||||
const QImage clear = BrushRaster::context(20, 20, false);
|
||||
FilterSettings settings;
|
||||
settings.vignetteAmount = 100;
|
||||
FilterJob job{FilterKind::vignette, clear, settings, 1, std::nullopt, QTransform::fromTranslate(10, 10)};
|
||||
job.canvas = QRectF(0, 0, 40, 40);
|
||||
QImage expected = clear.copy();
|
||||
adjust_colored_vignette(expected.bits(), 20, 20, size_t(expected.bytesPerLine()), -10, -10, 40, 40, 1, 100, 50, 100, 60, 25, 0, 0, 0);
|
||||
QCOMPARE(PixelFilter::run(job), expected);
|
||||
// Its own corner sits inside the frame: fainter.
|
||||
QImage framedAlone = clear.copy();
|
||||
adjust_colored_vignette(framedAlone.bits(), 20, 20, size_t(framedAlone.bytesPerLine()), 0, 0, 20, 20, 1, 100, 50, 100, 60, 25, 0, 0, 0);
|
||||
QVERIFY(qAlpha(expected.pixel(0, 0)) < qAlpha(framedAlone.pixel(0, 0)));
|
||||
}
|
||||
|
||||
void VignetteCanvasTests::onlyVignettePaintsAnEmptyLayer()
|
||||
{
|
||||
Bar bar;
|
||||
EditorSession &session = bar.session();
|
||||
session.createDocument(40, 30);
|
||||
session.addBlankLayer();
|
||||
QVERIFY(!session.canAdjustColors() && session.canVignette());
|
||||
QVERIFY(bar.action("vignette").isEnabled() && !bar.action("gaussianBlur").isEnabled());
|
||||
// Other filters refuse the empty layer.
|
||||
session.beginFilter(FilterKind::gaussianBlur);
|
||||
QVERIFY(!session.filterEdit());
|
||||
// Folders and adjustments hold no pixels to paint.
|
||||
session.addAdjustment(AdjustmentKind::curves);
|
||||
session.setAdjustmentEditingID(std::nullopt);
|
||||
QVERIFY(!session.canVignette() && !bar.action("vignette").isEnabled());
|
||||
session.addGroup();
|
||||
QVERIFY(!session.canVignette());
|
||||
}
|
||||
|
||||
void VignetteCanvasTests::anEmptyLayerTakesTheVignetteAcrossTheCanvas()
|
||||
{
|
||||
EditorSession session;
|
||||
session.createDocument(40, 30);
|
||||
session.addBlankLayer();
|
||||
const QUuid id = session.activeLayerID().value();
|
||||
session.beginFilter(FilterKind::vignette);
|
||||
QVERIFY(prepared(session));
|
||||
QVERIFY(session.filterEdit().value().startedEmpty && session.filterEdit().value().canvas == QRectF(0, 0, 40, 30));
|
||||
// Round by default, as Swift 1.2.5 opens it.
|
||||
QCOMPARE(session.filterEdit().value().settings.vignetteRoundness, 100.0);
|
||||
QVERIFY(session.filterEdit().value().previewImage(id));
|
||||
QVERIFY(!session.activeLayer().value().asset);
|
||||
QVERIFY(committed(session));
|
||||
QCOMPARE(session.history.undoName(), QString("Vignette"));
|
||||
const ImageLayer layer = session.activeLayer().value();
|
||||
const QImage pixels = layer.asset.value().image();
|
||||
QCOMPARE(pixels.size(), QSize(40, 30));
|
||||
QCOMPARE(layer.transform.origin, QPointF(0, 0));
|
||||
// The kernel's canvas-wide fill: black edges, a clear middle.
|
||||
QImage expected = BrushRaster::context(40, 30, false);
|
||||
const FilterSettings defaults;
|
||||
adjust_colored_vignette(expected.bits(), 40, 30, size_t(expected.bytesPerLine()), 0, 0, 40, 30, 1, defaults.vignetteAmount, defaults.vignetteMidpoint,
|
||||
defaults.vignetteRoundness, defaults.vignetteFeather, defaults.vignetteHighlights, 0, 0, 0);
|
||||
QCOMPARE(pixels, expected);
|
||||
QVERIFY(qAlpha(pixels.pixel(20, 15)) == 0 && qAlpha(pixels.pixel(0, 0)) > 80);
|
||||
session.undo();
|
||||
QVERIFY(!session.activeLayer().value().asset);
|
||||
}
|
||||
|
||||
void VignetteCanvasTests::theCanvasShowsThePreviewOnAnEmptyLayer()
|
||||
{
|
||||
Shown shown(QSize(40, 30));
|
||||
shown.settle();
|
||||
shown.session.addBlankLayer();
|
||||
QCoreApplication::processEvents();
|
||||
const QImage before = shown.canvas->grab().toImage();
|
||||
shown.session.beginFilter(FilterKind::vignette);
|
||||
QVERIFY(prepared(shown.session));
|
||||
QVERIFY(shown.session.filterEdit().value().previewImage(shown.session.activeLayerID().value()));
|
||||
QTRY_VERIFY(shown.canvas->grab().toImage() != before);
|
||||
shown.session.cancelFilter();
|
||||
QTRY_COMPARE(shown.canvas->grab().toImage(), before);
|
||||
}
|
||||
|
||||
void VignetteCanvasTests::aLayerWithPixelsKeepsItsOwnFrameAndClear()
|
||||
{
|
||||
// A grey square in clear pixels, smaller than the canvas.
|
||||
EditorSession session;
|
||||
session.createDocument(48, 48);
|
||||
QImage image = BrushRaster::context(24, 24, false);
|
||||
QPainter(&image).fillRect(QRect(3, 3, 18, 18), QColor::fromRgbF(0.7, 0.7, 0.7));
|
||||
session.insert(ImportedImage(image, image, QStringLiteral("Square")));
|
||||
session.beginFilter(FilterKind::vignette);
|
||||
QVERIFY(prepared(session));
|
||||
QVERIFY(!session.filterEdit().value().startedEmpty && !session.filterEdit().value().canvas);
|
||||
QVERIFY(committed(session));
|
||||
const QImage pixels = session.activeLayer().value().asset.value().image();
|
||||
QCOMPARE(pixels.size(), QSize(24, 24));
|
||||
// Clear stays clear; the square darkens within its own frame.
|
||||
QCOMPARE(qAlpha(pixels.pixel(0, 0)), 0);
|
||||
QCOMPARE(qAlpha(pixels.pixel(3, 3)), 255);
|
||||
QVERIFY(qRed(pixels.pixel(3, 3)) < qRed(pixels.pixel(12, 12)));
|
||||
}
|
||||
|
||||
void VignetteCanvasTests::aSmallEmptyLayerGrowsToTheCanvas()
|
||||
{
|
||||
// An empty box inside the canvas, off its corner.
|
||||
EditorSession session;
|
||||
session.createDocument(40, 30);
|
||||
session.addBlankLayer();
|
||||
const QUuid id = session.activeLayerID().value();
|
||||
rewrite(session, [&](ProjectSnapshot &snapshot) { record(snapshot, id).transform = LayerTransform{.origin = {3, 7}, .size = {20, 12}}; });
|
||||
session.selectLayer(id);
|
||||
session.beginFilter(FilterKind::vignette);
|
||||
QVERIFY(prepared(session));
|
||||
QCOMPARE(session.filterEdit().value().previewSource.size(), QSize(40, 30));
|
||||
QCOMPARE(session.filterEdit().value().grownTransform.value(), (LayerTransform{.origin = {0, 0}, .size = {40, 30}}));
|
||||
QVERIFY(committed(session));
|
||||
const ImageLayer layer = session.activeLayer().value();
|
||||
QCOMPARE(layer.transform, (LayerTransform{.origin = {0, 0}, .size = {40, 30}}));
|
||||
// The canvas's corner, past the old box, took the colour.
|
||||
QVERIFY(qAlpha(layer.asset.value().image().pixel(0, 0)) > 80);
|
||||
}
|
||||
|
||||
void VignetteCanvasTests::theFrameIsReadTopDown()
|
||||
{
|
||||
// Off-centre and not square, so a flip would show.
|
||||
const QImage clear = BrushRaster::context(20, 12, false);
|
||||
FilterSettings settings;
|
||||
settings.vignetteAmount = 100;
|
||||
FilterJob job{FilterKind::vignette, clear, settings, 1, std::nullopt, QTransform::fromTranslate(3, 7)};
|
||||
job.canvas = QRectF(0, 0, 40, 30);
|
||||
const QImage result = PixelFilter::run(job);
|
||||
QImage expected = clear.copy();
|
||||
adjust_colored_vignette(expected.bits(), 20, 12, size_t(expected.bytesPerLine()), -3, -7, 40, 30, 1, 100, 50, 100, 60, 25, 0, 0, 0);
|
||||
QCOMPARE(result, expected);
|
||||
QCOMPARE(qAlpha(result.pixel(0, 0)), 100);
|
||||
}
|
||||
|
||||
QTEST_MAIN(VignetteCanvasTests)
|
||||
#include "VignetteCanvasTests.moc"
|
||||
Reference in new issue
Block a user