Commit Graph
8442 Commits
Author SHA1 Message Date
David Mann c0cfcb4e38 Merge #10941: Crop: ⇧-corner drag follows the frame's diagonal (#10871) 2026-11-03 00:30:49 +01:00
David Mann b8bcc02cdb Merge #10905: Crop with Delete Cropped Pixels off turns the Background into Layer 0 (#10867) 2026-11-03 00:30:48 +01:00
David Mann 79ffb5711a Merge #10752: Layers panel: show the filter row in every theme 2026-11-03 00:30:45 +01:00
ljkx 6dd5156e5f Crop: the Shift-corner unit test expects the diagonal size (#10871) 2026-11-02 23:21:59 +01:00
aiongg b957878f3f Merge pull request #10776 from davidmann4/integration-batch-12
Integration batch 12: 14 Actions PRs on main
2026-11-02 15:13:40 -05:00
Psychotoxical 0d27191cb7 Layers panel: keep filtering while the filter row is hidden
Filter Options › Hide hides the row but keeps the filter applied, as in the
reference application (#10876, #10877). This PR now only draws the row in
every theme.
2026-11-02 21:07:53 +01:00
David Mann 673bfb4d9c Merge remote-tracking branch 'origin/main' into integration-batch-12
# Conflicts:
#	docs/file-format-parity.md
2026-11-02 20:15:23 +01:00
ljkx 61c72186f6 Crop: a Shift-corner drag follows the frame's diagonal (#10871)
With a free ratio, Shift on a crop corner kept the proportions by letting
the larger relative extent win, so (-100, -50) on an 800 x 500 frame gave
720 x 450. Photoshop projects the pointer onto the frame's diagonal through
the dragged corner and gives 706 x 441. Do the same, with and without Auto
Center Preview and on a turned frame. A ratio preset, Alt+Shift and edge
handles keep today's rule.
2026-11-02 20:03:32 +01:00
FredApps f8e393fdb6 Merge pull request #6266 from danielmigueltejedor/fix/gnome-window-decorations
Enable font-free GNOME/Wayland controls for system title bars
2026-11-02 19:59:41 +01:00
9034223c4b TIFF: open YCbCr images as RGB (#10316) (#10319)
* TIFF: open YCbCr images as RGB (#10316)

Scanners often write JPEG-compressed YCbCr TIFFs with 2x2 chroma
subsampling. The tiff crate decodes and upsamples them but returns raw
YCbCr samples, which the decoder rejected with "colour type YCbCr(8)".

The new tiff/ycbcr.rs converts them to RGB after decoding: JPEG data
uses the JFIF full-range BT.601 conversion (as libtiff does), other
compressions honour ReferenceBlackWhite and YCbCrCoefficients with the
TIFF defaults as fallback. 8 and 16-bit. YCbCr with extra samples, or
in other sample formats, fails with a clear unsupported error.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Contributors: add Yuki-init's display name

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* TIFF YCbCr: test that YCbCrCoefficients set the luma weights

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* TIFF: YCbCr tags with the wrong value count use the defaults

tag_f64s now returns None unless the tag holds exactly n values, so a
YCbCrCoefficients or ReferenceBlackWhite tag with extra values falls
back to the TIFF defaults instead of being truncated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lrgj5pnShYtsSnrsZq5iRn

* TIFF YCbCr: tests for tiled JPEG with ICC, big-endian 16-bit and planar

Covers the layouts the review asked for: a tiled 4:2:0 JPEG file with an
embedded profile (checked against an independent JPEG decoder), a
big-endian 16-bit file, and planar data in both byte orders.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lrgj5pnShYtsSnrsZq5iRn

* TIFF YCbCr: cite the formula sources and pin the green-channel choice

Documents where the conversion comes from: TIFF 6.0 Section 21
(YCbCrCoefficients, pp. 90-91) and libtiff's tif_color.c
(TIFFYCbCrToRGBInit, TIFFYCbCrtoRGB). Red and blue follow the spec;
green follows libtiff and adds Y unscaled, which matches the spec only
when the luma weights sum to 1. The module doc records this as a
deliberate compatibility choice, and a new test pins it: with weights
0.25/0.25/0.25, mid gray stays (128, 128, 128).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lrgj5pnShYtsSnrsZq5iRn

* Codecs README: YCbCr TIFFs with three 8- or 16-bit samples decode to RGB

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lrgj5pnShYtsSnrsZq5iRn

* Format tiff_ycbcr tests with rustfmt

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(tiff): compare grouped YCbCr decoding and update support notes

* style(tiff): align grouped decode call with test formatting

* style(tiff): apply repository rustfmt to grouped YCbCr regression

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com>
Co-authored-by: FredApps <FredApps@users.noreply.github.com>
2026-11-02 19:31:22 +01:00
FredApps 9236d27eb6 Merge current main into fix/gnome-window-decorations 2026-11-02 19:10:43 +01:00
ljkx 945cf2218d Crop with Delete Cropped Pixels off turns the Background into Layer 0 (#10867)
A crop with Delete Cropped Pixels off now turns the Background into a
normal layer named "Layer 0", as in Photoshop, within the crop's one
history step: straight, extending and turned crops, and crops with a
target W x H x Resolution. The pixels past the canvas stay on every
layer, and the area an extending crop adds is transparent on every
layer instead of taking the background colour. A turned crop converts
the Background before the turn, so it turns like the other layers and
keeps its overhang.

Delete Cropped Pixels on is unchanged. Image > Reveal All keeps its
current result (the Background stays, its extension takes the
background colour) by calling the inner crop with a keep-Background
flag. Content-Aware crop fill with Delete off was not measured and is
left as it is.

Adds crates/engine/tests/it/crop_delete_off_background.rs. The #7086
test now locks the cropped layer back into a Background, the state
documents saved before this change still hold.
2026-11-02 18:29:48 +01:00
David MannandClaude Opus 5.5 43fab54950 Integration: the SVG clipboard service doc says ordinary paste reads it too; the bulk-load test checks the sets that stay loaded (#10704, #10601)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 17:56:07 +01:00
.CooCoo 289dd0a874 Format: take the .pcraft envelope from craft-pcraft-format (#10724)
* Format: take the .pcraft envelope from craft-pcraft-format

The manifest envelope, TileRef, Hash, FORMAT_VERSION, EXTENSION, the
entry names, tile_path and blob_path, is_valid_hash, the manifest
nesting and item checks and the migration runner now come from
craft-libs' craft-pcraft-format. photocraft-format re-exports them, so
callers do not change. Manifest is craft_pcraft_format::Manifest<DocM>.

Saved bundles keep the same bytes. A save still checks only the nesting
and the item count of its manifest, not its size, and the manifest
nesting limit is still computed from photocraft_doc::MAX_GROUP_DEPTH.

Tests: a save, load and save again gives the same bytes in every color
mode and depth, with and without previews; MAX_GROUP_DEPTH equals
craft_pcraft_format::MAX_GROUP_DEPTH.

* Format: pin craft-pcraft-format from storytold/craft-libs

* Format: read manifests with craft-pcraft-format's read_manifest_with

Pins craft-pcraft-format to dca870a0, the head of craft-libs #407.
Loading reads the manifest with read_manifest_with, which runs the
limit check, the version check, the migration steps and the envelope
read in one path; the layers are still decoded one at a time after the
rest of the document. A save builds the manifest with Manifest::new and
tile references with TileRef::new, and the bundle's directory names and
the .zst suffix come from the crate's constants. Saved bytes and error
messages are unchanged.

* Format: pin craft-pcraft-format to craft-libs #407's head 04dd1bd

04dd1bd changes only the crate's docs and a test, so photocraft-format
builds the same code.

* Format: pin craft-pcraft-format to craft-libs main at b8f7d93

craft-libs #407 merged as b8f7d93. Its read_manifest also refuses a
thumbnail other than thumb.png and a composite other than
composite/preview.png, the only names photocraft-format writes.
2026-11-02 11:43:46 -05:00
David MannandClaude Opus 5.5 379fcd81ac Integration: collapse the Paste As Pixels fallback condition (clippy collapsible_if) (#10704)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 17:11:58 +01:00
David MannandClaude Opus 5.5 46b3302cdd Integration: test that the multi-file Load Actions summary counts a surviving set's unsupported steps (#10601)
Found by CodeRabbit on #10776.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 16:49:54 +01:00
David MannandClaude Opus 5.5 c01d82198a Integration: a Background selection keeps its modifier, and Move needs a layer destination (#10702, #10753)
A Cmd-click adding the Background to the selection loaded as a plain select that replaced
the selection; a Move to an ordinal of a channel loaded as a layer arrange. Found by
CodeRabbit on #10776.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 16:47:11 +01:00
David MannandClaude Opus 5.5 f79a169651 Integration: one Actions row and the batch's event total in the file format parity page
The doc merge left five Actions rows that disagreed (version 12 unsupported vs read; 120 vs
127 events). Found by CodeRabbit on #10776.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 16:42:36 +01:00
David MannandClaude Opus 5.5 f6e3eccab7 Integration: Paste As keeps a newer internal copy over an unchanged SVG, and Pixels falls back to the bitmap (#10704)
An ordinary paste now remembers the SVG next to the bitmap it took, and a clipboard that no
longer holds the remembered SVG forgets it, so Paste As neither replaces a newer internal copy
with the old SVG nor ignores the same SVG copied again later. Paste As Pixels reads the bitmap
when the SVG read fails. Found by CodeRabbit on #10776.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 16:42:36 +01:00
David MannandClaude Opus 5.5 50fd920b3b Integration: a gallery effect's own step keeps its panel name bytes on save (#10632)
The remembered name was the Latin-1 reading of the bytes, so a GBK or Shift_JIS name was
written back garbled. Found by CodeRabbit on #10776.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 16:33:29 +01:00
David MannandClaude Opus 5.5 c03c97222b Integration: Trap, Match Color, Custom and gallery steps that record a key twice stay kept steps (#10630, #10631, #10632)
The first value played and saved, the other was dropped. Same rule as the Group I and
Distort mappers' `setting`. Found by CodeRabbit on #10776.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 16:30:02 +01:00
David MannandClaude Opus 5.5 c31ef9b683 Integration: edit.paste's "as":"path" is a vector path, not a host file, for the automation registry check (#10704)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 14:57:08 +01:00
David MannandClaude Opus 5.5 e155cf85bf Integration: batch tests read composed pixels with Buffer::into_px after main's #10514 (#10704, #10618)
Main's buffer reuse (#10514) gives Buffer a Drop impl, so moving .px out of it no longer compiles.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 14:09:34 +01:00
David Mann 4c95a2d4bd Merge main 5d9c57e60d into integration batch 12 2026-11-02 13:54:27 +01:00
David MannandClaude Opus 5.5 027d191312 Integration: the dither test reads pixels with as_chunks (clippy chunks_exact_to_as_chunks) (#10704)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 13:34:57 +01:00
David MannandClaude Opus 5.5 8caea149c1 Integration: regenerate docs/atn.md for the batch (144 supported events, settings each PR keeps)
Supported table read from the batch's events.rs. The PRs' own sections (settings and command
options retained, Render defaults, steps kept disabled) are kept; the known-gaps tables that
#10702 and #10704 close are gone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 13:03:44 +01:00
David MannandClaude Opus 5.5 218a84b801 Merge PR #10704: Actions: restore command options for ATN playback
With our fix-up 8a0f959d29 on top of the author's head 072a87e7b0.
Conflicts with the batch resolved: Image Size keeps #10702's point sizes and
writes `scaleStyles` only when true (no key and false play the same in the
reference application, so recorded steps save back unchanged); Convert to
Profile takes #10704's options; new tests carry #10743's `encoding` field;
#10702's absolute-intent test expects the new Convert to Profile flags.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 12:57:10 +01:00
David MannandClaude Opus 5.5 568bf40943 Integration: a Filter Gallery effect's recorded event exports again next to #10628's mask and merge events (#10632)
#10628 refuses an `_atnEvent` that is not a string; #10632 stores the effect's own event
there as an object, so every gallery step loaded from its own event failed Save Actions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 12:51:07 +01:00
Khawar Zamman Wani 5d9c57e60d Linux: return memory of closed documents to the system (#8743) (#10515)
* perf: reuse large buffers so a pinned glibc trim threshold costs no speed (#10479)

With MALLOC_TRIM_THRESHOLD_ set (#8743), glibc maps every allocation over
128 KiB fresh, so tiles, compositor bands and filter scratch were page-faulted
in on every call: viewport compose 3.6x slower, Gaussian Blur up to 1.8x,
.pcraft save 2.4x.

- photocraft_raster::scratch: a process-wide, capped pool of large Vec
  buffers (u8/u16/f32/[f32; 4]). A miss drops the oldest buffers worth its
  size down to a 256 MiB floor, so peak memory stays near glibc's defaults.
  The engine releases the pool after any command that closes a document,
  Headless::close does too, and an idle thread releases it after 2 s unused.
- Tiles, compose Buffer/masks/bands, algo Image and the blur paths, engine
  adjustment/filter scratch, GPU tile upload and io import buffers go through
  the pool.
- .pcraft save reuses one zstd FrameCompressor per worker (byte-identical).
- interactive_bench reports minor page faults per run.

36 MP, trim threshold set vs glibc defaults: viewport 5.6 vs 6.1 ms (was
22.7), Blur r4 445 vs 437 ms (was 852), Curves CPU proxy 35.0 vs 35.7 ms
(was 50), flatten 146 vs 147 ms (was 237), save .pcraft 251 vs 230 ms (was
545). RSS after closing a 5400x7200 document: 21 MB with trim set.

* fix(linux): restart once with glibc's trim threshold set so closed documents return memory (#8743)

With glibc's default malloc, the memory of a closed document stays with the
process: freeing a large buffer raises glibc's dynamic mmap threshold, so
later tile buffers land in heap pages glibc keeps. A session that opened and
closed a 5400x7200 document held ~450 MiB afterwards.

photocraft_engine::malloc_tuning::apply(), called first in both binaries'
main, re-executes the binary once with MALLOC_TRIM_THRESHOLD_=131072 on Linux
with glibc: same PID, arguments, argv[0] and environment, in safe Rust
(CommandExt::exec). A malloc variable or glibc.malloc.* tunable the user or a
package already set is left alone, so setting MALLOC_TRIM_THRESHOLD_ opts
out. No-op on macOS, Windows, musl and wasm.

Depends on the scratch-buffer pool (#10479), without which the pinned
threshold slows filters, previews, flatten and .pcraft save by 26-209%.

* fix: corpus tests build again; return remaining pooled buffers (#10479 review)

- Corpus-gated io tests and the Photoshop-oracle test moved `.px` out of a
  `Buffer`, which now implements `Drop` (E0509 with `--features corpus`):
  use `into_px()`.
- io: masks, the merged image, extra channels, smart-filter masks,
  smart-object pixels and patterns give their `interleave` buffers back to
  the scratch pool after writing them.
- compose: recycle the mask values of a pass-through group, `effect_shape`
  and both `split_parts` callers.
- Styled Free Transform preview copies each band out instead of moving it,
  so the band returns to the pool.
- interactive_bench: the fault count covers each whole run, untimed setup
  included; say so.

* fix: corpus tests build again; return remaining pooled buffers (#10479 review)

- Corpus-gated io tests and the Photoshop-oracle test moved `.px` out of a
  `Buffer`, which now implements `Drop` (E0509 with `--features corpus`):
  use `into_px()`.
- io: masks, the merged image, extra channels, smart-filter masks,
  smart-object pixels and patterns give their `interleave` buffers back to
  the scratch pool after writing them.
- compose: recycle the mask values of a pass-through group, `effect_shape`
  and both `split_parts` callers.
- Styled Free Transform preview copies each band out instead of moving it,
  so the band returns to the pool.
- interactive_bench: the fault count covers each whole run, untimed setup
  included; say so.

* fix: recycle the masks of adjustment-atop and shape layers (#10479 review)

* fix: recycle the masks of adjustment-atop and shape layers (#10479 review)

* fix: recycle effect hiding masks (#10479 review)

* fix: recycle effect hiding masks (#10479 review)

* fix: recycle knockout, CMYK and GPU shape masks; release the pool when Split/Merge Channels close a document (#10479 review)

* fix: recycle knockout, CMYK and GPU shape masks; release the pool when Split/Merge Channels close a document (#10479 review)

* fix: release the pool when a closed document's cancelled workers exit; idle-release switch for tests; don't pool PSD planes (#10479 review)

* fix: release the pool when a closed document's cancelled workers exit; idle-release switch for tests; don't pool PSD planes (#10479 review)

* fix: release the pool for workers cancelled before their document closes; take the pool lock for budget and idle-release changes (#10479 review)

* fix: release the pool for workers cancelled before their document closes; take the pool lock for budget and idle-release changes (#10479 review)

* fix: idle release rechecks activity under the lock; try_take_exact releases the pool and retries before failing (#10479 review)

* fix: idle release rechecks activity under the lock; try_take_exact releases the pool and retries before failing (#10479 review)

* test: cover a successful try_take_exact miss; name the failed-reservation test for what it checks (#10479 review)

* test: cover a successful try_take_exact miss; name the failed-reservation test for what it checks (#10479 review)

* fix: pool the final GPU effect shape buffer and return it after use (#10479 review)

* fix: pool the final GPU effect shape buffer and return it after use (#10479 review)

* docs: describe scratch pool lifecycle regression invariants

* test: verify polling releases cancelled document scratch buffers

* perf: recycle remaining transform selection and PSD scratch buffers

* fix: free PSD gather bands before allocating channel planes

* test: assert exact scratch take reuses pooled storage
2026-11-02 09:30:46 +01:00
Khawar Zamman Wani 86ddfb72f7 Reuse large buffers so returning freed memory to the OS doesn't slow filters (#10479) (#10514)
* perf: reuse large buffers so a pinned glibc trim threshold costs no speed (#10479)

With MALLOC_TRIM_THRESHOLD_ set (#8743), glibc maps every allocation over
128 KiB fresh, so tiles, compositor bands and filter scratch were page-faulted
in on every call: viewport compose 3.6x slower, Gaussian Blur up to 1.8x,
.pcraft save 2.4x.

- photocraft_raster::scratch: a process-wide, capped pool of large Vec
  buffers (u8/u16/f32/[f32; 4]). A miss drops the oldest buffers worth its
  size down to a 256 MiB floor, so peak memory stays near glibc's defaults.
  The engine releases the pool after any command that closes a document,
  Headless::close does too, and an idle thread releases it after 2 s unused.
- Tiles, compose Buffer/masks/bands, algo Image and the blur paths, engine
  adjustment/filter scratch, GPU tile upload and io import buffers go through
  the pool.
- .pcraft save reuses one zstd FrameCompressor per worker (byte-identical).
- interactive_bench reports minor page faults per run.

36 MP, trim threshold set vs glibc defaults: viewport 5.6 vs 6.1 ms (was
22.7), Blur r4 445 vs 437 ms (was 852), Curves CPU proxy 35.0 vs 35.7 ms
(was 50), flatten 146 vs 147 ms (was 237), save .pcraft 251 vs 230 ms (was
545). RSS after closing a 5400x7200 document: 21 MB with trim set.

* fix: corpus tests build again; return remaining pooled buffers (#10479 review)

- Corpus-gated io tests and the Photoshop-oracle test moved `.px` out of a
  `Buffer`, which now implements `Drop` (E0509 with `--features corpus`):
  use `into_px()`.
- io: masks, the merged image, extra channels, smart-filter masks,
  smart-object pixels and patterns give their `interleave` buffers back to
  the scratch pool after writing them.
- compose: recycle the mask values of a pass-through group, `effect_shape`
  and both `split_parts` callers.
- Styled Free Transform preview copies each band out instead of moving it,
  so the band returns to the pool.
- interactive_bench: the fault count covers each whole run, untimed setup
  included; say so.

* fix: recycle the masks of adjustment-atop and shape layers (#10479 review)

* fix: recycle effect hiding masks (#10479 review)

* fix: recycle knockout, CMYK and GPU shape masks; release the pool when Split/Merge Channels close a document (#10479 review)

* fix: release the pool when a closed document's cancelled workers exit; idle-release switch for tests; don't pool PSD planes (#10479 review)

* fix: release the pool for workers cancelled before their document closes; take the pool lock for budget and idle-release changes (#10479 review)

* fix: idle release rechecks activity under the lock; try_take_exact releases the pool and retries before failing (#10479 review)

* test: cover a successful try_take_exact miss; name the failed-reservation test for what it checks (#10479 review)

* fix: pool the final GPU effect shape buffer and return it after use (#10479 review)

* docs: describe scratch pool lifecycle regression invariants

* test: verify polling releases cancelled document scratch buffers

* perf: recycle remaining transform selection and PSD scratch buffers

* fix: free PSD gather bands before allocating channel planes

* test: assert exact scratch take reuses pooled storage
2026-11-02 09:29:59 +01:00
David MannandClaude Opus 5.5 c788695a59 Integration: event_for keeps the batch's lookup plus #10632's routing of effect events (#10632)
WIP: gallery_effect_steps_save_back_as_the_event_they_were_recorded_as still fails (Accented Edges step refused on save); to resolve next.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 03:21:42 +01:00
8a0f959d29 Integration: real recorded Convert to Profile and Paste steps play, sparse parallel dither (#10704)
- Convert to Profile: the reference application records `sdwM` -1 for RGB
  targets (`convertToProfile T="ProPhoto RGB" Inte=AClr Dthr=true Fltt=false
  rasterizePlaced=false sdwM=-1`). The PR accepted only 2, so every recorded
  step became a kept step that fails at playback. Accept and write -1.
- Paste: an ordinary Edit > Paste records `AntA Anno` + `As Pxel` (Paste in
  Place adds `inPlace true`). `As Pxel` was routed through the explicit vector
  mode (no layer recreation, no mask/channel targets) and Paste in Place became
  a kept step. `As Pxel` now keeps the ordinary Paste / Paste in Place route.
- Dither: transparent layers stay sparse (only stored tiles are dithered) and
  tiles run in parallel. 24 MP 8-bit, one layer: opaque 1576 -> 101 ms;
  transparent with one stored tile 1670 -> 5 ms and 384 -> 1 stored tiles.
- Image Size: a recorded step without `scaleStyles` (Fit Image) gained
  `scaleStyles false` on save; the key is now written only when styles scale.

Co-authored-by: yoriswind <164435053+yoriswind@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 03:18:46 +01:00
David Mann 79a2a4e15f Merge PR #10632: Actions: map all 47 Filter Gallery effects (#10592)
With our fix-up 023e2662d7 on top of the author's head. Conflicts resolved item by item (Rust), docs as unions.
2026-11-02 03:18:45 +01:00
David Mann 520a996b30 Merge PR #10630: Actions: map Photoshop's Split Channels, Merge Spot Channel, Trap and Match Color steps (#10609)
With our fix-up a299bf9f6c on top of the author's head. Conflicts resolved item by item (Rust), docs as unions.
2026-11-02 03:15:28 +01:00
023e2662d7 Integration: Filter Gallery effect steps keep their recorded event (#10632)
Three gaps in loading and saving Filter Gallery steps from .atn files:

- An effect recorded under its own event by string ID (event `cutout`
  with `GEfk` = `Ct  `, the form newer versions write) stayed a kept
  Photoshop step: only 4-char codes were routed to the effect table.
  The effects' string IDs are now matched too.
- A step recorded as the effect's own event (`AccE` "Accented Edges")
  saved back as a `GEfc` "Filter Gallery" event, so load -> save changed
  the event and its panel name. The step now remembers the event it was
  loaded from (hidden `_atnEvent` param) and writes it back when that
  event is the step's own effect; `GEfc` steps, steps made in PhotoCraft
  and a stale or forged event naming another effect save as `GEfc`.
  Playback strips the param (`playback_params`, like `_actionLayer`), so
  the command, the journal (Last Filter), smart filters and the step's
  dialog never carry it.
- A key that was present but had the wrong type or a non-finite value
  took the default (Texturizer `InvT` as Integer 1 played with invert
  off; `EdgW` NaN played as 2). Only missing keys take defaults now; an
  unreadable present key keeps the step.

Co-authored-by: hectorviov <1501713+hectorviov@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 03:15:07 +01:00
a299bf9f6c Integration: Trap round trip, Match Color source None, Split Channels only on single-layer images (#10630)
- A Trap step recorded without Wdth loaded as {} and saved back as Wdth 1 px,
  so it reloaded as {"width":1}. It now loads as width 1 (what the command
  plays), so load -> save -> load gives the same step.
- The Match Color dialog records Source "None" as "source": -1, which the
  export refused because "source" wasn't an allowed key, so a Match Color
  recorded in PhotoCraft could never be saved. A null or negative source
  (and a sourceLayer with it) is now written as the source-less event; a
  real source (>= 0) is still refused.
- Split Channels closes the original, but split and its enabled check never
  checked the layer count, so a played SplC split and closed a layered
  document with no undo. Like the reference application it now refuses
  (Err, document untouched and open) a document with more than one layer
  (a hidden one counts) or a lone group; a Background-only image or a lone
  normal layer still splits.

Co-authored-by: hectorviov <1501713+hectorviov@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 03:11:30 +01:00
David Mann 951b49e939 Merge PR #10616: Import color component selections from Photoshop actions
With our fix-up cf30adf2a2 on top of the author's head. Conflicts resolved item by item (Rust), docs as unions; docs/atn.md regenerated at the end.
2026-11-02 03:10:25 +01:00
David Mann 496d9f3623 Merge PR #10618: Map Stylize and Pixelate action events
With our fix-up 52cc86351b on top of the author's head. Conflicts resolved item by item (Rust), docs as unions; docs/atn.md regenerated at the end.
2026-11-02 03:10:21 +01:00
David Mann 72d3e49fc6 Merge PR #10620: Map Render filter steps in Photoshop actions
With our fix-up 563666d69a on top of the author's head. Conflicts resolved item by item (Rust), docs as unions; docs/atn.md regenerated at the end.
2026-11-02 03:10:18 +01:00
David MannandClaude Opus 5.5 d97a1c2efa Integration: resolve #10702 and #10628 against the batch (layer selection, event lookup, Vibrance)
- steps_layer.rs select_from/select_to: #10753's layer references by name, index and ordinal
  (recorded_layer_target / recorded_layer_ref) together with #10702's property references and the
  zero document offset it refuses; the line merge had interleaved the two functions.
- layer_params.rs explicit_layer: #10753's group-end arm had lost its closing brace next to
  #10702's Background arm.
- events.rs event_for: the batch's lookup (by code, else by non-empty name, legacy codes) kept;
  #10628 renamed IntW/Dstr to intersectWith/distribute, and the run-time names the reference
  application reports (interfaceWhite, distort) still load through NAME_ALIASES (test extended).
- Vibrance: one event, saved as the string event `vibrance` (#10702, from #8659) with the
  `vibrance` class main writes (#8534); `Vbrn` still loads. #10702's round-trip test expects that
  class now (the top-level class does not change what the reference application plays).
- #10702's tests build AtnFile with the `encoding` field main added (#8327).

All engine tests pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 03:10:15 +01:00
52cc86351b Integration: Trace levels clamp to 0..255, dialog Tiles/Pointillize save (#10618)
Trace Contour read any i32 level, but a recorded step carries a whole
number 0..255 that the reference application clamps; import now clamps
(Lvl -1 -> 0, 300 -> 255) and export refuses levels outside 0..255.

Save Actions refused every Tiles/Pointillize step recorded through the
filter dialog, because the dialog adds foreground/background colours the
recorded event does not carry. Export now drops them (playback uses the
current colours, as the reference application does). Tiles count is
rounded on export like the native command rounds it (17.4 -> TlNm 17),
and null params export the defaults instead of being refused.

Co-authored-by: yoriswind <164435053+yoriswind@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 03:07:01 +01:00
cf30adf2a2 Integration: keep Shift-click and Lmnc channel selects, record panel colour clicks by name (#10616)
- A Shift-click channel select (`slct` Chnl.Chnl.Cyn with `Extd` true) imported as a plain
  single-channel `channel.target`, so playback targeted Cyan alone and Save dropped `Extd`.
  It now stays a kept Photoshop step (only `Extd` absent or false maps).
- `select Chnl.Chnl.Lmnc` went through `channel_of`'s legacy fallback, targeted Lightness and
  saved back as `Lght `. The select importer now uses the exact CHANNELS lookup, so it stays a
  kept step and round-trips unchanged.
- The Channels panel recorded a colour click as `{"channel":{"color":0}}`, which the new export
  rejects, so Save Actions of a PhotoCraft recording failed. A colour row click now sends the
  mode's lowercase colour name (`{"color":"cyan"}`), which targets the same channel and exports
  as the component select; other rows keep their reference.

Co-authored-by: yoriswind <164435053+yoriswind@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 03:03:10 +01:00
563666d69a Integration: Lens Flare dialog centre and Clouds seed 0 on Save Actions (#10620)
The Lens Flare params doc said "centerX":number=0.5, which the filter
dialog reads as an integer field defaulting to 0: Filter > Render > Lens
Flare put the flare in the top-left corner and an imported 0.4 centre
rounded to 0. The doc is back to 0..1=0.5; the engine still plays
off-canvas centres unclamped (a recorded 1.4 centre now has a playback test).

A Clouds or Difference Clouds step recorded through PhotoCraft's dialog
carries {"seed":0}, and Save Actions refused it. The event has no seed
key; seed 0 is what the imported empty step plays as, so it is dropped on
export. Any other seed still plays differently and is still refused.

Co-authored-by: yoriswind <164435053+yoriswind@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 02:55:32 +01:00
David Mann 5d6041c1cd Merge PR #10628: Map selection, clipboard and layer-operation ATN events
With our fix-up 0e41091687 on top of the author's head. Conflicts resolved item by item (Rust), docs as unions; docs/atn.md regenerated at the end.
2026-11-02 02:55:08 +01:00
David Mann 4f899196cb Merge PR #10702: Actions: preserve settings in mapped ATN events
With our fix-up 78cf0cf91f on top of the author's head. Conflicts resolved item by item (Rust), docs as unions; docs/atn.md regenerated at the end.
2026-11-02 02:55:02 +01:00
78cf0cf91f Integration: transform anchor codes, Close save:false, Batch Save As + Close, opacity and thumbnail-click regressions (#10702)
- FTcs reference points used a 3x3 grid (Qcs0..Qcs8). The reference application has
  Qcsa centre, Qcs0..Qcs3 corners clockwise from top-left and Qcs4..Qcs7 the sides
  top/right/bottom/left (measured: 50 % scale, Qcs6 keeps the bottom centre, Qcs1 the
  top-right corner). Qcs8 and Qcsi stay kept steps; nothing writes Qcs8.
- file.close {"save": false} (used by ui-egui tests) became an error; it discards again
  and saves as Svng N.
- Batch never performs the action's Save As, so the scratch document stayed dirty and the
  recorded Close (no Svng -> "save":"ask") failed every file of "...; Save As; Close".
  A Save / Save As save point now marks the document saved.
- Adjustment-layer opacity is quantised to 8 bits on load; a recorded 70 % saved back as
  70.196 %. The whole percent that quantises to the same level is saved again.
- Cmd-clicking a group's thumbnail errored; it loads the whole canvas again as before.
  A recorded Cmd-click on another layer's thumbnail kept the numeric id and failed strict
  Save Actions; it records the layer by name (or as the Background).

Co-authored-by: yoriswind <164435053+yoriswind@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 02:53:47 +01:00
0e41091687 Integration: Group F steps recorded by the reference application map (#10628)
The Group F loader only mapped descriptors with an empty name and class
`null`, and nested shapes with an empty name. The reference application
names every step descriptor after the localized step (plus NUL), uses
the event code as its class and names nested shapes too, so every real
recorded Add To / Fade / Merge Layers ... step stayed a kept step and
never played (e.g. `addTo` with name "Add To\0", class `AddT` and a
nested `Rctn` named "\0"). The header is no longer checked; the shape
is matched by class only; key, order and enum checks stay strict.

IntW, Dstr and MrgL were registered with the string IDs
`interfaceWhite`, `distort` and none; the real ones are
`intersectWith`, `distribute` and `mergeLayers`, so steps written with
them stayed kept.

Co-authored-by: yoriswind <164435053+yoriswind@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 02:50:33 +01:00
David Mann b022fe8b5e Merge PR #10601 fix-up: Load every selected Actions file
Our fix-up 4abb9a4f9494 on top of the author's head 9b1be2f526 (merged earlier).
2026-11-02 02:40:58 +01:00
David Mann d570b77abb Merge PR #10753: Support recorded layer, document and layer comp references
With our fix-up ea4112a4ce on top of the author's head 686f08c884. docs/atn.md kept from the batch (regenerated at the end); development.md and parity doc rows as unions.
2026-11-02 02:40:55 +01:00
David MannandClaude Opus 5.5 e16e4beb12 Integration: repair the merged Actions test file and event table; keep each PR's export rules (#10627, #10629, #10631)
- atn_cmds/tests.rs: the line unions of the merges split tests into each other (shared closing
  lines kept once). Rebuilt from main plus every merged PR's added and changed tests, item by
  item; the shared every_mapped_command table merged line by line.
- events.rs: Remove White Matte had lost its closing lines in the same way.
- #10627 and #10631 both added a `bare_from`/`bare_to` with different export rules (the Group E
  one also takes a pixel `target`): #10627's is now `empty_from`/`empty_to`, so Focus Area and
  Remove Background keep their own rule.
- The non-object params fix of #10631's fix-up (632a62b554) applied to the same `sets_only`
  copies in steps_image.rs (#10627) and steps_distort.rs (#10629).
- Smart Sharpen export writes `presetKind` Custom first again, as recorded steps carry it
  (#10627's fix-up 6ab24508d8 had dropped it, so load -> save lost the key).

All engine tests pass (lib 2591, integration suites green).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 02:40:14 +01:00