diff --git a/AGENTS.md b/AGENTS.md index 29de3e3..7b5469e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -199,7 +199,7 @@ Platform adaptations, each forced by a missing Qt analog: - The Layers panel's gray (14.17, Swift 1.4.8: `06313a9`): `layersLookEditable` is Swift's: `canEditLayers` without what lasts only while the mouse is down (a stroke, a warp, a drag's transform edit, a pixel move) or a moment (`isProjectBusy`, read as `showsBusy`, which shows after 250 ms). The list's rows and their eyes (an effect's eye keeps `canEditLayers`, as Swift makes each effect row anew with it), the footer's buttons (new layer, folder, adjustments, delete with an active layer), the effects menu (with a layer that has pixels) and the mask button (one layer selected, without a mask) follow it, so nothing flashes gray for a click; every action still asks the session's own gate, which refuses meanwhile. The effects menu's folder term is left out as dead: a folder never holds pixels, so the pixel term refuses it. Tests: `LayersLookEditableTests` (each moment looks editable while the gate stays shut, each lasting state grays, the footer and list mid-stroke and under a pending transform, the effects menu's and mask button's own terms); the busy cases in `BlendModePickerTests`, `LayersPanelTests`, `NativeLayerListTests` and `LayerEffectRowTests` now wait for the indicator, and a click before it changes nothing. - Export As (14.18, Swift 1.4.8: `9287cf1`, `450af5f`, `b1db93d`): `ProjectController::exportAs(start, done)` replaces `exportJPEG`; `ExportFormat` (png, jpeg, pdf, Swift's raw values through `rawValue`, `allExportFormats`) starts the sheet, else the last format exported this run (`m_lastExportFormat`, PNG at first), and the File menu's Export JPEG… opens it on JPEG; Export As… (Ctrl+Alt+Shift+W, a new shortcut definition) on the last. The flow is the JPEG one: the render in a worker, the sheet, then the format's panel (Export PNG, Export JPEG or Export PDF, suggesting `.png`, `.jpeg` or `.pdf`), the write in a worker, one alert titled "Couldn’t export" for a failure past the render. `UI/ExportAsSheet` (Swift's sheet, renamed from the JPEG one): titled Export As; the format as three checkable buttons; the size as two `PickerField`s keeping the proportions (a number applies on Return or leaving, else both fields show the size again; a scrub or menu replaces their typing) and a scale menu of 25, 50, 75, 100 and 200%, every scale held between a hundredth and the side and surface limits; Quality for JPEG alone (saved only from a JPEG export), the page size in inches at the document's DPI for PDF alone, the background swatch ("Export Background") for JPEG and PDF; "Transparency kept · sRGB" for PNG, else "sRGB"; a change of format or size asks again only when the settings change, as Swift's `task(id:)`. The encoding resizes first (`ImageExporter::resized`: smooth, the same raster at the same size, `tooLarge` past the limits) and encodes by format (`pngData`; `jpeg`; `pdfData` over `flattened` for the preview). `JPEGPreview` takes the export's size beside its image (`setImage(image, pixels, updating)`), so Fit and 100% follow the chosen size, and draws Swift 1.4.8's checkerboard (20-point tiles of grays 0.30 and 0.35, anchored at the image) behind what it shows. Swift's PDF comes from `CGContext`'s PDF writer; `QPdfWriter` rounds a page to whole points, so `ImageExporter::pdfData` writes PDF 1.4 by hand: one page whose MediaBox is the print size in points (72 / resolution a pixel, 72 when none), the background as a filled rectangle when given, the pixels as a Flate RGB image with a gray soft mask, straight colour (adaptation; checked against poppler's `pdfinfo` and `pdftoppm`). A Flate stream that `qCompress` cannot make (it returns nothing when memory runs out) is `ExportError(render)`, never an empty stream. A zoom set before the first image (Ctrl+1 while the sheet still encodes) centres that image when it lands, as Swift's preview, made with its first result, centres on appearing. The page's inches round as Swift's formatter: its shortest digits, halves to even (45 pixels at 72 DPI are 0.62 in). A size field reads Swift's `Int` field (`typedInteger`, checked against Swift 6.0.3): a sign, decimal digits of any script read by code point, a fraction and an exponent (its leading zeros dropped), the number truncated toward zero and exact within Int64 (`1e3` is 1000, `12.7` is 12, `-9.223372036854775808e18` is Int64's least, `1e-400` is 0, `128e` is 128), grouping refused; known difference: ICU's lenient parse also reads a leading number before other text (`7abc` is 7, `1,000` is 1, `--5` is −5), where the twin puts the size back; and ICU rounds some long fractions before Swift truncates them (`199.99999999999999` is 200, `0.99999999999999999` is 1, while `9007199254740993.5` stays …993 and `12.99999999999999` stays 12), a rule the measured cases do not pin down, so the twin truncates the digits as typed. Encoding finds no memory as `ExportError(render)` at `encode`'s door, so Export As's PNG worker never meets a `bad_alloc`. Equivalent mutant: the parser's `point <= 0` as `< 0` (a point at zero leaves no digits, which read as 0). Tests: `ExportPDFTests` (the page, its size, the straight pixels and alpha, rows and channels, the background, a refusal, resizing, flattening), `ExportPDFFailureTests` (a binary of its own: a 360-pixel PDF, small enough that Qt converts it on the calling thread (a pool thread that cannot start hangs Qt 6.4's conversion), under limits from 64 KB to 4 MB throws or inflates whole, both reached, and a PNG likewise throws or matches the unstarved file), `ExportAsSheetTests` (formats and their rows and bytes, the size and its limits, the checkerboard, the page against Swift 6.0.3's formatter), `JPEGPreviewTests::aZoomBeforeTheFirstImageCentresIt`, `ProjectExportTests` (each format through the controller, the menu's Export As on the last format), the entry in `CompositorMenusTests` and `KeyboardShortcutsTests` (its chord, group and place). Equivalent mutants: none found. - Filter › Scanlines (14.19, Swift 1.4.9: `06c3a22`, `1187736`, `cdde327`): Scanlines leaves Dither for a filter of its own. `kernels/DitherPixels.c/.h` are `cdde327`'s, with 13.17's adaptation carried on: Swift's GCD blocks become static band functions with context structs (`scanlines_tone_band`, `line_scan_band`, `blur_band`, `spread_band`, `column_band`, `split_band`), each failure flag an `atomic_int`, every band through `dither_bands`; the arithmetic is Swift's word for word, and `dither_glow`'s new easing near white came with it. `ScanlinesTests::theKernelDrawsSwiftsOwnBytes` holds that: Swift's own file, built unchanged with clang's blocks and libdispatch in the `swift:6.0.3` image, drew five scenes (37 by 53 and 70 by 300, every setting, both directions of Displace, 32 bands of columns, lines and rows) and a glow, and the twin's bytes hash the same (FNV-1a). `Document/Scanlines.h/.cpp` twin Swift's file: `ScanlinesSettings` with Swift's fields, defaults (glow 0 now, thickness 70, smoothness 50) and clamps, Line Spacing and Color Split rounded, `apply` handing the kernel the normalized settings and Two Colors' bytes (black on white otherwise), then 13.17's `glowing` moved here unchanged. `DitherStyle` loses `scanlines` (`usesPixelSize` is ASCII alone, `drawsMarks` every style without tones) and `DitherSettings` its four fields. `FilterKind::scanlines` ("Scanlines") sits after Dither in Swift's order, so the Filter menu lists it there; it previews whole, as Dither does. Its two colours reuse Dither's picker (`openDitherColorPicker`, `setDitherColor`, Swift's), choosing the edit's own settings, and the titles drop "Dither" (`Color Picker (Light Color)`, `(Dark Color)`). The sheet's `scanlines` (`UI/FilterSheet+Scanlines.cpp`) is Swift's `scanlinesControls`: twelve rows with Swift's ranges, units and help, then the Colors menu and swatches, which `colorChoices` builds for Dither too (the screen's and the lines' colour help for Scanlines). Equivalent mutant: `glowing` run at glow 0 (it adds nothing). Tests: `ScanlinesTests` (Swift's eight, Swift's own bytes, each setting reaching its field, Two Colors rounded, every clamp through `FilterSettings` too, clear pixels, the eased glow against a full-size blur), `ScanlinesSheetTests` (the menu place and a commit, the sheet's rows, ranges and fields, both pickers), the order in `FilterModelTests`, `FinishingFilterTests`, `DitherSheetTests` and `CompositorMenusTests`, the acceptance run's Scanlines row through the menu. -- The Navigator (14.20, Swift 1.4.9: `6490bb3`, `3e09948`; `3c6cf5e`'s conflict resolved toward the minimap, which supersedes the panel): `Rendering/NavigatorGeometry.h/.cpp` twin Swift's file: `NavigatorGeometry` (the document fitted and centred in a box, `thumbnailRect`, `documentPoint` held to the document's edges), and `CanvasViewport::visibleDocumentRect` and `centerView`, declared in `CanvasViewport.h` and defined there, as Swift's extension; `centerView` goes through `translate`, so the view stops following Fit. Swift's `min` and `abs` in `visibleDocumentRect` are dead (the viewport never flips) and left out. `EditorSession::navigatorImage` (`EditorSession+Navigator.cpp`, its fragment `EditorSession+Navigator.h`) twins Swift's: the document drawn at the picture's size, never enlarged, each side rounded, a pixel at least, every layer and placed mask scaled by its axis, blur radius and motion distance by the width's, through a bare session's `drawLiveComposite` so an edit in progress stays out; a context that cannot be allocated, or a painted layer that cannot flatten while it draws, logs on `omaphoto.rendering` and gives no picture, where Swift's `try?` is silent (`NavigatorFailureTests`, a binary of its own: a 4000 by 3000 painted layer under 8 MB of room, the minimap shown without a picture). Swift's medium interpolation on the context is dead here (`LayerRenderer` sets each layer's own) and left out. `UI/NavigatorMinimap` twins Swift's view: a `QWidget` in `ContentView`'s canvas overlay (the mask badge's layout, its margins 12, top right), following the session's `changed()` itself: shown while View › Navigator holds (`navigator.visible` in `QSettings`, Swift's `AppStorage`, off at first), Canvas Only is off, a document is open and the zoom is 300% or more; hidden it drops its picture, key and wait, as SwiftUI drops the view. Its size is `sizeFor` (Swift's `size(for:)`: `QWidget::size` takes the name), fitted inside 126 by 90 and rounded; it fills gray 0.11, draws the 300-pixel picture smoothly over its whole box, the view's box in the accent 2.5 wide inside its rect (three points at least), clipped to a rounded rect of 4, then a 25% white rim. Swift's `task(id:)` is `RenderKey` (the document's id, size and layers and `brushRevision`, so a selection or guide redraws nothing): the first picture renders at once, each later key after a 250 ms single-shot precise `navigatorWait`. A left press or drag centres the view on the point (`centerView`, then `notify`), the arrow its cursor over the canvas's. Known difference: Swift's shadow (radius 6) has no twin, since a widget paints only inside itself. View › Navigator (300% and above) is a checkable entry before Pixel Grid that writes the setting and notifies; the command palette finds it. Tests: `NavigatorTests` (Swift's nine; the geometry's edge cases, both axes and a placed mask scaled, a layer and its mask off the origin, a streak shrunk at its edge, the wait restarted by a later edit, a transform in progress left out, the minimap's showing, waiting, clicks, cursor, box, gray and rim), `ViewMenuTests::theNavigatorEntryShowsTheMinimap` (the entry's place and words, the minimap 12 points in at the canvas's top right), the View menu's count in `CompositorMenusTests`. Equivalent mutants: none found. +- The Navigator (14.20, Swift 1.4.9: `6490bb3`, `3e09948`; `3c6cf5e`'s conflict resolved toward the minimap, which supersedes the panel): `Rendering/NavigatorGeometry.h/.cpp` twin Swift's file: `NavigatorGeometry` (the document fitted and centred in a box, `thumbnailRect`, `documentPoint` held to the document's edges), and `CanvasViewport::visibleDocumentRect` and `centerView`, declared in `CanvasViewport.h` and defined there, as Swift's extension; `centerView` goes through `translate`, so the view stops following Fit. Swift's `min` and `abs` in `visibleDocumentRect` are dead (the viewport never flips) and left out. `EditorSession::navigatorImage` (`EditorSession+Navigator.cpp`, its fragment `EditorSession+Navigator.h`) twins Swift's: the document drawn at the picture's size, never enlarged, each side rounded, a pixel at least, every layer and placed mask scaled by its axis, blur radius and motion distance by the width's, through a bare session's `drawLiveComposite` so an edit in progress stays out; a context that cannot be allocated, or a painted layer that cannot flatten while it draws, logs on `omaphoto.rendering` and gives no picture, where Swift's `try?` is silent (`NavigatorFailureTests`, a binary of its own: a 4000 by 3000 painted layer under 8 MB of room, the minimap shown without a picture). Swift's medium interpolation on the context is dead here (`LayerRenderer` sets each layer's own) and left out. `UI/NavigatorMinimap` twins Swift's view: a `QWidget` in `ContentView`'s canvas overlay (the mask badge's layout, its margins 12, top right), following the session's `changed()` itself: shown while View › Navigator holds (`navigator.visible` in `QSettings`, Swift's `AppStorage`, off at first), Canvas Only is off, a document is open and the zoom is 300% or more; hidden it drops its picture, key and wait, as SwiftUI drops the view. Its size is `sizeFor` (Swift's `size(for:)`: `QWidget::size` takes the name), fitted inside 126 by 90 and rounded; it fills gray 0.11, draws the 300-pixel picture smoothly over its whole box, the view's box in the accent 2.5 wide inside its rect (three points at least), clipped to a rounded rect of 4, then a 25% white rim. Swift's `task(id:)` is `RenderKey` (the document's id, size and layers and `brushRevision`, so a selection or guide redraws nothing): the first picture renders at once, each later key after a 250 ms single-shot precise `navigatorWait`. A left press or drag centres the view on the point (`centerView`, then `notify`), the arrow its cursor over the canvas's. Known difference: Swift's shadow (radius 6) has no twin, since a widget paints only inside itself. View › Navigator (300% and above) is a checkable entry before Pixel Grid that writes the setting and notifies; the command palette finds it. Tests: `NavigatorTests` (Swift's nine; the geometry's edge cases, both axes and a placed mask scaled, a layer and its mask off the origin, a streak shrunk at its edge, both blurs by the width's factor where the two factors differ, centring at a backing scale of two, the wait restarted by a later edit, a transform in progress left out, the minimap's showing, waiting, clicks, cursor, box, gray and rim), `ViewMenuTests::theNavigatorEntryShowsTheMinimap` (the entry's place and words, the minimap 12 points in at the canvas's top right), the View menu's count in `CompositorMenusTests`. Equivalent mutants: none found. - 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