Commit Graph
2732 Commits
Author SHA1 Message Date
Alan Rockefeller f75cfd5a56 Clone Stamp and Healing Brush clone from a source set in another open document (#10381)
* Clone Stamp and Healing Brush clone from a source set in another open document

Alt-clicking a clone source in one document and painting in another sampled the
painted document at the source point: a clone source slot held a point and a layer
but not the document. Slots now record the document (and its layer) they were set
in, and painting elsewhere samples that document, as in Photoshop.

- Engine: CloneSource gains `document` (serde default, so older slots read as
  "the document being painted"). Clone Stamp, Healing Brush, the live stroke, the
  Pattern Preview wrap and the brush-tip preview read the source document. Pixels
  are converted to the painted document's colour mode and depth like Edit > Paste
  (no ICC conversion, same as Paste). Current Layer / Current & Below use the
  layer the source was set on (that document's active layer if it was deleted);
  All Layers uses its composite.
- A source whose document was closed is an error asking for a new source, and
  stores no aligned anchor, instead of silently sampling the wrong document.
- cloneSource.set takes an optional `"document": index` (with `source`);
  cloneSource.list and set report each slot's document index.
- UI: the Clone Source panel names the source document (or says it was closed),
  the source crosshair isn't drawn over another document, and the brush preview
  cache includes the source document's revision.

Tests: engine tests for 8/16/32-bit with and without wrap, colour-mode conversion
(Gray > RGB16, RGB > CMYK), sampled layer, Healing Brush, live stroke vs commit,
preview, closed/invalid documents and cloneSource.set/list; a UI test that
Alt-clicks in one document and paints in another.

* Clone test: the aligned stroke past the smaller source document's edge leaves the destination unchanged

Review follow-up: with "aligned": false the stroke sampled inside the source, so it didn't test the edge its comment named, and nothing was asserted.

* Clone Stamp / Healing Brush: an empty Clone Source slot refuses to paint

clone_params fell back to ui.clone_source / ui.clone_offset when the active
slot was empty. Those coordinates carry no document, so after Alt-clicking in
one document and picking an empty slot in another, the stroke read the first
document's coordinates from the second one's pixels.

* fix: reject stale cross-document clone previews

* Clone Source: a copy opened after the source document closed isn't its source

A native copy keeps its document's ID, and a reopened document may keep its ID. A slot set in
a document that was then closed resolved that ID again once a copy was open, and painted from
the copy instead of reporting the closed source (CodeRabbit, clone_source.rs).

Session::close now marks the slots set in the closing document closed (CloneSources::forget_doc,
next to the other per-feature forget_doc calls). A closed slot refuses the stroke and its live
preview with the closed-source error, shows "Source: document closed" in the panel, reports no
`document` in cloneSource.list/set, and draws no overlay or crosshair, whatever document has the
ID now. Setting a new source clears it.
2026-11-12 18:00:54 +01:00
David Mann e165933772 Merge pull request #12430 from ljkx/atn-fit-image
Actions: map File › Automate › Fit Image (#10723)
2026-11-12 13:26:58 +01:00
David Mann a037e55a7f Merge pull request #9952 from ashokram-si/feat/jpeg-options-size-readout
JPEG Options: show how big the file will be (FILE-215-19, #7843)
2026-11-12 13:26:45 +01:00
ljkx da6f09ce70 docs/atn.md: Fit Image leaves a fitting document unchanged only with limit (Don't Enlarge) 2026-11-12 11:03:28 +01:00
Dylan M. Taylor 74330157cb Use craft-psd for PSD/PSB format code (#13414)
* Use craft-psd from craft-libs; remove crates/psd

* Regenerate the scorecard (28 crate roots)

* Take craft-psd at craft-libs 55d120f2

* Regenerate lockfile for current shared format crates

* Pin shared format crates to current craft-libs main

* Match brush import cap to shared decoder budget

* xtask: update layer fixtures after PSD extraction
2026-11-12 02:53:54 -05:00
Dylan M. Taylor 21bb9fda63 Use shared crates for raster codecs (#13416)
* Use craft-raster-codecs, craft-dpx, craft-tga, craft-gif and craft-exr for the format code in crates/codecs

* Regenerate lockfile for current shared format crates

* Pin shared format crates to current craft-libs main

* Fix codec module imports for clippy
2026-11-12 01:29:33 -05:00
David Mann 82adf56e11 Merge remote-tracking branch 'origin/main' into feat/jpeg-options-size-readout 2026-11-12 02:22:55 +01:00
David MannandClaude Opus 5.5 0d9a9a5a70 Docs: the screenshot rule allows a doc's own before/after comparison
CodeRabbit on #13206: docs/multi-channel-editing.md displays before/after
comparisons, which the rule keeps; it targets screenshots that only show a
PR's change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-11 16:41:05 +01:00
David MannandClaude Opus 5.5 0cc411241b Docs: before/after screenshots go in the PR description, not the repo
AGENTS.md rule 6 and docs/images/SOURCES.md now say docs/images holds only
images the README or a doc displays. Removes the nine PR before/after
captures nothing references (gradient-overlay-method, guide-position,
native-pipette-macos, preferences-apply; 1.9 MB) and their ATTRIBUTION.md
rows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-11 16:28:16 +01:00
ljkx 0a2a49852a Merge remote-tracking branch 'origin/main' into atn-fit-image
# Conflicts:
#	crates/engine/src/cmd/atn_cmds.rs
2026-11-11 16:24:18 +01:00
David Mann 9b3435cedb Merge pull request #12523 from SvenCarstensen/codex/fix-macos-pipette-lag
Fix macOS Text Color Picker cursor lag with a native pipette
2026-11-11 14:40:57 +01:00
David Mann 91badc3a97 Merge pull request #12469 from ljkx/actions-artboard-section
Actions: map Layer › New › Artboard and Artboard from Layers (#10723)
2026-11-11 14:39:50 +01:00
FredApps 36bfc50fd3 Merge branch 'main' into feat/image-processor-open-documents
Resolve conflicts by keeping both the Image Processor source parameter
and main's pdfSpotPlates text, and by combining the Last updated
headers in docs/gaps.md and docs/target-app-parity.md.
2026-11-11 11:10:40 +01:00
Tanish Manku 066c55c98e Merge pull request #11297 from TrevorBrown-dev/fix/close-prompt-cmd-d-11282
Unsaved-changes prompt: ⌘D is Don't Save on macOS (#11282)
2026-11-11 00:30:55 -07:00
Tanish Manku 8c142f7e3b Merge pull request #11292 from indy2kro/fix/11271-space-hand-view-menu
Hand held by Space: right-click opens the view menu, as in Photoshop (#11271)
2026-11-11 00:30:10 -07:00
Tanish Manku e301a1d835 Merge pull request #11106 from pkubaj/main
Enable winit's font-free Adwaita decorations on FreeBSD
2026-11-11 00:27:28 -07:00
Tanish Manku ce8e7c6a19 Merge pull request #11054 from yoriswind/codex/preset-import-choice-validation
Preserve preset import dialogs after invalid API answers
2026-11-11 00:26:42 -07:00
Tanish Manku e12da4d048 Merge pull request #10720 from dark13b/fix/issue-10492
Drops: route by a pure function, say when a folder is dropped, log how far a drag got (#10492)
2026-11-11 00:23:15 -07:00
Tanish Manku 4ed05762c0 Merge pull request #10426 from ebosido444-ai/perf/box-line-kernel
perf(algo): gather a strip before walking box-blur columns
2026-11-11 00:18:50 -07:00
Tanish Manku 2dbbdde170 Merge pull request #10395 from qvest-ssels/fix/dialog-label-overrun-9635
Dialogs: one shared label column that never overruns in a longer translation (#9635)
2026-11-11 00:15:36 -07:00
Tanish Manku 0fc597634a Merge pull request #10384 from ingling/pdf-spot-plates
PDF: spot channels as overprinting Separation plates (FILE-215-17)
2026-11-11 00:15:13 -07:00
Tanish Manku 3ffcc590eb Merge pull request #9980 from ljkx/fix/opacity-next-to-mode
Options bar: Opacity right after Mode in every theme (#9784)
2026-11-11 00:11:33 -07:00
Tanish Manku 00895b90bf Merge pull request #8836 from kuvshin888/guide-shift-existing
Guides: Shift snaps a moved guide to ruler ticks too, on whole pixels in a pixel ruler
2026-11-11 00:01:14 -07:00
Dylan M. Taylor 5d4b68f04c Use craft-heif from craft-libs for HEIC/HEIF (#13076)
* Use craft-heif from craft-libs; remove crates/heif

PhotoCraft's HEIF decoder had drifted from LightCraft's by about 700 lines;
both now use craft-heif (storytold/craft-libs#784), built from LightCraft's
copy. HEIC colours now follow the HEVC VUI's range and matrix and libheif's
chroma upsampling. 8-bit files still open as 8-bit documents; a colour
description the VUI specifies now tags the image as an nclx did.

The codecs -> heif standalone exception ends with the local crate, and the
HEIF nextest step drops -p photocraft-heif, which no longer exists.

* Pin craft-heif to the merge of storytold/craft-libs#794

* heif: count the decoder's 16-bit buffer against max_alloc

* heif: match Apple's chroma upsampling; pin craft-heif to storytold/craft-libs#799

* heif: free the decoder's buffer before the HDR conversion and count that conversion's peak

* heif: hold the canvas the decoder really allocates to max_alloc
2026-11-11 01:59:37 -05:00
Tanish Manku e5cffe2375 Merge pull request #7239 from haranishi/review/packaged-cli-docs-20261018
Linux packages: ship the CLI in source Flatpaks and offline automation guides
2026-11-10 23:58:52 -07:00
Tanish Manku 254000acaa Merge pull request #7020 from fjamesprice/perf/camera-raw-gpu-texture-dehaze
perf(camera-raw): develop Dehaze, Clarity and Texture on the GPU in the preview (#6916)
2026-11-10 23:58:06 -07:00
Tanish Manku 1d6cd334ba Merge pull request #6951 from fjamesprice/perf/camera-raw-gpu-local-tone
perf(camera-raw): develop Shadows and Highlights on the GPU in the preview (#6916)
2026-11-10 23:57:54 -07:00
Tanish Manku dc855ae902 Merge pull request #6864 from hidoba/codex/curves-followup-fixes
Fix remaining grayscale Levels/Curves target mismatches
2026-11-10 23:57:32 -07:00
Tanish Manku c2e8c4db82 Merge pull request #6787 from ingling/spot-dot-gain
Color Settings › Spot: dot gain for spot channel and Multichannel ink previews
2026-11-10 23:57:09 -07:00
Michael Higgins 9d5be5ec8e Bridge: never send the control token to a listener that is not the app
## Summary

`photocraft-cli mcp --bridge` sent the control token in clear as its first frame, to whatever had accepted the connection: a `[::1]` listener reached through a `localhost` lookup, or any process holding `127.0.0.1:<port>` while the app was not listening. This PR fixes both cases:

- **Literal loopback only.** `BridgeClient` parses its address into a socket address and connects to it. Only `127.0.0.1:<port>` and `[::1]:<port>` are accepted. `localhost:<port>` means `127.0.0.1:<port>` (what the desktop app binds) and is never looked up.
- **Challenge authentication.** The bridge authenticates with `auth.challenge {clientNonce}` → `{serverNonce, serverProof}` → `auth {proof}`. Both proofs are HMAC-SHA256 keyed by the token over both nonces, each under its own label. The client checks the server's proof before it sends its own and never falls back to sending the token. The token never crosses the wire, so a listener that is not the app gets only a random nonce.
- **One shared server implementation.** `security::ServerHandshake` replaces `authentication_reply`, and both the desktop control server and `photocraft-cli serve` use it. The servers still accept the one-frame `auth {token}`, so existing scripts and older clients keep working. A repeat `auth.challenge` after authenticating gets "already authenticated", like a repeat `auth`.
- HMAC comes from RustCrypto's `hmac` 0.12 with `sha2` 0.10 (`sha2` was already in the lockfile). The proofs are compared without an early exit.
- `docs/control-protocol.md` documents the handshake, the exact HMAC message, and the bridge's address rules. The book's protocol summary is updated too.

Compatibility: a new bridge needs an app from the same release. Against an older app it fails with "refused challenge authentication … the control token was not sent". It does not downgrade.

Known limit: the channel is plain TCP with no TLS, so nothing binds the handshake to the connection. A party sitting between a bridge and a real app could relay the handshake without learning the token. On loopback that needs it to hold the port the bridge dials while the app listens on another, which the literal-address rule above narrows. Older clients that still send `auth {token}` keep their old exposure until they upgrade.

Closes #9777

## Tests

New in `crates/automation/tests/it/bridge_token.rs`:

- `localhost_reaches_the_ipv4_app_not_an_ipv6_listener`: a listener on `[::1]:P` gets no connection, and the app on `127.0.0.1:P` does.
- `a_listener_that_is_not_the_app_never_receives_the_token`: on both loopbacks, a listener that stays silent, refuses, or fakes the challenge receives only the challenge frame, never the token. The error says why.
- `the_bridge_authenticates_to_the_headless_server_by_challenge`: end to end against `rpc::serve_tcp`. A wrong token is refused before its proof is sent.

Unit tests: the handshake in both directions, and that wrong, missing or replayed proofs, the token after a challenge, a second challenge and malformed nonces are all refused (`security.rs`). `loopback_socket` address parsing (`bridge.rs`). The desktop server answering a challenge, then dispatching (`control_server.rs`). The existing bridge and MCP fakes now run the real `ServerHandshake`.

Run locally (Windows 11, targeted tests only):

- **Fail before.** I put upstream's `crates/automation/src/bridge.rs` (the old client) back and ran `cargo test -p photocraft-automation --test it bridge_token`: **3 failed**. The `[::1]` listener received `{"method":"auth","params":{"token":"<the token>"}}`, a `127.0.0.1` listener received the raw token, and the headless test got "authentication required" instead of the refusal. The original audit tests (`audit_nv6_bridge_sends_token_to_ipv6_impostor`, `audit_nv6_bridge_sends_token_to_any_ipv4_holder`) fail the same way on upstream/main.
- **Pass after.**
  - `cargo test -p photocraft-automation --lib --test it -- bridge security tcp`: 23 + 21 passed. This covers the new tests, the existing bridge, MCP-bridge, security and serve TCP tests, and the legacy-token tests that still pass unchanged.
  - `cargo test -p photocraft control_server`: 6 passed, including the new challenge test. The legacy-token tests still pass.
  - `cargo clippy -p photocraft-automation --all-targets -- -D warnings`: clean.
  - `cargo fmt`: clean.
- `Cargo.lock` adds only `hmac` 0.12.1, plus `subtle` under `digest` (already in the lock). Checked with `cargo metadata --locked`.

Not run here: the full gate (`cargo xtask ...`, the workspace-wide tests, wasm, clippy on the desktop app crate). That is left to CI.

API note: the workspace-internal `security::authentication_reply` is replaced by `ServerHandshake::reply`, which returns an `AuthStep`.


<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
<!-- coderabbit-ai-generated:v1 -->

## Summary by CodeRabbit

* **Security**
  * Control connections support challenge-response authentication, verifying possession of the configured token without sending it. Token-in-frame authentication remains available for scripts.
  * The MCP bridge accepts literal loopback addresses and maps `localhost` directly to IPv4 loopback. It rejects servers that refuse or cannot complete challenge authentication.
* **Bug Fixes**
  * Rejected authentication exchanges close the connection; successful authentication allows requests to proceed.
* **Documentation**
  * Control protocol and bridge authentication behavior are documented.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-11-11 07:35:17 +01:00
ashokram-si 23104adb7e Merge remote-tracking branch 'origin/main' into feat/jpeg-options-size-readout
# Conflicts:
#	crates/ui-egui/src/i18n/cs.tsv
#	crates/ui-egui/src/i18n/de.tsv
#	crates/ui-egui/src/i18n/el.tsv
#	crates/ui-egui/src/i18n/es.tsv
#	crates/ui-egui/src/i18n/fr.tsv
#	crates/ui-egui/src/i18n/hi.tsv
#	crates/ui-egui/src/i18n/hu.tsv
#	crates/ui-egui/src/i18n/id.tsv
#	crates/ui-egui/src/i18n/it.tsv
#	crates/ui-egui/src/i18n/ja.tsv
#	crates/ui-egui/src/i18n/ko.tsv
#	crates/ui-egui/src/i18n/nl.tsv
#	crates/ui-egui/src/i18n/pl.tsv
#	crates/ui-egui/src/i18n/pt-br.tsv
#	crates/ui-egui/src/i18n/ru.tsv
#	crates/ui-egui/src/i18n/sr.tsv
#	crates/ui-egui/src/i18n/tr.tsv
#	crates/ui-egui/src/i18n/uk.tsv
#	crates/ui-egui/src/i18n/vi.tsv
#	crates/ui-egui/src/i18n/zh-hans.tsv
#	crates/ui-egui/src/i18n/zh-hant.tsv
#	docs/gaps.md
#	docs/target-app-parity.md
2026-11-11 10:26:12 +05:30
skylar taylor-barrick 1f77e59589 Merge origin/main (2772ac1e7) into craft-i18n cutover
Keep all 34 catalog deletions; sync crop options and fill rasterization text via craft-i18n 4a642c06dbcb8172f307f827c1167cd5dd8ac341. Adapt the fill prompt catalog test to embedded catalogs and preserve both parity-document updates.
2026-11-10 20:16:09 -06:00
ljkxandClaude Fable 5.2 c1fb12fe1d Merge main into fix/opacity-next-to-mode
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-10 23:13:50 +01:00
Vlad Grankovsky 333566a616 Merge upstream main 609e2aa72 into curves follow-up 2026-11-10 19:51:17 +01:00
Tom InglingandClaude c77f589184 Merge storytold main (0d08cb7dd) into pdf-spot-plates
One conflict, the gaps.md header: main's line (2026-11-08, G9 Column
Size now leads it) with this branch's G10 spot-plate item in front.
Main's changes to control-protocol.md and pdf/text.rs merged cleanly;
the doc.save row keeps `pdfSpotPlates`.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-11-10 13:48:01 -05:00
FredApps 96581039a8 Merge main into feat/ui-217-4-columns-unit 2026-11-10 18:41:49 +01:00
kuvshin888andClaude Opus 5.5 87e77b8ad0 Guides: Shift snaps a moved guide to ruler ticks too, on whole pixels in a pixel ruler
A guide already on the canvas, moved with the Move tool (or with Ctrl/Cmd and another tool), kept
following the pointer: `handle_move_pointer` (canvas/tool_input.rs) called `GuideDrag::track`,
which never looked at Shift.

- `GuideDrag::follow_pointer`: with Shift the guide goes to the ruler tick nearest the pointer,
  with the same `snap_to_tick` as a ruler drag. The tick is taken from the pointer before
  View › Snap moved it (`unsnapped(raw)`), as a ruler drag never calls `snap_guide`, and that
  move's snap lines are cleared. It goes through `track`, so a guide ⌥ turns (#5034) snaps along
  the axis it now runs on.
- The release is judged by the pointer (`GuideDrag::pointer`), not by the tick: let go where it
  was pressed, the guide is clicked (selected); let go off the document, it is removed. Off the
  document a Shift move doesn't snap, and a tick past the document's edge leaves the guide on it.
- `snap_tick`: in a pixel ruler Shift snaps no closer than a whole pixel (guides out of a ruler,
  moved guides, the zero point). Deep in, a pixel ruler labels every 5, 2 or 1 px, so its minor
  ticks were 0.5, 0.2 or 0.1 px apart and Shift put guides between pixels.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 20:07:08 +03:00
ashokram-si 1d0cfbcf6a Merge remote-tracking branch 'origin/main' into feat/jpeg-options-size-readout
# Conflicts:
#	contributors/people.toml
#	crates/ui-egui/src/i18n/cs.tsv
#	crates/ui-egui/src/i18n/el.tsv
#	crates/ui-egui/src/i18n/es.tsv
#	crates/ui-egui/src/i18n/fr.tsv
#	crates/ui-egui/src/i18n/hu.tsv
#	crates/ui-egui/src/i18n/it.tsv
#	crates/ui-egui/src/i18n/ja.tsv
#	crates/ui-egui/src/i18n/ko.tsv
#	crates/ui-egui/src/i18n/nl.tsv
#	crates/ui-egui/src/i18n/pl.tsv
#	crates/ui-egui/src/i18n/pt-br.tsv
#	crates/ui-egui/src/i18n/ru.tsv
#	crates/ui-egui/src/i18n/sr.tsv
#	crates/ui-egui/src/i18n/tr.tsv
#	crates/ui-egui/src/i18n/uk.tsv
#	crates/ui-egui/src/i18n/vi.tsv
#	docs/file-format-parity.md
#	docs/gaps.md
#	docs/target-app-parity.md
2026-11-10 22:29:29 +05:30
Khawar Zamman WaniandFredApps e1145b604e Make text-on-path placement editable (#11629)
* Add path text placement ranges and drag gestures

* Preview path placement and track bounded canvas damage

* Preserve legacy path anchors and show overflow markers

* Match path marker hit targets and directional cursors

* Fix path placement lint failures

* Cover path text body movement and position locks

* Keep open path text movable at range endpoints

* Target the type path after attaching text

* Fix path text range rendering and placement lifecycle

* Map vertical path text and preserve drag branch continuity

* Cover path placement lifecycle and preview damage regressions

* Preserve closest branch projection and fix CI regressions

* Normalize path endpoints and bind gestures to their viewport

* Simplify viewport ownership guards for Clippy

* Document path placement invariants and regression cases

* Fix path placement ownership, validation and geometry caching

* Correct path placement regression fixtures

* Ignore transient path geometry cache in UI state equality

* fix: allow path text flips with stale placement ranges

* fix: retain path text handles after shortening curves

* test: account for layer translation in path placement drags

* fix: mark path text range with x and o markers on the path

The beginning marker is an x and the end a small circle, both on the
path, with a plus in the circle while the range hides glyphs. The
cursor's triangle points right over the beginning, left over the end,
and both ways over the text body, which moves the whole range.
Direct Selection leaves an anchor or handle under a marker to path
editing; Path Selection always takes the marker.

* fix: resolve coincident path text markers by lap or drag direction

The x and o markers coincide on a closed path's full lap and on an
empty or end-clamped open range. A full lap now moves the whole text;
otherwise the first motion picks the beginning (backward) or the end
(forward). Marker fixtures press on the path, where the markers now
sit.

* fix: let either end of a full-lap path text range be dragged

Text attached to a closed path fills one lap, so its x and o markers
coincide and every press moved the whole text, leaving no way to
shorten the range. As the text begins just ahead of that point and
ends just behind it, a press ahead along the path now grabs the
beginning and one behind grabs the end; the point itself still moves
the whole text.

* test: check full-lap marker drags do not flip the text

* fix: read and write the path text range in PSD

Type on a path keeps its range in the Txt2 frame as /2 /6 [start end],
in curve-segment units with -1 for the default. Three reference-editor
files in ag-psd's test set fix the mapping: text-path ends a third into
the first segment and draws only "so" of "some text", and
text-path-26/27 move the start on an open path.

Import reads both ends (and the flipped flag) into the layer's start and
end. Export writes an open path whole with its start and end in the
range, instead of cutting the path at the start, and a closed path's
limited end as a range from its start. Path text in a document that
never came from a PSD now gets a Txt2 block, so its path is saved at
all, and the warning that the end limit is lost is gone.

* fix: let both selection tools grab a path text marker on an anchor

Direct Selection gave an anchor under a marker the click, so an open
path's default markers (on its end anchors) and a closed path's
coincident full-lap markers (on its first anchor) could not be moved
with that tool. In the reference editor either selection tool drags a
marker wherever the pointer shows the text bar with a triangle, so the
marker now wins for both tools.

The Direct Selection tests for reshaping a type path (#9114) drag its
last anchor, where the default end marker sits; they now end the
range short of it. To reshape at an anchor under a marker, drag the
marker off it or select the anchor with a marquee and nudge it.

* fix: keep explicit and empty path text ranges through PSD

An explicit end at the path's end stays explicit, so centered and right type keeps its alignment within the span. A closed range whose ends meet reads as empty, not as a full lap.

* ci: re-run checks after a download outage

* ci: re-run checks after a download outage

* fix: preserve legacy path text anchors through PSD

* ci: re-run checks against current main

---------

Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com>
2026-11-10 16:28:51 +01:00
Tsieg e191f5e41e fix: sanitize untitled Image Processor export names 2026-11-10 21:48:31 +07:00
terminalgateandClaude Sonnet 5.5 fb3b6a4b70 Eraser: add Erase to History (Brush, Pencil and Block modes) (#10692)
* Eraser: add Erase to History (Brush, Pencil and Block modes)

Photoshop's Eraser has an Erase to History option; PhotoCraft had only the History Brush.
With it ticked, the Eraser paints the History panel's source state back instead of
erasing, through paint.historyBrush with eraseToHistory. Pencil and Block modes paint back
through their hard tips. A layer that did not exist in the source state paints that
state's visible image; the History Brush itself still refuses such a layer, as Photoshop's
does.

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

* Keep tool_event within clippy's complexity limit

* Test Erase to History at every depth and tip

* Update the History Brush params comment for Erase to History

* Erase to History paints a layer made since only from a single-layer state

Compared with Photoshop 27.11.0: on a layer the source state does not hold, Erase to History paints
when that state holds a single layer, Background or not, and gives an error when it holds a second
layer, even an empty one. The composite of a multi-layer state is gone; those states now refuse
the layer with the History Brush's error.

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

* Erase to History paints a single source layer's own pixels

Photoshop 27.11.0 paints a layer made since with the source layer's pixels: a 50% opacity layer
gives a solid stroke, where the composite gave a faded one.

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

* Erase to History: Pencil mode paints at full flow; test a group-only source state

Pencil mode has no Flow control, so a lower Flow set in Brush mode must not carry over. A source
state holding only a group has no pixels of its own and refuses a layer made since.

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

* Erase to History keeps its options if a tool key is pressed mid-stroke

The stroke read the current tool and brush when it was committed, so pressing a tool's letter
during the drag dropped eraseToHistory and the stroke failed on a layer made since. The options
and the Eraser's tip are now captured at the press.

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

* Erase to History: a single solid shape paints its colour; name the History row

Photoshop 27.11.0 paints a shape layer's solid colour along the whole stroke, past its path, and
calls the stroke History Eraser in the History panel.

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

* Erase to History: a single shape without a solid fill paints white

Observed in Photoshop 27.11.0: a gradient, pattern or no fill paints plain white along the whole stroke, past the path and whatever the outline, in RGB, CMYK, Grayscale and Lab.

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

* Eraser: Alt at the press reverses Erase to History for that stroke

As in Photoshop 27.11.0: with the option off Alt restores, with it on Alt erases. Alt pressed or released during the stroke changes nothing, and the option itself is left as it was.

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

* Erase to History: name the Photoshop version compared

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

* Erase to History: review fixes for the error and the History Brush tip

A layer without pixels now gets the error that the target did not exist
in that history state, not "no active document". The History Brush keeps
its size, hardness and spacing from the press, as the Eraser does, if a
tool key is pressed mid-stroke.

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

* Brush parity: add the Eraser's Erase to History

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

* Erase to History: document the functions this change touches

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

* History Brush and Erase to History keep their whole brush from the press

Only mode, opacity, flow, size, hardness and spacing were kept from the press; the rest of the brush (tip shape, sampled tip, angle, dynamics, smoothing) came from the session brush at release. A tool key mid-stroke changed that brush, the commit rejected the live preview, and the fallback repainted the whole stroke with the new tool's brush. The stroke's params now carry the press-time brush as "brush" (stored tips as references, as each tool's remembered brush), and paint.historyBrush deep-merges it onto the session brush, so the live preview, the commit, its fallback and a replayed action all paint with it. Tests: a tool switch mid-stroke paints exactly what the same stroke paints without it, for the History Brush and Erase to History in Brush, Pencil and Block mode, across tip shape, sampled tips, dynamics and smoothing; a "brush" param paints as the session brush would; a malformed one is refused.

* History Brush docs: say "outside its shape", not "past its path"

The automation crate flags any command whose params text mentions a path as a possible filesystem path (registry_filesystem_path_params_are_classified); the eraseToHistory description meant a shape's vector outline. Also a test that a state whose one root item is a group of several layers is refused like the one-layer group: a group has no pixels to paint back.

* Test that a Shift-click Erase to History line commits its live pixels unsmoothed

Main's Shift-click fix (#11887) and this branch's press-time History
Brush params meet in finish_stroke_with; check the commit sends
smoothing 0 beside the brush param in all three Eraser modes.

---------

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-10 14:51:20 +01:00
ec97a146f5 Support ordered automation read roots with safe save provenance (#3023)
* fix(cli): reject repeated automation read roots

* feat(automation): add ordered read roots with isolated saves

Stage source and regression tests for GGEO-AI02 validation before updating PR #3023. Repeated reads accumulate; writes remain single-root and implicit saves retain their selected capability provenance.

* style: format multi-root automation extension

* test: use fixed pixel chunks in read-root regression

* docs: reconcile control protocol with upstream TGA options

* docs: state multi-root capabilities directly in API summaries

* test: construct regression actions through upstream defaults

* Clarify headless and desktop directory automation reads

* Format the shared root parser and relocated service regressions

* test: adapt automation import callback to ImportOptions

* test: expect read-root diagnostic for denied smart-object input

* test: reject repeated write roots while allowing ordered read roots

* test: assert explicit empty-root parser diagnostic

* test: exercise leaf-link save using a supported image filename

---------

Co-authored-by: FredApps <FredApps@users.noreply.github.com>
Co-authored-by: Codex <codex@openai.com>
2026-11-10 14:28:12 +01:00
Tsieg e0cb757a64 Merge main into Image Processor source branch 2026-11-10 20:09:45 +07:00
Tom InglingandClaude 9b5568ec92 Merge storytold main (2a950d64d) into pdf-spot-plates
Conflicts were the two docs headers only: gaps.md and
file-format-parity.md each take main's line (Fujifilm compressed RAF
now leads it) with this branch's PDF spot-plate items in front. Main
did not touch the PDF writer or the save commands since 009b93c5e.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-11-10 07:42:12 -05:00
David Mann 2a950d64dd Merge pull request #7377 from smyrgeorge/fuji-compressed-raf
Fujifilm compressed RAF: decode lossless and lossy files instead of the embedded preview
2026-11-10 13:36:29 +01:00
Tsieg 9f1e7a50cc feat: support current and open documents in Image Processor 2026-11-10 17:37:13 +07:00
David Mann 4720d1cc93 Merge branch 'main' into atn-fit-image (merge queue)
# Conflicts:
#	docs/atn.md
2026-11-10 03:04:19 +01:00
Tom InglingandClaude Fable 5.1 d08a0bfaba Merge storytold main (009b93c5e) into pdf-spot-plates: plates on artboard pages
Main rewrote the PDF writer since e4e2607c8: a page per artboard with
page labels, writer cancellation (`ctl`), an export memory budget, and
PDF import. This branch adds spot plates, an optional page image and an
XMP packet. The merged writer keeps both:

- `print_pdf_pages` numbers its objects dynamically: per page its page,
  contents, image and one XObject per plate; then the shared font, ICC
  profile, overprint ExtGState and XMP metadata stream, each only when
  present, the selection font and last the page-label tree. The XMP
  packet is a parameter now, not a page field, since it is the
  catalog's.
- `export(doc, spot_plates, ctl)` / `export_with(doc, spot_plates, xmp,
  ctl)`. An artboard page carries its own region of every plate
  (`plates_of` takes the page rect), so a two-board document separates
  into two pages of named plates; a Multichannel artboard document is
  pages of plates alone. The export budget counts one byte a pixel per
  plate alongside the composite. New test:
  artboard_pages_carry_their_region_of_each_plate.
- lib.rs passes `ctl` through; the PDF arm keeps the XmpEmbed gate.
- file_cmds.rs: main's `maximizeCompatibility` and this branch's
  `pdfSpotPlates` both listed in the four spec strings and the import.
- docs: gaps.md and file-format-parity.md headers take main's line with
  this branch's items in front; the PDF parity row keeps main's new
  read column and notes and this branch's plate notes, plus that an
  artboard page carries its region of each plate; control-protocol.md's
  doc.save row lists both new params.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-09 14:46:47 -05:00
ljkxandClaude Opus 5.5 dcde46d00d Merge main into fix/opacity-next-to-mode
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 20:16:42 +01:00
David Mann c64708b836 Merge pull request #11836 from yoriswind/codex/recorded-displace-10705
Map recorded Windows Displace actions
2026-11-09 19:27:57 +01:00