Review 2 fixes for 13.17 and 13.18: the glow scales back exactly, tests for the front tab, a rendering editor, the Scanlines rows, removePoint's reach, repair's strict bound, the divider strip; bullets corrected

This commit is contained in:
ZacharyZhang-NY committed 2026-10-01 20:02:02 -04:00
1 parent 81894f744a
commit 5c8e18c5af
7 files changed
+129 -18

No files matched your search

+3
View File
@@ -50,6 +50,9 @@ void CameraRawColorTests::curvesAreRepairedIntoOrder()
QCOMPARE(repaired({{0, 0}, {0.995, 0.5}, {1, 1}}), (Points{{0, 0}, {0.99, 0.5}, {1, 1}}));
QCOMPARE(repaired({{0, 0}, {0.5, 0.3}, {0.505, 0.6}, {1, 1}}), (Points{{0, 0}, {0.5, 0.3}, {1, 1}}));
QCOMPARE(repaired({{0, 0}, {0.004, 0.3}, {1, 1}}), CameraRawCurveSettings::linear());
// Exactly a hundredth past the last kept is too near.
QCOMPARE(repaired({{0, 0}, {0.5, 0.3}, {0.5 + 0.01, 0.6}, {1, 1}}), (Points{{0, 0}, {0.5, 0.3}, {1, 1}}));
QCOMPARE(repaired({{0, 0}, {0.5, 0.3}, {0.5 + 0.0100001, 0.6}, {1, 1}}).size(), size_t(4));
// A curve of one point passes its input through.
CameraRawCurveSettings curve;
const std::vector<float> table = curve.channelTable({{0.5, 0.7}});
+27
View File
@@ -59,6 +59,8 @@ private slots:
void theChosenPointIsLitAndReadOut();
void aNewChannelOrPresetDropsTheChoice();
void aDoubleClickRemovesAnInnerPointAndTheChoice();
void aDoubleClickReachesFourHundredthsByX();
void aDoubleClickInTheStripMovesADivider();
void onlyTheLeftButtonDragsAndAPressStartsAfresh();
void theGraphSaysWhatADragDoes();
};
@@ -358,6 +360,31 @@ void CameraRawCurveGraphTests::aDoubleClickRemovesAnInnerPointAndTheChoice()
QCOMPARE(graph.readout().text(), QString("In 255 Out 255"));
}
void CameraRawCurveGraphTests::aDoubleClickReachesFourHundredthsByX()
{
Graph graph(true);
// Beside the start nothing is added; 0.037 removes, 0.042 not.
QVERIFY(3.0 / graph.graph->width() < 0.01);
for (const auto &[inner, removed] : {std::pair(0.045, true), std::pair(0.05, false)}) {
graph.set([inner](CameraRawCurveSettings &curve) { curve.rgb = {{0, 0}, {inner, 0.9}, {1, 1}}; });
QTest::mouseDClick(graph.graph->window()->windowHandle(), Qt::LeftButton, {}, graph.graph->mapTo(graph.graph->window(), QPoint(3, 140)));
QCOMPARE(graph.curve().rgb.size(), removed ? size_t(2) : size_t(3));
}
}
void CameraRawCurveGraphTests::aDoubleClickInTheStripMovesADivider()
{
Graph graph;
const int w = graph.graph->width(), h = graph.graph->height();
graph.set([](CameraRawCurveSettings &curve) { curve.rgb = {{0, 0}, {0.5, 0.5}, {1, 1}}; });
// Both presses take the nearest divider there; no point goes.
QTest::mouseDClick(graph.graph->window()->windowHandle(), Qt::LeftButton, {}, graph.graph->mapTo(graph.graph->window(), QPoint(int(0.3 * w), h - 5)));
QVERIFY(std::abs(graph.curve().shadowSplit - 30) < 0.5);
QVERIFY(graph.curve().darkSplit == 50 && graph.curve().lightSplit == 75);
QCOMPARE(graph.curve().rgb.size(), size_t(3));
QVERIFY(graph.curve().shadows == 0 && graph.curve().darks == 0);
}
void CameraRawCurveGraphTests::onlyTheLeftButtonDragsAndAPressStartsAfresh()
{
Graph graph(true);
+48
View File
@@ -53,6 +53,8 @@ private slots:
void quittingCancelsAnOpenDialog();
void quittingCancelsTheOtherDialogs();
void quittingCancelsAnAdjustmentsEditor();
void onlyTheTabOnScreenIsSettled();
void aQuitWaitsForAnAdjustmentsEditorToRender();
void aWorkspaceGoneMidSettleCallsNothing();
void closingTheWindowAppliesTheGradientThenAsks();
void aBusyProjectHoldsTheQuit();
@@ -165,6 +167,52 @@ void QuitPendingEditsTests::quittingCancelsAnAdjustmentsEditor()
QVERIFY(workspace.canSwitch());
}
void QuitPendingEditsTests::onlyTheTabOnScreenIsSettled()
{
ProjectWorkspace workspace;
const std::shared_ptr<ProjectTab> first = workspace.tabs().front();
first->session.createDocument(100, 20);
first->session.insert(filled(QColor(200, 100, 50)));
workspace.newCanvas();
const std::shared_ptr<ProjectTab> second = workspace.tabs().back();
QVERIFY(second != first);
first->session.beginFilter(FilterKind::gaussianBlur);
second->session.createDocument(100, 20);
second->session.insert(filled(QColor(200, 100, 50)));
second->session.beginFilter(FilterKind::gaussianBlur);
QCOMPARE(workspace.current().id, second->id);
QVERIFY(awaited([&](std::function<void()> done) { workspace.settlePendingEdits(std::move(done)); }));
QVERIFY(!second->session.filterEdit());
QVERIFY(first->session.filterEdit());
// The other way round, the first alone.
workspace.select(first->id);
second->session.beginFilter(FilterKind::gaussianBlur);
QVERIFY(awaited([&](std::function<void()> done) { workspace.settlePendingEdits(std::move(done)); }));
QVERIFY(!first->session.filterEdit());
QVERIFY(second->session.filterEdit());
}
void QuitPendingEditsTests::aQuitWaitsForAnAdjustmentsEditorToRender()
{
ProjectWorkspace workspace;
EditorSession &session = workspace.current().session;
session.createDocument(100, 20);
session.insert(filled(Qt::white));
session.addAdjustment(AdjustmentKind::exposure);
const QUuid id = session.adjustmentEditingID().value();
// Asked for, not yet rendered: nothing to cancel, so refused.
QTest::ignoreMessage(QtWarningMsg, "Quit refused: the project is still busy");
DialogDesk desk;
QCOMPARE(answer(workspace), std::optional(false));
QVERIFY(desk.seen.isEmpty());
QVERIFY(session.adjustmentEditingID() == id && !session.adjustmentOriginal());
QVERIFY(awaited([&](std::function<void()> done) { session.beginAdjustmentEditing(id, std::move(done)); }));
// Once open, the editor is cancelled; the quit goes on.
QVERIFY(session.filterEdit() && session.adjustmentOriginal());
QVERIFY(awaited([&](std::function<void()> done) { workspace.settlePendingEdits(std::move(done)); }));
QVERIFY(!session.adjustmentEditingID() && !session.filterEdit() && workspace.canSwitch());
}
void QuitPendingEditsTests::aWorkspaceGoneMidSettleCallsNothing()
{
auto workspace = std::make_unique<ProjectWorkspace>();
+44 -13
View File
@@ -53,6 +53,23 @@ std::vector<int> gray(int level, double spacing, double glow = 0)
return column(scanlines(spacing, glow).apply(filled(16, 32, QColor(level, level, level))), 5);
}
// Worst difference from Swift's first glow, blurred whole.
int glowError(const QImage &image, double glow)
{
const QImage lines = scanlines(8).apply(image), result = scanlines(8, glow).apply(image);
const QImage blurred = PixelAdjust::gaussianBlur(lines, 8 * 3 + 3, true);
const float amount = float(glow / 100 * 2.5);
int worst = 0;
for (int y = 0; y < image.height(); ++y) {
for (int x = 0; x < image.width(); ++x) {
const int plain = lines.constScanLine(y)[x * 4], light = blurred.constScanLine(y)[x * 4];
const int expected = int(std::lround(std::min(255.0f, float(plain) + float(light) * amount)));
worst = std::max(worst, std::abs(result.constScanLine(y)[x * 4] - expected));
}
}
return worst;
}
std::vector<int> repeated(const std::vector<int> &band, int times)
{
std::vector<int> all;
@@ -113,6 +130,7 @@ private slots:
void everyBandOfATallImageIsDrawn();
void bandsDrawAsOneBandDoes();
void theGlowIsTheBlurredLightAtItsAmount();
void theGlowScalesBackExactlyOffTheStep();
void theGlowNeverPassesAlpha();
void theSheetShowsScanlinesRows();
void theRowsWriteTheirSettings();
@@ -302,21 +320,9 @@ void ScanlinesTests::theGlowIsTheBlurredLightAtItsAmount()
{
QImage image = filled(64, 160, Qt::black);
QPainter(&image).fillRect(QRect(0, 0, 32, 40), Qt::white);
const QImage lines = scanlines(8).apply(image);
// Swift's first glow: the lines blurred whole, sigma 27.
const QImage blurred = PixelAdjust::gaussianBlur(lines, 8 * 3 + 3, true);
for (const double glow : {20.0, 100.0}) {
const QImage result = scanlines(8, glow).apply(image);
const float amount = float(glow / 100 * 2.5);
int worst = 0;
for (int y = 0; y < 160; ++y) {
for (int x = 0; x < 64; ++x) {
const int plain = lines.constScanLine(y)[x * 4], light = blurred.constScanLine(y)[x * 4];
const int expected = int(std::lround(std::min(255.0f, float(plain) + float(light) * amount)));
worst = std::max(worst, std::abs(result.constScanLine(y)[x * 4] - expected));
}
}
// Three levels of the shrink's blur, times 2.5.
const int worst = glowError(image, glow);
QVERIFY2(worst <= 8, qPrintable(QString::number(worst)));
}
// Below the light, at amount 0.5, it fades.
@@ -324,6 +330,15 @@ void ScanlinesTests::theGlowIsTheBlurredLightAtItsAmount()
QVERIFY(fading[48] > 20 && fading[48] < fading[40] && fading[120] < 3);
}
void ScanlinesTests::theGlowScalesBackExactlyOffTheStep()
{
// 73 plus two 84-point pads is off the 6 step.
QImage image = filled(73, 73, Qt::black);
QPainter(&image).fillRect(QRect(0, 0, 40, 40), Qt::white);
const int worst = glowError(image, 100);
QVERIFY2(worst <= 8, qPrintable(QString::number(worst)));
}
void ScanlinesTests::theGlowNeverPassesAlpha()
{
QImage image = BrushRaster::context(16, 32, false);
@@ -346,6 +361,22 @@ void ScanlinesTests::theSheetShowsScanlinesRows()
"Glow|Light blooming around the lines, like a CRT's phosphors", "Dots|Break the lines into glowing beads",
"Wobble|Make the lines waver sideways down the screen, like a CRT losing sync",
"Density|More ink (darker) or less before dithering", "Contrast|", "Colors|"}));
// A value mid-range sits mid-travel; each unit is Swift's.
const std::tuple<const char *, double DitherSettings::*, double, QString> middles[] = {
{"lineSpacing", &DitherSettings::lineSpacing, 17, "px"}, {"glow", &DitherSettings::glow, 50, "%"},
{"dots", &DitherSettings::dots, 50, "%"}, {"wobble", &DitherSettings::wobble, 32, "px"}};
for (const auto &[name, setting, middle, unit] : middles) {
FilterSettings settings = shown.session.filterEdit().value().settings;
settings.dither.*setting = middle;
shown.session.updateFilter(settings, true);
QSlider *slider = shown.sheet->findChild<QSlider *>(QString::fromLatin1(name) + "Slider");
QLineEdit *field = shown.sheet->findChild<QLineEdit *>(QString::fromLatin1(name) + "Field");
QVERIFY2(slider && field, name);
QCOMPARE(slider->value(), slider->maximum() / 2);
QCOMPARE(slider->minimum(), 0);
const QList<QLabel *> labels = field->parentWidget()->findChildren<QLabel *>(QString(), Qt::FindDirectChildrenOnly);
QCOMPARE(labels.last()->text(), unit);
}
// Each slider's travel reaches its range's ends.
const std::tuple<const char *, double DitherSettings::*, double, double> sliders[] = {
{"lineSpacingSlider", &DitherSettings::lineSpacing, 2, 32}, {"glowSlider", &DitherSettings::glow, 0, 100},