463 lines
23 KiB
C++
463 lines
23 KiB
C++
#include "Document/BrushStroke.h"
|
|
#include "Document/EditorSession.h"
|
|
#include "Document/PixelAdjust.h"
|
|
#include "UI/ColorPaletteControls.h"
|
|
#include "UI/FilterSheet.h"
|
|
#include <QLabel>
|
|
#include <QLineEdit>
|
|
#include <QSignalSpy>
|
|
#include <QSlider>
|
|
#include <QTimer>
|
|
#include <QtTest>
|
|
#include <limits>
|
|
|
|
extern "C" {
|
|
#include "AdjustPixels.h"
|
|
}
|
|
|
|
// Swift's FinishingFilterTests: Vignette, Bloom / Glow, Tonal Contrast.
|
|
namespace {
|
|
QImage filled(int width, int height, const QColor &colour)
|
|
{
|
|
QImage image = BrushRaster::context(width, height, false);
|
|
image.fill(colour);
|
|
return image;
|
|
}
|
|
|
|
QImage run(FilterKind kind, const QImage &source, const FilterSettings &settings, double scale = 1)
|
|
{
|
|
return PixelFilter::run(FilterJob{kind, source, settings, scale, std::nullopt, QTransform()});
|
|
}
|
|
|
|
bool opaque(const QImage &image)
|
|
{
|
|
for (int y = 0; y < image.height(); ++y) {
|
|
for (int x = 0; x < image.width(); ++x) {
|
|
if (qAlpha(image.pixel(x, y)) != 255)
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// A 24-point layer, a grey square, a 48 canvas.
|
|
EditorSession &sample(EditorSession &session)
|
|
{
|
|
session.createDocument(48, 48);
|
|
QImage image = BrushRaster::context(24, 24, false);
|
|
image.fill(0);
|
|
QPainter(&image).fillRect(QRect(3, 3, 18, 18), QColor::fromRgbF(0.7, 0.7, 0.7));
|
|
session.insert(ImportedImage(image, image, QStringLiteral("Sample")));
|
|
return session;
|
|
}
|
|
|
|
bool committed(EditorSession &session)
|
|
{
|
|
bool done = false;
|
|
session.commitFilter([&done] { done = true; });
|
|
return QTest::qWaitFor([&done] { return done; }, 10'000);
|
|
}
|
|
}
|
|
|
|
class FinishingFilterTests : public QObject {
|
|
Q_OBJECT
|
|
private slots:
|
|
void vignetteDarkensCornersWhileKeepingCenterAndAlpha();
|
|
void vignetteBlendsSelectedColorOnlyAtEdges();
|
|
void bloomSpreadsLightFromBrightPixels();
|
|
void tonalContrastIncreasesMidtoneDetailWithoutChangingAlpha();
|
|
void eachFilterCommitsAsOneUndoStepAndKeepsLayerEffects();
|
|
void theKernelsTakeTheSettingsAtTheScale();
|
|
void settingsKeepSwiftsDefaultsAndBounds();
|
|
void nothingToChangeClosesWithoutAStep();
|
|
void bloomGrowsItsGridAndIsTrimmed();
|
|
void aPreviewForAnOlderGridLandsNothing();
|
|
void aJobWaitingBehindAnotherEditTakesTheNewGrid();
|
|
void theVignettePickerPreviewsAndPutsBack();
|
|
void theSheetShowsSwiftsControls();
|
|
};
|
|
|
|
void FinishingFilterTests::vignetteDarkensCornersWhileKeepingCenterAndAlpha()
|
|
{
|
|
FilterSettings settings;
|
|
settings.vignetteAmount = 80;
|
|
const QImage result = run(FilterKind::vignette, filled(41, 41, QColor::fromRgbF(0.8, 0.8, 0.8)), settings);
|
|
QVERIFY(qRed(result.pixel(20, 20)) > qRed(result.pixel(0, 0)) + 50);
|
|
QVERIFY(std::abs(qRed(result.pixel(20, 20)) - 204) <= 2);
|
|
QVERIFY(opaque(result));
|
|
}
|
|
|
|
void FinishingFilterTests::vignetteBlendsSelectedColorOnlyAtEdges()
|
|
{
|
|
FilterSettings settings;
|
|
settings.vignetteAmount = 100;
|
|
settings.vignetteHighlights = 0;
|
|
settings.vignetteColor = AdjustmentColor(1, 0, 0);
|
|
const QImage result = run(FilterKind::vignette, filled(41, 41, QColor::fromRgbF(0.5, 0.5, 0.5)), settings);
|
|
const QRgb corner = result.pixel(0, 0), centre = result.pixel(20, 20);
|
|
QVERIFY(qRed(corner) > qGreen(corner) + 80);
|
|
QVERIFY(std::abs(qRed(centre) - qGreen(centre)) <= 2 && std::abs(qRed(centre) - 128) <= 2);
|
|
QVERIFY(opaque(result));
|
|
}
|
|
|
|
void FinishingFilterTests::bloomSpreadsLightFromBrightPixels()
|
|
{
|
|
QImage source = filled(65, 65, Qt::black);
|
|
QPainter(&source).fillRect(QRect(30, 30, 5, 5), Qt::white);
|
|
FilterSettings settings;
|
|
settings.bloomAmount = 100;
|
|
settings.bloomRadius = 12;
|
|
const QImage result = run(FilterKind::bloomGlow, source, settings);
|
|
QVERIFY(qRed(result.pixel(40, 32)) > qRed(result.pixel(2, 2)) && qRed(result.pixel(40, 32)) > 0);
|
|
QVERIFY(opaque(result));
|
|
// Past a clear layer's light, the glow still shows.
|
|
QImage isolated = filled(65, 65, Qt::transparent);
|
|
QPainter(&isolated).fillRect(QRect(30, 30, 5, 5), Qt::white);
|
|
QVERIFY(qAlpha(run(FilterKind::bloomGlow, isolated, settings).pixel(40, 32)) > 0);
|
|
}
|
|
|
|
void FinishingFilterTests::tonalContrastIncreasesMidtoneDetailWithoutChangingAlpha()
|
|
{
|
|
QImage source = filled(64, 16, Qt::black);
|
|
{
|
|
QPainter painter(&source);
|
|
for (int stripe = 0; stripe < 8; ++stripe)
|
|
painter.fillRect(QRect(stripe * 8, 0, 8, 16), QColor::fromRgbF(stripe % 2 ? 0.6 : 0.4, stripe % 2 ? 0.6 : 0.4, stripe % 2 ? 0.6 : 0.4));
|
|
}
|
|
const FilterSettings settings{.tonalAmount = 100, .tonalRadius = 6, .tonalShadows = 0, .tonalMidtones = 100, .tonalHighlights = 0};
|
|
const QImage result = run(FilterKind::tonalContrast, source, settings);
|
|
QVERIFY(qRed(result.pixel(5, 8)) < qRed(source.pixel(5, 8)));
|
|
QVERIFY(qRed(result.pixel(13, 8)) > qRed(source.pixel(13, 8)));
|
|
QVERIFY(opaque(result));
|
|
}
|
|
|
|
void FinishingFilterTests::eachFilterCommitsAsOneUndoStepAndKeepsLayerEffects()
|
|
{
|
|
// Swift's three, and a blur: every filter keeps effects now.
|
|
for (const FilterKind kind : {FilterKind::vignette, FilterKind::bloomGlow, FilterKind::tonalContrast, FilterKind::gaussianBlur}) {
|
|
EditorSession session;
|
|
sample(session);
|
|
const LayerEffects effects{.stroke = StrokeEffect{.size = 3}};
|
|
session.setEffects(effects);
|
|
session.beginFilter(kind);
|
|
QVERIFY(session.filterEdit().value().kind == kind);
|
|
const int steps = session.history.undoCount();
|
|
QVERIFY(committed(session));
|
|
QVERIFY(!session.filterEdit());
|
|
QCOMPARE(session.history.undoCount(), steps + 1);
|
|
QCOMPARE(session.history.undoName(), rawValue(kind));
|
|
QVERIFY(session.activeLayer().value().effects == effects);
|
|
}
|
|
}
|
|
|
|
void FinishingFilterTests::theKernelsTakeTheSettingsAtTheScale()
|
|
{
|
|
QImage source = filled(40, 30, QColor(40, 120, 200));
|
|
QPainter(&source).fillRect(QRect(10, 8, 12, 10), QColor(250, 240, 90));
|
|
// Vignette: Swift's kernel with every setting handed on.
|
|
FilterSettings vignette{.vignetteAmount = 70, .vignetteColor = AdjustmentColor(0.2, 0.9, 0.4), .vignetteMidpoint = 30,
|
|
.vignetteRoundness = -40, .vignetteFeather = 20, .vignetteHighlights = 60};
|
|
QImage expected = source.copy();
|
|
// 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};
|
|
const QImage base = PixelAdjust::gaussianBlur(source, 6, false);
|
|
expected = source.copy();
|
|
adjust_tonal_contrast(expected.bits(), base.constBits(), 40, 30, size_t(expected.bytesPerLine()), size_t(base.bytesPerLine()), 80, -30, 70, 45);
|
|
QCOMPARE(run(FilterKind::tonalContrast, source, tonal, 2), expected);
|
|
// Bloom: the pixels plus their glow at amount / 50.
|
|
const FilterSettings bloom{.bloomAmount = 30, .bloomRadius = 2};
|
|
const QImage glow = PixelAdjust::gaussianBlur(source, 4, false);
|
|
expected = source.copy();
|
|
for (int y = 0; y < 30; ++y) {
|
|
for (int index = 0; index < 160; ++index)
|
|
expected.scanLine(y)[index] = uchar(std::min(255.0, std::round(source.constScanLine(y)[index] + 0.6 * glow.constScanLine(y)[index])));
|
|
}
|
|
QCOMPARE(run(FilterKind::bloomGlow, source, bloom, 2), expected);
|
|
}
|
|
|
|
void FinishingFilterTests::settingsKeepSwiftsDefaultsAndBounds()
|
|
{
|
|
const FilterSettings defaults;
|
|
QVERIFY(defaults.vignetteAmount == 35 && defaults.vignetteColor == AdjustmentColor(0, 0, 0) && defaults.vignetteMidpoint == 50);
|
|
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);
|
|
// Past the ends, clamped; no number, the default.
|
|
const double nan = std::numeric_limits<double>::quiet_NaN();
|
|
const FilterSettings high = FilterSettings{.vignetteAmount = 101, .vignetteColor = AdjustmentColor(2, -1, 0.5), .vignetteMidpoint = 101,
|
|
.vignetteRoundness = 101, .vignetteFeather = 101, .vignetteHighlights = 101, .bloomAmount = 101,
|
|
.bloomRadius = 151, .tonalAmount = 101, .tonalRadius = 101, .tonalShadows = 101,
|
|
.tonalMidtones = 101, .tonalHighlights = 101}
|
|
.normalized();
|
|
QVERIFY(high.vignetteAmount == 100 && high.vignetteColor == AdjustmentColor(1, 0, 0.5) && high.vignetteMidpoint == 100);
|
|
QVERIFY(high.vignetteRoundness == 100 && high.vignetteFeather == 100 && high.vignetteHighlights == 100 && high.bloomAmount == 100);
|
|
QVERIFY(high.bloomRadius == 150 && high.tonalAmount == 100 && high.tonalRadius == 100 && high.tonalShadows == 100);
|
|
QVERIFY(high.tonalMidtones == 100 && high.tonalHighlights == 100);
|
|
const FilterSettings low = FilterSettings{.vignetteAmount = -1, .vignetteMidpoint = -1, .vignetteRoundness = -101, .vignetteFeather = -1,
|
|
.vignetteHighlights = -1, .bloomAmount = -1, .bloomRadius = 0.5, .tonalAmount = -1, .tonalRadius = 0.5,
|
|
.tonalShadows = -101, .tonalMidtones = -101, .tonalHighlights = -101}
|
|
.normalized();
|
|
QVERIFY(low.vignetteAmount == 0 && low.vignetteMidpoint == 0 && low.vignetteRoundness == -100 && low.vignetteFeather == 0);
|
|
QVERIFY(low.vignetteHighlights == 0 && low.bloomAmount == 0 && low.bloomRadius == 1 && low.tonalAmount == 0 && low.tonalRadius == 1);
|
|
QVERIFY(low.tonalShadows == -100 && low.tonalMidtones == -100 && low.tonalHighlights == -100);
|
|
const FilterSettings none = FilterSettings{.vignetteAmount = nan, .vignetteMidpoint = nan, .vignetteRoundness = nan, .vignetteFeather = nan,
|
|
.vignetteHighlights = nan, .bloomAmount = nan, .bloomRadius = nan, .tonalAmount = nan,
|
|
.tonalRadius = nan, .tonalShadows = nan, .tonalMidtones = nan, .tonalHighlights = nan}
|
|
.normalized();
|
|
QVERIFY(none == FilterSettings());
|
|
// The Filter menu's order: after Add Noise, before Lens Correction.
|
|
QCOMPARE(rawValue(FilterKind::vignette), QString("Vignette"));
|
|
QCOMPARE(rawValue(FilterKind::bloomGlow), QString("Bloom / Glow"));
|
|
QCOMPARE(rawValue(FilterKind::tonalContrast), QString("Tonal Contrast"));
|
|
QVERIFY(allFilterKinds[3] == FilterKind::vignette && allFilterKinds[4] == FilterKind::bloomGlow && allFilterKinds[5] == FilterKind::dither
|
|
&& allFilterKinds[6] == FilterKind::tonalContrast);
|
|
QVERIFY(!isImageAdjustment(FilterKind::bloomGlow) && !isAutomatic(FilterKind::tonalContrast) && !isAutomatic(FilterKind::vignette));
|
|
}
|
|
|
|
void FinishingFilterTests::nothingToChangeClosesWithoutAStep()
|
|
{
|
|
const std::pair<FilterKind, FilterSettings> idle[] = {
|
|
{FilterKind::vignette, FilterSettings{.vignetteAmount = 0}},
|
|
{FilterKind::bloomGlow, FilterSettings{.bloomAmount = 0}},
|
|
{FilterKind::tonalContrast, FilterSettings{.tonalAmount = 0}},
|
|
{FilterKind::tonalContrast, FilterSettings{.tonalShadows = 0, .tonalMidtones = 0, .tonalHighlights = 0}}};
|
|
for (const auto &[kind, settings] : idle) {
|
|
EditorSession session;
|
|
sample(session);
|
|
const ImportedImage before = session.activeLayer().value().asset.value();
|
|
session.beginFilter(kind);
|
|
session.updateFilter(settings, true);
|
|
const int steps = session.history.undoCount();
|
|
QVERIFY(committed(session));
|
|
QVERIFY(!session.filterEdit() && session.history.undoCount() == steps);
|
|
QVERIFY(session.activeLayer().value().asset.value().identity() == before.identity());
|
|
}
|
|
// One tone alone still changes the pixels.
|
|
EditorSession session;
|
|
sample(session);
|
|
session.beginFilter(FilterKind::tonalContrast);
|
|
session.updateFilter(FilterSettings{.tonalShadows = 0, .tonalMidtones = 0, .tonalHighlights = 50}, true);
|
|
const int steps = session.history.undoCount();
|
|
QVERIFY(committed(session));
|
|
QCOMPARE(session.history.undoCount(), steps + 1);
|
|
}
|
|
|
|
void FinishingFilterTests::bloomGrowsItsGridAndIsTrimmed()
|
|
{
|
|
QCOMPARE(FilterEdit::blurMargin(FilterKind::bloomGlow, FilterSettings{.bloomRadius = 10}), 32.0);
|
|
QCOMPARE(FilterEdit::blurMargin(FilterKind::tonalContrast, FilterSettings{.tonalRadius = 10}), 0.0);
|
|
QCOMPARE(FilterEdit::blurMargin(FilterKind::vignette, FilterSettings()), 0.0);
|
|
EditorSession session;
|
|
sample(session);
|
|
const LayerTransform before = session.activeLayer().value().transform;
|
|
session.beginFilter(FilterKind::bloomGlow);
|
|
session.updateFilter(FilterSettings{.bloomAmount = 100, .bloomRadius = 4}, true);
|
|
// Room for the default radius, 24: three sigmas and two.
|
|
QCOMPARE(session.filterEdit().value().grownMargin, 74.0);
|
|
QVERIFY(committed(session));
|
|
// The glow reaches past the layer; clear edges go.
|
|
const LayerTransform after = session.activeLayer().value().transform;
|
|
QVERIFY(after.size.width() > before.size.width() && after.size.width() < before.size.width() + 2 * 74);
|
|
}
|
|
|
|
void FinishingFilterTests::aPreviewForAnOlderGridLandsNothing()
|
|
{
|
|
EditorSession session;
|
|
session.createDocument(1200, 1200);
|
|
session.insert(ImportedImage(filled(1200, 1200, QColor(90, 90, 90)), QImage(), QStringLiteral("Big")));
|
|
session.beginFilter(FilterKind::bloomGlow);
|
|
QTRY_VERIFY(!session.filterEdit().value().preparing);
|
|
// Every landed preview matches the grid it shows on.
|
|
bool mismatched = false;
|
|
QObject::connect(&session, &EditorSession::changed, &session, [&] {
|
|
const std::optional<FilterEdit> &edit = session.filterEdit();
|
|
if (edit && edit->preparedPreview && edit->preparedPreview->size() != edit->previewSource.size())
|
|
mismatched = true;
|
|
});
|
|
session.updateFilter(FilterSettings{.bloomRadius = 5}, true);
|
|
const quint64 version = session.filterEdit().value().previewSourceVersion;
|
|
// Wider than the grid: it grows mid-render.
|
|
session.updateFilter(FilterSettings{.bloomRadius = 40}, true);
|
|
QCOMPARE(session.filterEdit().value().previewSourceVersion, version + 1);
|
|
QTRY_VERIFY(session.filterEdit().value().preparedPreview && !session.filterEdit().value().preparing);
|
|
QVERIFY(!mismatched);
|
|
QCOMPARE(session.filterEdit().value().preparedPreview->size(), session.filterEdit().value().previewSource.size());
|
|
// Preview off: nothing waits; the stale render ends it.
|
|
session.updateFilter(FilterSettings{.bloomRadius = 5}, true);
|
|
session.updateFilter(FilterSettings{.bloomRadius = 60}, false);
|
|
QTRY_VERIFY(!session.filterEdit().value().preparing);
|
|
QVERIFY(!session.filterEdit().value().preparedPreview);
|
|
}
|
|
|
|
void FinishingFilterTests::aJobWaitingBehindAnotherEditTakesTheNewGrid()
|
|
{
|
|
// The reviewer's sequence: a cancelled edit's worker still runs.
|
|
EditorSession session;
|
|
session.createDocument(1200, 1200);
|
|
session.insert(ImportedImage(filled(1200, 1200, QColor(90, 90, 90)), QImage(), QStringLiteral("Big")));
|
|
session.beginFilter(FilterKind::gaussianBlur);
|
|
QVERIFY(session.filterEdit().value().preparing);
|
|
session.cancelFilter();
|
|
// Bloom's first job waits; Preview off grows its grid meanwhile.
|
|
session.beginFilter(FilterKind::bloomGlow);
|
|
session.updateFilter(FilterSettings{.bloomRadius = 50}, false);
|
|
bool mismatched = false, armed = true;
|
|
QObject::connect(&session, &EditorSession::changed, &session, [&] {
|
|
const std::optional<FilterEdit> &edit = session.filterEdit();
|
|
if (edit && edit->preparedPreview && edit->preparedPreview->size() != edit->previewSource.size())
|
|
mismatched = true;
|
|
// The waiting job starts: Preview comes back next turn.
|
|
if (std::exchange(armed, false))
|
|
QTimer::singleShot(0, &session, [&session] { session.updateFilter(FilterSettings{.bloomRadius = 50}, true); });
|
|
});
|
|
QTRY_VERIFY(!armed);
|
|
QTRY_VERIFY(session.filterEdit().value().preparedPreview && !session.filterEdit().value().preparing);
|
|
QVERIFY(!mismatched);
|
|
}
|
|
|
|
void FinishingFilterTests::theVignettePickerPreviewsAndPutsBack()
|
|
{
|
|
EditorSession session;
|
|
sample(session);
|
|
// Only the open Vignette takes it.
|
|
session.beginFilter(FilterKind::bloomGlow);
|
|
session.openVignetteColorPicker();
|
|
QVERIFY(!session.colorPicker());
|
|
session.cancelFilter();
|
|
session.beginFilter(FilterKind::vignette);
|
|
session.updateFilter(FilterSettings{.vignetteColor = AdjustmentColor(0, 0, 1)}, true);
|
|
session.openVignetteColorPicker();
|
|
const ColorPickerState opened = session.colorPicker().value();
|
|
QCOMPARE(opened.target.title(), QString("Color Picker (Vignette Color)"));
|
|
QVERIFY(opened.original == (PaletteColor{0, 0, 1}));
|
|
// The working colour previews; Cancel puts the original back.
|
|
PickerHSB hsb = opened.hsb;
|
|
hsb.setRGB(PaletteColor{1, 0, 0});
|
|
session.setColorPickerHSB(hsb);
|
|
session.previewVignetteColor();
|
|
QCOMPARE(session.filterEdit().value().settings.vignetteColor, AdjustmentColor(1, 0, 0));
|
|
session.closeColorPicker(false);
|
|
QCOMPARE(session.filterEdit().value().settings.vignetteColor, AdjustmentColor(0, 0, 1));
|
|
// OK keeps the colour; a picker already open refuses another.
|
|
session.openVignetteColorPicker();
|
|
hsb.setRGB(PaletteColor{0, 1, 0});
|
|
session.setColorPickerHSB(hsb);
|
|
const QUuid id = session.colorPicker().value().id;
|
|
session.openVignetteColorPicker();
|
|
QCOMPARE(session.colorPicker().value().id, id);
|
|
session.closeColorPicker(true);
|
|
QCOMPARE(session.filterEdit().value().settings.vignetteColor, AdjustmentColor(0, 1, 0));
|
|
// The panel's Cancel closes the picker unkept, its OK kept.
|
|
session.openVignetteColorPicker();
|
|
hsb.setRGB(PaletteColor{1, 1, 0});
|
|
session.setColorPickerHSB(hsb);
|
|
session.cancelFilter();
|
|
QVERIFY(!session.colorPicker() && !session.filterEdit());
|
|
session.beginFilter(FilterKind::vignette);
|
|
session.openVignetteColorPicker();
|
|
hsb.setRGB(PaletteColor{1, 1, 0});
|
|
session.setColorPickerHSB(hsb);
|
|
QVERIFY(committed(session));
|
|
QVERIFY(!session.colorPicker());
|
|
QCOMPARE(session.filterSettings().vignetteColor, AdjustmentColor(1, 1, 0));
|
|
}
|
|
|
|
void FinishingFilterTests::theSheetShowsSwiftsControls()
|
|
{
|
|
// Each row: title, range, unit and scale, as Swift.
|
|
struct Row {
|
|
const char *name;
|
|
double FilterSettings::*member;
|
|
double low, high;
|
|
QString unit;
|
|
bool logarithmic;
|
|
};
|
|
const std::pair<FilterKind, std::vector<Row>> kinds[] = {
|
|
{FilterKind::vignette,
|
|
{{"amount", &FilterSettings::vignetteAmount, 0, 100, "%", false},
|
|
{"midpoint", &FilterSettings::vignetteMidpoint, 0, 100, "%", false},
|
|
{"roundness", &FilterSettings::vignetteRoundness, -100, 100, "", false},
|
|
{"feather", &FilterSettings::vignetteFeather, 0, 100, "%", false},
|
|
{"highlights", &FilterSettings::vignetteHighlights, 0, 100, "%", false}}},
|
|
{FilterKind::bloomGlow,
|
|
{{"amount", &FilterSettings::bloomAmount, 0, 100, "%", false}, {"radius", &FilterSettings::bloomRadius, 1, 150, "px", true}}},
|
|
{FilterKind::tonalContrast,
|
|
{{"amount", &FilterSettings::tonalAmount, 0, 100, "%", false}, {"shadows", &FilterSettings::tonalShadows, -100, 100, "%", false},
|
|
{"midtones", &FilterSettings::tonalMidtones, -100, 100, "%", false},
|
|
{"highlights", &FilterSettings::tonalHighlights, -100, 100, "%", false},
|
|
{"radius", &FilterSettings::tonalRadius, 1, 100, "px", true}}}};
|
|
for (const auto &[kind, rows] : kinds) {
|
|
EditorSession session;
|
|
sample(session);
|
|
session.beginFilter(kind);
|
|
FilterSheet sheet(session);
|
|
QCOMPARE(sheet.findChildren<QSlider *>().size(), qsizetype(rows.size()));
|
|
for (const Row &row : rows) {
|
|
auto *slider = sheet.findChild<QSlider *>(QString::fromLatin1(row.name) + "Slider");
|
|
auto *field = sheet.findChild<QLineEdit *>(QString::fromLatin1(row.name) + "Field");
|
|
QVERIFY2(slider && field, row.name);
|
|
const QList<QLabel *> labels = field->parentWidget()->findChildren<QLabel *>(QString(), Qt::FindDirectChildrenOnly);
|
|
QCOMPARE(labels.last()->text(), row.unit);
|
|
// The ends, then the middle: linear, or the geometric mean.
|
|
const double middle = row.logarithmic ? std::round(std::sqrt(row.low * row.high)) : (row.low + row.high) / 2;
|
|
for (const auto &[position, value] : {std::pair(0, row.low), std::pair(1000, row.high), std::pair(500, middle)}) {
|
|
// Its own setting alone moves, and the field shows it.
|
|
FilterSettings expected = session.filterEdit().value().settings;
|
|
expected.*row.member = value;
|
|
slider->setValue(position);
|
|
QVERIFY2(session.filterEdit().value().settings == expected, row.name);
|
|
QCOMPARE(field->text().toDouble(), value);
|
|
}
|
|
}
|
|
QCOMPARE(sheet.findChild<SwatchButton *>("vignetteSwatch") != nullptr, kind == FilterKind::vignette);
|
|
}
|
|
EditorSession session;
|
|
sample(session);
|
|
session.beginFilter(FilterKind::vignette);
|
|
FilterSheet sheet(session);
|
|
QCOMPARE(sheet.findChild<QSlider *>("amountSlider")->parentWidget()->toolTip(),
|
|
QString("Blend the chosen color into the edges while keeping the center unchanged"));
|
|
QCOMPARE(sheet.findChild<QSlider *>("highlightsSlider")->parentWidget()->toolTip(), QString("Protect bright areas near the edge"));
|
|
QVERIFY(sheet.findChild<QSlider *>("midpointSlider")->parentWidget()->toolTip().isEmpty());
|
|
// The swatch: 24 points, its words, picker and colour.
|
|
SwatchButton &swatch = *sheet.findChild<SwatchButton *>("vignetteSwatch");
|
|
QCOMPARE(swatch.size(), QSize(24, 24));
|
|
QCOMPARE(swatch.toolTip(), QString("Choose the vignette color"));
|
|
QCOMPARE(swatch.accessibleName(), QString("Vignette color"));
|
|
const QList<QLabel *> labels = swatch.parentWidget()->findChildren<QLabel *>();
|
|
QVERIFY(labels.size() == 1 && labels[0]->text() == QString("Color") && labels[0]->width() == 95);
|
|
QCOMPARE(swatch.grab().toImage().pixelColor(12, 12).rgb(), QColor(Qt::black).rgb());
|
|
swatch.click();
|
|
QVERIFY(session.colorPicker().value().target.kind == ColorPickerTarget::Kind::vignette);
|
|
// The sheet hands the picker's colour on and repaints.
|
|
PickerHSB hsb = session.colorPicker().value().hsb;
|
|
hsb.setRGB(PaletteColor{1, 0, 0});
|
|
session.setColorPickerHSB(hsb);
|
|
QCOMPARE(session.filterEdit().value().settings.vignetteColor, AdjustmentColor(1, 0, 0));
|
|
QCOMPARE(swatch.grab().toImage().pixelColor(12, 12).rgb(), QColor(Qt::red).rgb());
|
|
// Shown, the swatch repaints on a change unasked.
|
|
sheet.show();
|
|
QVERIFY(QTest::qWaitForWindowExposed(&sheet));
|
|
struct Paints : QObject {
|
|
int count = 0;
|
|
bool eventFilter(QObject *, QEvent *event) override
|
|
{
|
|
count += event->type() == QEvent::Paint;
|
|
return false;
|
|
}
|
|
} paints;
|
|
swatch.installEventFilter(&paints);
|
|
QTest::qWait(100);
|
|
paints.count = 0;
|
|
hsb.setRGB(PaletteColor{0, 0, 1});
|
|
session.setColorPickerHSB(hsb);
|
|
QTRY_VERIFY(paints.count > 0);
|
|
}
|
|
|
|
QTEST_MAIN(FinishingFilterTests)
|
|
#include "FinishingFilterTests.moc"
|