Commit Graph
313 Commits
Author SHA1 Message Date
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
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 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 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
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
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 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
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
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
ea4112a4ce Integration: group-end layer index fails, ordinal precondition resolves once (#10753)
A `{"layer":{"index":n}}` that lands on a group end made explicit_layer
return None, so layer.translate and the other callers acted on the
selected layers instead (e.g. layer.translate {"layer":{"index":3}} with
L1+L2 selected moved both). It now fails with "a group end is not a
layer"; layer.select still treats a group end as a no-op.

explicit_layer_enabled resolved a relative ordinal (Frwr/Bckw), made that
layer active, then ran the precondition with the unresolved ordinal, which
resolved again one layer further. With only Red targeted and A (active),
B, C (type) bottom to top, edit.clear {"layer":{"ordinal":"Frwr"}} was
refused with "Rasterize the layer" although it acts on raster B. The
precondition now sees the resolved layer id.

Co-authored-by: yoriswind <164435053+yoriswind@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 02:17:31 +01:00
1f524c378b Integration: track the v12 .atn fixtures and sniff v12 in the CLI (#10743)
The three version 12 fixtures were tracked inside crates/psd/fuzz/corpus,
which crates/psd/fuzz/.gitignore ignores and cargo-fuzz rewrites (cmin) or
deletes; the psd and engine test binaries include_bytes! them, so cleaning
the corpus broke the build. They move to crates/psd/tests/fixtures/atn with
a README (sizes, purpose, sha256; the seeds note folded in) and an
ATTRIBUTION.md row; the fuzzer takes the folder as an extra corpus dir.

photocraft-cli recognised an extensionless Actions file only by version
word 16, so a version 12 set without .atn (classic Mac OS Photoshop 5)
went down the JSON path and failed with a UTF-8 error. It now accepts 12
and 16, the versions photocraft-psd reads. Input: v12-commands.atn copied
to "Legacy Actions" and passed to load_actions_file.

Co-authored-by: yoriswind <164435053+yoriswind@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 02:09:41 +01:00
yoriswind 686f08c884 test(engine): record and export accepted comp index bounds 2026-11-01 14:08:40 -08:00
yoriswind c5ed8f9fcf Cover comp recording wire bounds and invalid move destinations 2026-11-01 13:44:29 -08:00
yoriswind 6e5f02c014 Check absolute profile intent against an independent pixel value 2026-11-01 13:17:55 -08:00
yoriswind 0ae7356064 Respect mask creation clearing the selection in the control test 2026-11-01 13:15:34 -08:00
yoriswind 0aac40105e Reject group transparency before modifying the selection 2026-11-01 13:12:39 -08:00
yoriswind cebea834c1 Prove recorded comp ordinals restore the current layout 2026-11-01 13:06:05 -08:00
yoriswind 8aed34d540 Cover group transparency and empty-name source defaults 2026-11-01 13:04:53 -08:00
yoriswind 21b6546907 Resolve recorded Action reference forms at playback 2026-11-01 12:55:02 -08:00
yoriswind e8c5608966 Strengthen paste and profile playback checks
Include the default frosted Glass code in the reference allowed values.
2026-11-01 12:38:07 -08:00
yoriswind 2854b4f9fb Preserve named transparency sources and allow false depth flags 2026-11-01 12:17:43 -08:00
yoriswind 408f48d0bc Reproduce named transparency and false depth-flag import failures 2026-11-01 12:16:02 -08:00
yoriswind 713aa92ba5 Exercise legacy action imports through the integration suite 2026-11-01 11:50:10 -08:00
yoriswind 027b2cdd3c Cover normal Paste followed by Paste As after an internal copy 2026-11-01 11:43:45 -08:00
yoriswind a78b1f1bf8 Keep command-option dependencies limited to style scaling and profile intent 2026-11-01 11:33:53 -08:00
yoriswind 363caa53c3 Check pixel orientation after relative rotation 2026-11-01 10:07:36 -08:00
yoriswind 53f3ef7c41 Record the default resampler for point-sized actions 2026-11-01 09:57:05 -08:00
yoriswind 46fb7822c9 Support source-backed SVG Paste modes and ATN classes
Retain bounded original SVG alongside its raster mirror. Explicit Pixels,
Path and Shape modes use real source data, validate destination and workload
limits before editing, and preserve legacy Paste when the mode is omitted.
Round-trip typed Photoshop Paste classes and keep unsupported descriptors.

Adapted-from: Skyfuller1303's PR #3409
Original-commit: 897f200727
Original-source: https://github.com/storytold/photocraft/pull/3409

Validation: engine check --tests; strict lib and vector target Clippy;
7 focused native/ATN tests; owned-file formatting and diff checks.
2026-11-01 09:35:13 -08:00
yoriswind c345eef785 Validate ATN sizing and opacity and honor transform anchors 2026-11-01 09:34:25 -08:00
yoriswind d3392e6124 Honor fill rotation and style paste group eligibility 2026-11-01 09:09:00 -08:00
yoriswindandbommerts bfe096a988 Preserve named Vibrance exports and adjustment creation opacity
Port the relevant changes from @bommerts in PR #8659:
c4d5c63259,
9f0ae2e43f,
35d1102540 and
668f006ef8.

Keep current mask handling and layer placement. Separate layer opacity from
adjustment settings so a default Gradient Map still saves as a default preset.

Co-authored-by: bommerts <44821497+bommerts@users.noreply.github.com>
2026-11-01 08:11:10 -08:00
yoriswind c2c5b5c30f Cover Vibrance and adjustment opacity action round trips
Vibrance playback coverage adapted from bommerts' PR #8659, commit c4d5c63259. Add the reported Gradient Map opacity case, metadata-only defaults, and atomic validation controls.
2026-11-01 08:09:11 -08:00
yoriswind ab0b26a9c6 Wire profile action options and dither 8-bit conversions 2026-11-01 07:46:48 -08:00
yoriswind 23f1b8bce7 Revert "Add optional layer-style scaling to Image Size"
This reverts commit d7bce26deb.
2026-11-01 07:15:45 -08:00
yoriswind 947d4fd1b0 Revert "Preserve Image Size style scaling through ATN actions"
This reverts commit de9c5e743e.
2026-11-01 07:15:45 -08:00
yoriswind 91fd170570 Resolve recorded ATN dimensions at playback and write mode classes 2026-11-01 07:15:34 -08:00
yoriswind 69e93b8ca1 Add native profile flatten and placed-rasterization options 2026-11-01 07:05:52 -08:00
yoriswind 84a84851cb Cover recorded canvas percentages, point sizes and mode flags 2026-11-01 07:04:05 -08:00
yoriswind c56822347b Preserve Image Size style scaling through ATN actions 2026-11-01 06:59:02 -08:00
yoriswind db1fb662a8 Add optional layer-style scaling to Image Size 2026-11-01 06:59:02 -08:00
yoriswind ca60678754 Describe CAF action conversion and regression witnesses 2026-11-01 06:37:14 -08:00
yoriswind 376949a6f3 Preserve Content-Aware Fill options in ATN actions 2026-11-01 06:34:22 -08:00
yoriswind abb28e4d37 Correct mapped ATN identifiers and wrap document offsets 2026-11-01 06:24:58 -08:00
yoriswind 4875f94ebb Expose Content-Aware Fill scale and mirror options 2026-11-01 06:13:03 -08:00
yoriswind de9c5e743e Preserve Image Size style scaling through ATN actions 2026-11-01 06:13:02 -08:00
yoriswind d7bce26deb Add optional layer-style scaling to Image Size 2026-11-01 06:13:00 -08:00
David MannandClaude Opus 5.5 622707a36f Address CodeRabbit review: four findings on the batch
- Channel targeting routes an allow-list of pixel-editing paint commands, not every
  `paint.*` id: the Mixer Brush's Alt-click load and Symmetry no longer follow a colour
  set or get refused with "Rasterize the layer" on a fill layer. Patch and Content-Aware
  Move stay routed (#9274).
- Native vector save-back checks the preview SVG as strictly as stored previews are
  checked on load and PSD export, so a preview with an unreadable embedded image is
  refused instead of stored and later dropped (#8571).
- .pcraft blobs read their zstd frame with framing headroom, so an incompressible
  preview right at its 16 MiB limit still loads; the limit still bounds the data.
- Levels/Curves recall (Alt+shortcut) decides the combined Lab record from the target
  the dialog captured, as remembering does, so a viewed layer mask keeps its settings
  beside a Lab colour set (#9274).

Each comes with a test that fails without it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 14:41:36 +01:00