14.28 round 1: the cache, the job's brightness and the scope's bytes pinned

This commit is contained in:
ZacharyZhang-NY committed 2026-10-11 15:39:40 -04:00
1 parent 6dc0ffbfa3
commit 22bd52e8ae
6 files changed
+75 -1

No files matched your search

+1
View File
@@ -0,0 +1 @@
[(' if (m_cameraRawBrightness && m_cameraRawBrightness->first == previewSource.cacheKey())\n return m_cameraRawBrightness->second;\n', ''), (' job.cameraRawBrightness);', ' std::nullopt);')]
+1
View File
@@ -0,0 +1 @@
[(' const QImage pixels = image.format() == QImage::Format_RGBA8888_Premultiplied ? image : drawn(image);', ' const QImage pixels = drawn(image);')]
+1 -1
View File
@@ -206,7 +206,7 @@ Platform adaptations, each forced by a missing Qt analog:
- Camera Raw's Light and Color as Photoshop's (14.25, Swift 1.4.9: `57ab3bb`): `kernels/AdjustPixels.c/.h` were 57ab3bb's: Exposure, white balance and Contrast ran as one composed table before Highlights and Shadows, Whites, Blacks, Saturation and Vibrance as another after (one table since 14.26). `Document/CameraRawTables.h/.cpp` twin Swift's enum as a namespace: Photoshop's colour tables, 147 of 17³ (`size`), composed over a 33³ `grid`; `tables()` reads them once, `whiteBalance` (four tables round Temperature and Tint, 9 × 9 by 25), `stages` (Swift's order, sliders at zero left out), `compose` (empty without stages, the kernel then handed none; a grid that finds no memory is `ExportError(render)`) and `neutralize` (Swift's search: steps of 10, 2 and 0.5 round the best, the first of equals kept, counted by index where Swift strides, the same values). Swift's bundle resource is a Qt resource compiled into the library (`src/Resources/CameraRawTables.bin`, `qt_add_resources`), so it is always there: Swift's nil-table paths (no tables, no Light and Color, no neutral balance) have no twin, and a header or size that does not match is a `std::logic_error`, a broken build. Its planes were raw DEFLATE until 14.27 (xz since, below). `CameraRawSettings` loses `gains` and its three constants and `neutralizeLinear`; `neutralizeStraight` is the tables' search, and Auto hands it the linear mean encoded again (`encode`), as Swift's. The `Balance` is `CameraRawSettings::Balance`, where Swift's returns a tuple. Tests: Swift's three changed tests in `CameraRawTests` (exposure, translucent too, contrast, whites, blacks, saturation by Photoshop's numbers) and `whiteBalanceAndVibranceMatchPhotoshop`, 181 baked in `CameraRawSessionTests`, `CameraRawTablesTests` (the decoded tables' FNV hash and entries, stages and weights, each slider's tables, past the range, the composed corners, `neutralize`'s exact answers, refinements past −100 on either slider, each channel missing, ties among searched points, three stages composed in order, all from an independent Python decoder and search) (since 14.26 `CameraRawWiringTests::theTableReadsThePicturesBrightness`) and Auto through the encoded mean, a dark pixel on its straight segment.
- Adaptive Contrast, Highlights and Shadows; Dehaze, Texture, Clarity and Sharpening (14.26, Swift 1.4.9: `75147a2`): `kernels/AdjustPixels.c/.h` were 75147a2's and the tables its 285 (`CRT2`, 1.4 MB; 763 since 14.27): Contrast, Highlights and Shadows each blend four tables round their value (−100, −50, −25, 0, 25, 50, 100) and the picture's brightness (53, 80, 104, 130, 160, 190, 224 in sRGB levels, `between` over points, held at the ends, the lower pair taking a point whole), read before any stage by `camera_raw_statistics` (`CameraRawTables::brightness`, `Brightness`: the brightest channel's mean for Contrast, the log-average for Highlights, the luminance for Shadows), only when one of the three is set, else 128 each; every Light and Color slider is now one composed table (`stages` returns one list in Camera Raw's order) and `adjust_camera_raw` takes it alone. Dehaze removes or adds the haze colour of the brightest one percent and leaves one colour be; Texture and Clarity are guided filters at radii in proportion to the picture, so the preview's scale no longer reaches them (Glow's still does); Sharpening sharpens everywhere at Masking 0. These live in Swift's kernel, unchanged. The 14.16 tone curve is gone with it, and its curve test. Equivalent mutant: reading the brightness when none of the three is set (the stages then never read it). Tests: Swift's changed `CameraRawTests` (Highlights and Shadows by Photoshop's numbers, Dehaze on a shadow under a pale sky, one colour left be; pinned beside them: Highlights alone reading the picture, Highlights before Whites, a faint step sharpened at Masking 0) and `CameraRawToneOrderTests` (Highlights −100's white), `CameraRawTablesTests` (the 285 tables' hash and entries, two colours' three measures against Python, each slider's tables, the adaptive blends from an independent Python run, held ends and a measured point, three stages composed, mid gray's brightness), `CameraRawWiringTests::theTableReadsThePicturesBrightness` (the grade against the kernel through the picture's own brightness, not the default), `CameraRawRulesTests` (Glow reads the scale, Clarity no longer).
- Camera Raw's Color Mixer, Calibration, curve, vignettes and Color Grading as Photoshop's (14.27, Swift 1.4.9: `bdfb9c2`, `9dac4f1`, `7b5a592`, taken as their net change): `kernels/AdjustPixels.c/.h` were 7b5a592's (a stage holds up to eight tables, `adjust_camera_raw_curve_color` takes the Mixer's and Grading's composed tables, `adjust_camera_raw_optics` Exposure's tables for the lens vignette, the post-crop vignette darkens by a gain in linear light) and the tables its 763 (`CRT3`, 1.25 MB): a header of ten bytes (size, count, `step`), each entry a running difference in steps of `step` levels read as a signed byte and clamped, the planes an xz stream as Foundation's `.lzma` writes it, unpacked through liblzma (`lzma_stream_buffer_decode`, `LibLZMA::LibLZMA`; `liblzma-dev` in the dev image, `xz-devel` and `xz` in the Fedora and Arch images, `xz` in the PKGBUILD and the flake); the app no longer links zlib. `CameraRawTables` gains Swift's `blend` (up to eight parts; a null table is no change), `exposureTables`, `fifty` (−100 to 100 by 50), `gradingStages` (the four luminance sliders, then the global, midtone, highlight and shadow wheels: twelve hues by 30, saturations 25, 50 and 100 below which the lower is no change, the shadow and highlight wheels between Blending 0, 50 and 100, Balance as Swift's fitted shares of saturation, a wheel with no share left out) and `mixerStages` (luminance, saturation, hue, eight colours each); `stages` opens with Calibration's six primaries and ends with the parametric curve's four regions while `CameraRawCurveSettings::hasMeasuredDividers` holds (dividers 25, 50, 75 and Refine Saturation 0), when `toneTable` draws the point curve alone. `applyCurveColor` hands the kernel zeros for the Mixer's and Grading's formulas and their tables in their place, and their serializers (`mixerFloats`, `gradeFloats`), which Swift keeps without a caller, are gone; `applyCalibration` draws Shadows Tint alone; `apply` runs the Light table for Calibration or a measured curve too. Equivalent mutants: the shares' floor of zero (a negative share fails the `> 0` test as zero does) and `gradingStages`' `fmod` (a normalized hue lies within 0 to 360, where 360 wraps alike). Tests: Swift's `mixerCalibrationCurveAndVignettesMatchPhotoshop` in `CameraRawTests`; `CameraRawTablesTests` against an independent Python decoder of `CRT3` (the hash and entries; every earlier exact value redone on the stepped tables; the Mixer's, Calibration's, the curve's and Grading's tables and weights, a wheel wrapping past 330, Blending 0's own tables, Balance ±50 weighing each wheel's share and +100 leaving the shadows out, every Calibration binding, midtones' and highlights' luminance, Calibration, Light and the measured curve in order, moved dividers); the Highlights pins, Auto's dark pixel and the grading luminance by the same Python; `CameraRawCurveTests` (the tone table as measured and with dividers moved), `CameraRawLensTests` (Shadows Tint by the kernel, primaries through the table), `CameraRawRulesTests` (the clip views keep Calibration's table), `CameraRawWiringTests` (the curve call's tables).
- Camera Raw's preview 5 to 20 times faster (14.28, Swift 1.4.9: `a8630e4`): `kernels/AdjustPixels.c/.h` are a8630e4's, with 13.17's adaptation carried on: Swift threads its passes through GCD blocks (`in_bands` over `dispatch_apply`, and `dispatch_apply` alone for the statistics and the scope), which GCC cannot compile, so each block's body moved word for word into a static band function (`compose_band`, `compose_onto_band`, `camera_raw_band`, `blur_rows_band`, `blur_columns_band`, `effects_luma_band`, `effects_band`, `curve_color_band`, `detail_luma_band`, `sharpen_luma_band`, `sharpen_band`, `optics_band`, `calibration_band`, `statistics_band`, `scope_band`) whose context struct carries what the block captured, handed over as a compound literal, with Swift's split kept (one band under 64 items, else 32; 32 and 16 for the two sums); the bands run through `adjust_bands`, a C hook in `Document/ImageAdjustments.cpp` on `PoolMap::blocking`, as Dither's do. `CameraRawSettings::apply` takes Swift's known brightness (`std::optional<CameraRawTables::Brightness>`, read before Calibration now, measured there when none comes), composes the Light table always, and folds Curve, the Mixer and Grading into it (`foldingColor`: from `CameraRawTables::identity` when Light made none, the curves through `camera_raw_table_curves`, the Mixer's and Grading's stages through `camera_raw_compose_onto`) unless a point colour, Refine Saturation, a point-colour view, a clip view or the sharpen mask needs the curve pass. `CameraRawTables::Brightness` is declared in `CameraRaw.h`, reopening the namespace, since `apply`'s signature needs it and `CameraRawTables.h` holds the kernel's types, which only the core sees; `brightness(QImage)` draws the picture and measures it, and a picture that cannot be drawn logs on `omaphoto.rendering` and reads 128, where Swift's `try?` is silent. `FilterJob` carries `cameraRawBrightness`, which `previewJob` fills for Camera Raw from `FilterEdit::previewBrightness`, measured once and kept by the preview source's `cacheKey` (Swift keys it by `previewSourceVersion`, which the twin dropped in 13.8). `CameraRawScope::make` reads every other pixel each way through `camera_raw_scope`, straight from an `RGBA8888_Premultiplied` image's bytes, else drawn first; each band starts its own rows, so a short picture counts every row. Equivalent mutants: `foldingColor`'s `shape.adjusts()` (an identity curve leaves the table within a float's rounding), the cache's key (Camera Raw's preview source never changes while its panel is open), the band counts of the `in_bands` split (they only spread the work; the two sums' counts are not: one band reads only its own rows). Tests: `CameraRawFoldTests` (the fold against the kernel's own calls, from identity too; a point colour, Refine Saturation and a point-colour view keeping the pass; the brightness read before Shadows Tint and a known one used as handed; the session's job carrying the preview source's brightness and none for other kinds; every band of a forty-row ramp measured and counted; the calibration kernel's blue hue and saturation by their own effects), the scope's sampling and both formats in `CameraRawWiringTests`, the panel's ramp two pixels a level in `CameraRawPanelTests`.
- Camera Raw's preview 5 to 20 times faster (14.28, Swift 1.4.9: `a8630e4`): `kernels/AdjustPixels.c/.h` are a8630e4's, with 13.17's adaptation carried on: Swift threads its passes through GCD blocks (`in_bands` over `dispatch_apply`, and `dispatch_apply` alone for the statistics and the scope), which GCC cannot compile, so each block's body moved word for word into a static band function (`compose_band`, `compose_onto_band`, `camera_raw_band`, `blur_rows_band`, `blur_columns_band`, `effects_luma_band`, `effects_band`, `curve_color_band`, `detail_luma_band`, `sharpen_luma_band`, `sharpen_band`, `optics_band`, `calibration_band`, `statistics_band`, `scope_band`) whose context struct carries what the block captured, handed over as a compound literal, with Swift's split kept (one band under 64 items, else 32; 32 and 16 for the two sums); the bands run through `adjust_bands`, a C hook in `Document/ImageAdjustments.cpp` on `PoolMap::blocking`, as Dither's do. `CameraRawSettings::apply` takes Swift's known brightness (`std::optional<CameraRawTables::Brightness>`, read before Calibration now, measured there when none comes), composes the Light table always, and folds Curve, the Mixer and Grading into it (`foldingColor`: from `CameraRawTables::identity` when Light made none, the curves through `camera_raw_table_curves`, the Mixer's and Grading's stages through `camera_raw_compose_onto`) unless a point colour, Refine Saturation, a point-colour view, a clip view or the sharpen mask needs the curve pass. `CameraRawTables::Brightness` is declared in `CameraRaw.h`, reopening the namespace, since `apply`'s signature needs it and `CameraRawTables.h` holds the kernel's types, which only the core sees; `brightness(QImage)` draws the picture and measures it, and a picture that cannot be drawn logs on `omaphoto.rendering` and reads 128, where Swift's `try?` is silent. `FilterJob` carries `cameraRawBrightness`, which `previewJob` fills for Camera Raw from `FilterEdit::previewBrightness`, measured once and kept by the preview source's `cacheKey` (Swift keys it by `previewSourceVersion`, which the twin dropped in 13.8). `CameraRawScope::make` reads every other pixel each way through `camera_raw_scope`, straight from an `RGBA8888_Premultiplied` image's bytes, else drawn first; each band starts its own rows, so a short picture counts every row. Equivalent mutants: `foldingColor`'s `shape.adjusts()` (an identity curve leaves the table within a float's rounding), the cache's key (Camera Raw's preview source never changes while its panel is open), the band counts of the `in_bands` split (they only spread the work; the two sums' counts are not: one band reads only its own rows). Tests: `CameraRawFoldTests` (the fold against the kernel's own calls, from identity too; a point colour, Refine Saturation and a point-colour view keeping the pass; the brightness read before Shadows Tint and a known one used as handed; the session's job carrying the preview source's brightness and none for other kinds; every band of a forty-row ramp measured and counted; the calibration kernel's blue hue and saturation by their own effects; a job's brightness reaching the grade), `CameraRawFoldFailureTests` (a binary of its own, under `AddressSpaceLimit`: a measured preview source not drawn again, a premultiplied scope read in place), the scope's sampling and both formats in `CameraRawWiringTests`, the panel's ramp two pixels a level in `CameraRawPanelTests`.
- Drops between projects: `ProjectWorkspace::draggedLayerSource` finds the tab whose document holds the dragged id (Swift reads the drag pasteboard; the twin reads the drag's `QMimeData`, which every drop target has), `canReceiveDrag(data, destination)` refuses only a layer's own tab, and `receiveProviders` copies several ids one after another, as Swift awaits each provider, so a two-row drop on the New button opens two tabs as Swift's does. `ContentView` takes such a drop over its whole area (`setWorkspace` hands it the workspace; `acceptsDrop` keeps Swift's guards: busy, the sheet, the importer, a rename; `levels` arrives with 9.x), shows the 3-point accent ring inside the canvas while targeted and hands the drop's document point on when it fell on the canvas. `ProjectTabButton` takes drops for its tab (accent fill and rim, "Add to <title>"), and `NewCanvasButton`, the toolbar's New canvas button with the action as its default, for a fresh tab ("Open in a new project tab"), both refusing an Alt drag, which duplicates in the panel; Swift's transient `NewTabDropSlot` in the strip has no twin, the New canvas button is `NewProjectDropTarget` already. Files and pictures arrived with 10.4.
- `CanvasThumbnail` draws a layer or a mask inside the canvas's box at `backingScale` 2 (the panel's pixmaps carry that ratio); a mask's ground is `LayerMask::background`, white or black by its edge's majority, as the canvas treats it (Swift's `edgeTone` mean went with 12.12b). `LayerIcons` draws the panel's SF Symbols as paths, in one palette colour, like `ToolIcons`. `BlendModePicker` is a `QComboBox` whose popup previews the highlighted mode through `previewBlendMode` for the layer it opened on (Qt calls `hidePopup` before `activated`, so the preview, the target and the highlight are cleared from the event loop, after a choice had its chance) and refuses a choice once another layer is active. `LayerAppearanceControls` is Swift's blend and opacity row: the slider's press and release are one opacity drag (the release is watched application-wide while the slider is down, so a popup opened mid-drag cannot keep the step open), the percent field applies once through its `FocusOut`, which Return and Escape cause (Swift's `releasesFocusOnCommit`), and steps with Up and Down (Swift's `isFinite` test and the steps' clamp are left out: `setLayerOpacity` refuses a value that is no finite number and clamps the rest), and another active layer makes the controls anew, as Swift's `.id` does.
- `ProjectWorkspaceView` is the `QMainWindow`: the front tab's `ContentView` as its central widget, made anew when another tab comes to the front, as SwiftUI's `.id` rebuilds it, and disabled while the workspace manages; the window holds the shown tab (`std::shared_ptr`) until the old editor is deleted, as Swift's views hold their session, so a replaced or closed tab's session never ends under its editor (`ProjectWorkspace::tab` is public for this); a `QToolBar` with the New canvas action, the `ProjectTabStrip` (which takes the toolbar's free width) and the four zoom actions, since SwiftUI's `.toolbar` modifier lives in `ContentView` but the toolbar belongs to the window. Its actions follow the front session's `changed()`, and on that signal the window syncs every tab's button as well, because a busy front tab dims the other tabs' buttons, which their own sessions' signals do not reach; the strip syncs its buttons on the workspace's signal, each button on its own session's. The window's title is the front tab's, with Qt's `[*]` placeholder, so `setWindowModified` can show the mark that `isDocumentEdited` shows; `setWindowFilePath` carries `representedURL`. The editor's own minimum size is cleared inside the window, which owns the 800 × 520 minimum for the whole, toolbar included. Closing the window ignores the event and asks the workspace (`closeWindow`), which confirms tab by tab and then closes the window again with the property `closeConfirmed` set, since the workspace may not include `UI/` headers; a confirmed close is accepted and the mark cleared, so a window shown again asks again. `ProjectTabStrip` is a plain `QWidget` whose buttons are kept for the tabs that stay, made for new tabs and deleted for tabs that went, and placed by hand from `projectTabOverflow` (13.14): every button at its own size hint, so no title is squeezed below Swift's 35 points, laid out again when the strip is resized, the workspace changes or any tab's session changes; it fills no background, so the toolbar shows through (a stylesheet would do the same but freezes the widgets' palette, so a theme switch would pass them by); the unsaved dot is a character before the title. Files named on the command line go to `ProjectWorkspace::receive`, as Finder's Open With reaches the delegate. Layer drops on tabs and the New canvas button are 4.7's; file and picture drops arrived with 10.4.
+58
View File
@@ -0,0 +1,58 @@
#include "AddressSpaceLimit.h"
#include "Document/BrushStroke.h"
#include "Document/EditorSession.h"
#include "Document/Filters.h"
#include <QtTest>
#include <malloc.h>
// What a8630e4 spares: a second measure, a second picture.
class CameraRawFoldFailureTests : public QObject {
Q_OBJECT
private slots:
void aMeasuredSourceIsNotDrawnAgain();
void theScopeReadsPremultipliedBytesInPlace();
};
void CameraRawFoldFailureTests::aMeasuredSourceIsNotDrawnAgain()
{
QImage gray = BrushRaster::context(2048, 2048, false);
gray.fill(QColor(64, 64, 64));
EditorSession session;
session.createDocument(2048, 2048);
session.insert(ImportedImage(gray, gray.scaled(96, 96), QStringLiteral("Gray")));
session.beginFilter(FilterKind::cameraRaw);
const FilterEdit &edit = session.filterEdit().value();
QCOMPARE(edit.previewSource.size(), QSize(2048, 2048));
QVERIFY(std::abs(edit.previewBrightness().brightest - 64) < 0.01);
double again = 0;
{
// No room for a 16 MB redraw: the reading stays.
const AddressSpaceLimit limit(2ll << 20);
again = edit.previewBrightness().brightest;
}
QVERIFY(std::abs(again - 64) < 0.01);
}
void CameraRawFoldFailureTests::theScopeReadsPremultipliedBytesInPlace()
{
QImage gray = BrushRaster::context(4096, 4096, false);
gray.fill(QColor(64, 64, 64));
std::optional<CameraRawScope> scope;
{
// A 64 MB copy cannot fit; bytes read in place.
const AddressSpaceLimit limit(2ll << 20);
scope = CameraRawScope::make(gray);
}
QCOMPARE(scope.value().red[64], 2048.0 * 2048.0);
}
int main(int argc, char **argv)
{
// Large asks map memory the limit counts.
mallopt(M_MMAP_THRESHOLD, 64 * 1024);
QApplication app(argc, argv);
CameraRawFoldFailureTests tests;
return QTest::qExec(&tests, argc, argv);
}
#include "CameraRawFoldFailureTests.moc"
+13
View File
@@ -14,6 +14,7 @@ private slots:
void aPointColourOrRefineSaturationKeepsTheCurvePass();
void aKnownBrightnessIsReadBeforeCalibration();
void aSessionMeasuresItsPreviewSourceOnce();
void aJobsBrightnessReachesTheGrade();
void everyBandIsMeasuredAndCounted();
void calibrationsBlueHueTurnsAndItsSaturationSaturates();
};
@@ -158,6 +159,18 @@ void CameraRawFoldTests::aSessionMeasuresItsPreviewSourceOnce()
QVERIFY(!session.filterEdit().value().previewJob().cameraRawBrightness);
}
// A job's brightness is the one the grade reads.
void CameraRawFoldTests::aJobsBrightnessReachesTheGrade()
{
const QImage gray = image(4, 4, 128 / 255.0, 128 / 255.0, 128 / 255.0);
FilterSettings settings;
settings.cameraRaw.shadows = 100;
FilterJob job{FilterKind::cameraRaw, gray, settings, 1, std::nullopt, QTransform()};
job.cameraRawBrightness = CameraRawTables::Brightness{60, 60, 60};
const QImage handed = settings.cameraRaw.apply(gray, std::nullopt, 1, 0, -1, false, CameraRawTables::Brightness{60, 60, 60});
QVERIFY(pixels(PixelFilter::run(job)) == pixels(handed) && pixels(handed) != pixels(settings.cameraRaw.apply(gray)));
}
// Forty rows of gray: every band's rows reach the sums.
void CameraRawFoldTests::everyBandIsMeasuredAndCounted()
{
+1
View File
@@ -317,6 +317,7 @@ compositor_test(ColorAdjustmentTests)
compositor_test(CameraRawTests)
compositor_test(CameraRawTablesTests)
compositor_test(CameraRawFoldTests)
compositor_test(CameraRawFoldFailureTests)
target_include_directories(CameraRawFoldTests PRIVATE ${OMAPHOTO_KERNELS})
target_include_directories(CameraRawTablesTests PRIVATE ${OMAPHOTO_KERNELS})
compositor_test(CameraRawColorNoiseTests)