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:
1 parent
81894f744a
commit
5c8e18c5af
7 files changed
+129
-18
No files matched your search
@@ -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}});
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
@@ -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},
|
||||
|
||||
Reference in new issue
Block a user