14.25 round 1: the search's edges and ties, composition order, a dark Auto
This commit is contained in:
3 files changed
+27
-1
No files matched your search
@@ -203,7 +203,7 @@ Platform adaptations, each forced by a missing Qt analog:
|
||||
- Effects, text and shapes kept (14.22, Swift 1.4.9: `94e90bd`): `ImageLayer(record, asset, mask)` loads its live shape and text (`LayerShape::loaded`, `LayerText::loaded`), Swift's `ProjectSnapshot.documentLayers`, so `installProject` and `applyDocumentSize` (Canvas Size, Image Size, Crop, Trim) build the same layers; the canvas resizer carries effects, and Image Size scales them by the root of its two factors through `LayerEffects::scaled` (stroke, glows and blurs to 500, distances to 5,000, Photoshop's Scale Styles) while its resampling still flattens text and shapes, and a size that changes only the resolution keeps all three. Hue/Saturation, Levels, Invert and a floating merge keep the layer's effects. `docs/project-format.md` says so. Tests: `LayerEffectsSurviveTests` (Swift's five sizing cases; a stretch's mean factor and the ranges), `LayerEffectsTests::rebuildsAndResizesKeepThem` (the four rebuilds, both resizers), the shape and text halves in `ShapeLayerTests` and `TypeStoreTests`.
|
||||
- WebP (14.23, Swift 1.4.9: `5724099`): `ImageImporter` takes Qt's `webp` format beside JPEG, PNG and TIFF (`qt6-image-formats-plugins` carries it, as it carries TIFF; Fedora's `qt6-qtimageformats`, Arch's `qt6-imageformats` and nixpkgs' `qtimageformats` too), through the same reading, sRGB conversion and thumbnail; a dropped picture prefers WebP after TIFF and before Photoshop's, Swift's order; the unsupported and drop messages name WebP; the importer's filter offers `*.webp` and the desktop entry `image/webp`. Tests: `WebPImportTests` (Swift's lossless and lossy pictures: size, sRGB, the clear half, the budget, a drop opening a document; the drop order), the words in their files.
|
||||
- Ctrl or Meta with a middle drag zooms (14.24, Swift 1.4.9: `e03923f`, Blender's gesture): a middle press with Ctrl or Meta (Swift's Command or Control) keeps `m_middleZoom` (where it began, the zoom then) in place of the middle pan; each move with the middle button zooms by 2^((start.y − y) / 100) about the press, as the Zoom tool's drag doubles every 100 points, up in and down out, under the Zoom tool's magnifier, the minus while below the press; the brush circle hides meanwhile; the release, or a move without the middle button (Qt can lose a release), ends it and puts the tool's cursor back, as the pan's does. A left drag beside it goes on, and a right-button tip drag waits, as beside the pan. Tests: `CanvasNavigationTests::ctrlOrMetaWithTheMiddleButtonZooms` (doubling and halving about an off-centre press, from 75% too, both cursors against the Zoom tool's, Meta, a lost release, a plain middle drag still panning, a Hand drag going on and a tip drag waiting beside it).
|
||||
- Camera Raw's Light and Color as Photoshop's (14.25, Swift 1.4.9: `57ab3bb`): `kernels/AdjustPixels.c/.h` are 57ab3bb's, unchanged: Exposure, white balance and Contrast run as one composed table before Highlights and Shadows, Whites, Blacks, Saturation and Vibrance as another after. `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 are raw DEFLATE, as Foundation's `.zlib` writes them, inflated through zlib (`ZLIB::ZLIB`, which the PKGBUILD and the flake now name). `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. Equivalent mutant: `neutralize`'s range test (a point past ±100 reads the table at the end, already visited before it with the same cast, so it never wins strictly). Tests: Swift's three changed tests in `CameraRawTests` (exposure, 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 and its first-of-ties, all from an independent Python decoder and search), `CameraRawWiringTests::theTablesRunAroundHighlightsAndShadows` (the order against the kernel itself) and Auto through the encoded mean.
|
||||
- Camera Raw's Light and Color as Photoshop's (14.25, Swift 1.4.9: `57ab3bb`): `kernels/AdjustPixels.c/.h` are 57ab3bb's, unchanged: Exposure, white balance and Contrast run as one composed table before Highlights and Shadows, Whites, Blacks, Saturation and Vibrance as another after. `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 are raw DEFLATE, as Foundation's `.zlib` writes them, inflated through zlib (`ZLIB::ZLIB`, which the PKGBUILD and the flake now name). `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, 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, a refinement past −100 and ties among searched points, three stages composed in order, all from an independent Python decoder and search), `CameraRawWiringTests::theTablesRunAroundHighlightsAndShadows` (the order against the kernel itself) and Auto through the encoded mean, a dark pixel on its straight segment.
|
||||
- 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.
|
||||
|
||||
@@ -101,6 +101,12 @@ void CameraRawTablesTests::neutralizeFindsTheGridsGrayest()
|
||||
// White stays white anywhere: the first of the ties wins.
|
||||
const CameraRawSettings::Balance white = CameraRawTables::neutralize(1, 1, 1).value();
|
||||
QVERIFY(white.temperature == 0 && white.tint == 0);
|
||||
// A refinement past −100 keeps to the range.
|
||||
const CameraRawSettings::Balance edge = CameraRawTables::neutralize(84 / 255.0, 1 / 255.0, 97 / 255.0).value();
|
||||
QVERIFY(edge.temperature == -14.5 && edge.tint == -100);
|
||||
// Ties among searched points: tint rising within each temperature.
|
||||
const CameraRawSettings::Balance tie = CameraRawTables::neutralize(144 / 255.0, 216 / 255.0, 47 / 255.0).value();
|
||||
QVERIFY(tie.temperature == -100 && tie.tint == 60);
|
||||
QVERIFY(!CameraRawTables::neutralize(0.5, 0.0001, 0.5));
|
||||
QVERIFY(CameraRawTables::neutralize(0.5, 0.00011, 0.5));
|
||||
}
|
||||
@@ -115,6 +121,21 @@ void CameraRawTablesTests::identitySlidersComposeNothing()
|
||||
QCOMPARE(composed.size(), size_t(33) * 33 * 33 * 3);
|
||||
// The corners are its table's own black and white.
|
||||
QVERIFY(composed[0] == entry(92, 0)[0] / 255.0f && composed.back() == entry(92, 4912)[2] / 255.0f);
|
||||
// Three stages in their order, against Python's own run.
|
||||
CameraRawSettings three;
|
||||
three.exposure = 1;
|
||||
three.temperature = 40;
|
||||
three.contrast = 50;
|
||||
const std::vector<float> run = CameraRawTables::compose(CameraRawTables::stages(three).before);
|
||||
const auto at = [&](int r, int g, int b) {
|
||||
const size_t index = ((size_t(r) * 33 + g) * 33 + b) * 3;
|
||||
return std::array<float, 3>{run[index], run[index + 1], run[index + 2]};
|
||||
};
|
||||
const auto near = [](std::array<float, 3> got, std::array<float, 3> expected) {
|
||||
return std::abs(got[0] - expected[0]) < 1e-6f && std::abs(got[1] - expected[1]) < 1e-6f && std::abs(got[2] - expected[2]) < 1e-6f;
|
||||
};
|
||||
QVERIFY(near(at(16, 16, 16), {0.94074458f, 0.85085034f, 0.72421813f}));
|
||||
QVERIFY(near(at(16, 4, 28), {0.85322261f, 0.50834316f, 0.98791242f}));
|
||||
}
|
||||
|
||||
QTEST_GUILESS_MAIN(CameraRawTablesTests)
|
||||
|
||||
@@ -128,6 +128,11 @@ void CameraRawWiringTests::autoAveragesLinearLight()
|
||||
const CameraRawSettings::Balance automatic = CameraRawSettings::autoBalance(picture).value();
|
||||
QVERIFY(std::abs(automatic.temperature - linear.temperature) < 1e-9 && std::abs(automatic.tint - linear.tint) < 1e-9);
|
||||
QVERIFY(std::abs(automatic.temperature - encoded.temperature) > 1);
|
||||
// A dark pixel takes sRGB's straight segment.
|
||||
QImage dark = BrushRaster::context(1, 1, false);
|
||||
dark.setPixelColor(0, 0, QColor(3, 5, 8));
|
||||
const CameraRawSettings::Balance shade = CameraRawSettings::autoBalance(dark).value();
|
||||
QVERIFY(shade.temperature == 50.5 && shade.tint == 20);
|
||||
}
|
||||
|
||||
void CameraRawWiringTests::eachEyeClearsEveryFieldOfItsGroup()
|
||||
|
||||
Reference in new issue
Block a user