Commit Graph
959 Commits
Author SHA1 Message Date
Kushagra SaxenaandClaude Opus 5.5 aa54979583 Record a duplicate's inPlace only when a copy is an artboard
Alt-drag, Cmd+Opt-drag and Free Transform on a copy duplicate with
`inPlace: true` for every layer, but the flag only changes anything for
an artboard. With in-place duplicates now refused on strict .atn export,
a recorded copy of a plain layer could no longer be exported, although a
plain Duplicate replays it exactly. Recording now drops `inPlace` when no
copy is an artboard, as `layer.select` already rewrites its recorded
params.

Refs #7200

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 21:17:40 +05:30
Kushagra SaxenaandClaude Opus 5.5 443e6a3fc8 Refuse strict .atn export of an in-place layer duplicate
Photoshop's Duplicate Layer has no in-place flag, so `layer.duplicate
{"inPlace": true}` was exported as a plain Duplicate and replayed with
the artboard copy beside the original. Strict export now fails on it and
skipUnsupported reports it, as for a Close with a document target.

Fixes #7200

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 20:40:13 +05:30
aiongg d95fdf3181 Merge pull request #7569 from aiongg/integration-4
Integration batch: 12 PRs on main
2026-10-18 23:29:05 -04:00
Korg1ch f3fb5781ef Lens Blur: an alpha channel as the depth map source, as in Photoshop (#7400) (#7508)
* fix(filters): Lens Blur reads its depth from an alpha channel, listed in Depth Map › Source as in Photoshop (#7400)

* test(filters): Lens Blur depth from an alpha channel by name or index, its errors, and the Source menu (#7400)
2026-10-18 17:23:32 -04:00
Brian TandClaude Opus 5.5 ca69059dd0 Edit: Copy with no selection keeps fill and adjustment layers (#2344) (#7497)
Copy refused fill and adjustment layers ("the active layer has no
pixels"). With no pixel selection they are now taken whole, like smart
objects, type, shape and group layers, and Paste recreates them in this
or another document. An adjustment layer's clip spans the canvas, so
paste placement and the OS-clipboard mirror stay consistent.

Scorecard UI-217-23 note, gaps.md G9 and target-app-parity.md updated;
the row stays partial (a single pixel layer still copies pixels only).

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 17:23:26 -04:00
Nguyễn Minh Long 72a3423a7e Preferences: Enable Gestures off ignores touch-screen pinch and twist (#204) (#7478)
Edit > Preferences > Enhanced Controls > Enable Gestures (enhancedControls.touchGestures)
was saved but read by nothing, so it was hidden from the dialog. Now, with it off, two
fingers on a touch screen no longer zoom the canvas or the Navigator, nor turn the view.
The mouse and trackpad behave as before, including a trackpad pinch or Rotate event that
arrives while fingers rest on the screen.

- wheel_nav: pinch_factor() drops egui's multi-touch zoom when gestures are off and keeps
  the trackpad's own Event::Zoom (Windows' folded Ctrl + wheel pinch included);
  read_with() takes the preference.
- rotate_view: trackpad_delta() only falls back to the multi-touch rotation while gestures
  are on; Rotate events always count.
- canvas, Navigator: pass the preference through.
- prefs: touchGestures leaves HIDDEN_UNTIL_IMPLEMENTED, so the dialog shows it.
- Tests: synthetic two-finger pinch and twist, on and off, and the trackpad events with
  gestures off.
- docs/scorecard.md regenerated: settings that do nothing 26 -> 25 of 168.
2026-10-18 17:23:21 -04:00
Marco Arnulfo 0eb0387d53 Save for Web and Quick Export: "All" keeps the photo's EXIF (fixes #7436) (#7471) 2026-10-18 17:23:10 -04:00
Korg1ch f8e4a1344f Print: the last print's settings survive a relaunch, as in Photoshop (#7410) (#7526)
* fix(print): the last print's settings are kept in the preferences, so Print opens with them after a relaunch (#7410)

* test(print): the last print's settings survive a relaunch for Print and Print One Copy (#7410)
2026-10-18 16:57:24 -04:00
NetterSilverandClaude Haiku 5.5 92721dd8c4 Refuse strict .atn export of plain events whose target is not the layer pixels (#7564)
Fixes #7272

Co-authored-by: Claude Haiku 5.5 <noreply@anthropic.com>
2026-10-18 16:57:19 -04:00
David MannandClaude Opus 5.5 5225b592fe Perspective Crop: draw the crop quad's side of the horizon (#7557) (#7558)
When the crop quad's horizon crossed the layer and most of the layer
(or its centre) lay beyond it, warp picked the source's majority side
and skipped every output pixel, so the crop came out blank. A clip
wholly on one side of the inverse horizon only ever samples that side,
so draw it; a straddling clip keeps the majority rule (#4863).

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 16:57:14 -04:00
David MannandClaude Opus 5.5 d77a989ef4 image.crop: accept whole-number float targetWidth/targetHeight (#7551) (#7552)
The target size was read with as_u64 only, so 100.0 (json.dumps of a
float, many agent clients) was refused as not a whole number, while the
crop rectangle and image.imageSize accept it. Fractional, negative,
zero and out-of-range values are still refused.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 16:57:03 -04:00
8ea77bdfd6 Fade: restore uniformly inverted layer masks (#7383) (#7463)
* fix(fade): detect uniform mask changes and blend full canvas

* test(fade): cover uniform mask inversion and Fade across bit depths

* style(fade): keep default-pixel comparison readable

* Format edit_menu_cmds.rs with rustfmt

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

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 16:56:33 -04:00
Hodd 140230b141 Replacing selected type keeps its size, as in Photoshop (#7457)
Typing or pasting over a selection now takes the style of the first selected character instead of the one before it, so retyping a smaller second line no longer takes the first line's size from the line break. Fixes #7413.
2026-10-18 16:56:26 -04:00
Khawar Zamman WaniandClaude Opus 5.5 502d055c6c file.saveACopy refuses the document's own file without "overwrite" (#7437) (#7456)
* file.saveACopy refuses the document's own file without "overwrite" (#7437)

Save a Copy through the engine (control channel, MCP, serve, headless
action playback) silently replaced the open document's own file when
given its path, so a lossy re-encode could not be undone. It now returns
BadParams when `path` is the active document's file (same text, ./ or ..
spelling, hard link, case on Windows via same_file) unless the params
pass "overwrite": true, and nothing is written. The desktop dialog writes
through the shell after the OS confirms a replace, so it is unchanged.

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

* same_file: compare file identity on Windows, so a hard link is the same file

On Windows `same_file` compared canonical path strings, so a hard link to
the input (another name for the same file) passed the new convert guard
without --in-place. It now compares the volume serial number and file index
(the `same-file` crate, already in the dependency tree; std's file_index is
unstable), as unix compares dev and inode, falling back to the
case-insensitive canonical path when a file won't open. The engine's
same_file test now checks hard links on every platform. (Review on #7454.)

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

* saveACopy own-file test: hard link on every platform, doc state unchanged after a refusal

Each refused copy (same path, ./ spelling, hard link) now also checks the
document keeps its path, revision and unsaved state. The hard link is no
longer unix-only: same_file compares file identity on Windows too. (Review
on #7456.)

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 16:56:21 -04:00
aiongg 1c2193f562 Merge PR #7011: Artboards to Files: paths, notes, slices and marks move with the board (#7010) 2026-10-18 20:33:44 +00:00
aiongg b3b8c7b082 Merge PR #6809: Transform: drop a fill layer's cached render when its frame changes (#6808) 2026-10-18 20:33:44 +00:00
aiongg 4e517c054e Merge PR #6779: Image Size: a gradient fill's centre offset stays a fraction of the frame (#6778) 2026-10-18 20:33:44 +00:00
aiongg 978404424e Merge PR #6769: Presets: refuse renaming a group to a name another group has (#6768) 2026-10-18 20:33:43 +00:00
aiongg 5caeea8c1b Merge PR #6670: fix(engine): refuse a Content-Aware Fill with nothing to sample instead of filling black (#6532) 2026-10-18 20:33:43 +00:00
aiongg 34e5b4b42f Merge PR #5658: Video: Interpret Footage and New Video Layer from File reject an invalid frame rate (#5657) 2026-10-18 20:33:43 +00:00
aiongg baee303af1 Merge PR #5656: Image > Adjustments: locked smart objects refuse new smart filters (#5655) 2026-10-18 20:33:43 +00:00
aiongg fd44b1c873 Merge PR #5620: Smart Filters: deleting the last filter removes its filter mask (#5619) 2026-10-18 20:33:43 +00:00
aiongg a07b87a1af Merge upstream main into integration-2
crates/engine/src/mode_cmds/tests.rs: keeps #7139's opaque-background test and main's full-precision angle test
2026-10-18 15:11:28 +00:00
aiongg 4df773e966 Merge pull request #7405 from aiongg/next
Integration batch: 19 PRs on main
2026-10-18 11:09:40 -04:00
7066497886 Keep fractional translation-only Free Transform on .atn import (#7401)
Fixes #7298

Co-authored-by: Claude Haiku 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 10:51:21 -04:00
NetterSilverandClaude Haiku 5.5 713dbbed2f Save Actions ATN files atomically so a failed write keeps the old file (#7403)
Fixes #7290

Co-authored-by: Claude Haiku 5.5 <noreply@anthropic.com>
2026-10-18 10:50:37 -04:00
NetterSilverandClaude Haiku 5.5 80553ecd93 Refuse strict ATN export of Close with an explicit document target (#7393)
Fixes #7299

Co-authored-by: Claude Haiku 5.5 <noreply@anthropic.com>
2026-10-18 10:50:32 -04:00
David MannandClaude Opus 5.5 d459ca7939 Image Rotation: Arbitrary keeps Color Sampler points on their pixel (#7372) (#7373)
Sampler points are pixel indices, but rotate_arbitrary mapped them as the
pixel's top-left corner while the warp moves pixels by their centres, so
after a turn a point sat one pixel off its pixel, or fell off the canvas
(a corner point at 90° or 180°). Map the pixel centre, as #6720 does for
flips and right-angle turns; the turned Crop shares this map.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 10:39:34 -04:00
David MannandClaude Opus 5.5 a4c7b5ef89 Rotate Canvas Arbitrary: keep the angle at full precision, so rotated crops land on the pixel (#7369) (#7370)
rotate_arbitrary read `angle` as f32. rotated_crop sizes its canvas with
rotated_size at f64 and passes the angle through JSON; near a whole-pixel
ceil boundary the f32 angle grew the canvas 1 px more (1000x1000 at
0.11470651449337071: 1003 instead of 1002), shifting the crop by 1 px.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 10:39:29 -04:00
David MannandClaude Opus 5.5 fb6f8b4fa6 Canvas Size: growing the canvas re-renders PSD fill layers (#7362) (#7363)
A fill layer from a PSD keeps its stored pixels as a fill cache that covers
only the old canvas. Canvas Size, an extending Crop and Reveal All translated
that cache but kept it, so the added area of the fill layer stayed
transparent. Drop the fill caches whenever the canvas grows, so the fill
re-renders from its model over the whole canvas, as Photoshop does (Free
Transform and Perspective Crop already drop it).

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 10:39:24 -04:00
David MannandClaude Opus 5.5 5f648126c7 Crop and Straighten Photos keeps Duotone inks and is disabled for Indexed Color (#7354) (#7355)
The copies were made with Document::new and kept the mode but not the
duotone inks, so a Duotone scan came out as plain gray. Copy them, and
grey the command out for Indexed Color documents, as Photoshop does.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 10:39:15 -04:00
aiongg 0c36fbc49d Integration: #6035 move the new tests above existing tests' doc comments 2026-10-18 14:19:01 +00:00
aiongg 17e3490142 Merge PR #6035: Image Size: Indexed Color resamples with Nearest Neighbor only (fixes #6019) 2026-10-18 14:18:58 +00:00
aiongg 2437fc36c5 Merge PR #6046: fix(path): validate Make Selection options and guard selection state 2026-10-18 13:23:00 +00:00
aiongg 58d8d911a2 Merge PR #6003: Count: reject unknown groups instead of adding markers to the wrong group 2026-10-18 13:23:00 +00:00
aiongg eef436de45 Merge PR #5654: Count tool: deleting an earlier count group keeps the active group (#5653) 2026-10-18 13:22:59 +00:00
aiongg 785b9478d7 Merge PR #6771: Plug-ins: running a filter plug-in respects layer locks (#6770) 2026-10-18 13:22:59 +00:00
aiongg 45c5f41af0 Merge PR #7046: Render Video: GIF frame delay follows timelines under 1 fps (#7045) 2026-10-18 13:22:58 +00:00
aiongg c9ac35b3b1 Merge PR #7139: Perspective Crop and Rotate Arbitrary keep the Background opaque (#7138) 2026-10-18 13:22:57 +00:00
aiongg cab62b9516 Merge PR #6399: Brush: the Dual Brush tip keeps its proportion at tiny main sizes (#4809) 2026-10-18 13:15:52 +00:00
aiongg d072538dae Merge PR #5992: Retouch: bound every stroke command before its dense buffers (#4961, #6110) 2026-10-18 13:15:51 +00:00
aiongg 4127604122 Merge PR #6502: Engine: reject numeric overflow before document edits 2026-10-18 13:15:50 +00:00
wwew 04967b12f9 docs: write product name as PhotoCraft in engine module doc, LUT export header and image sources (#5454) 2026-10-18 03:35:28 -04:00
NetterSilverandClaude Haiku 5.5 dfb4aa70ae Keep Color Sampler points on their pixel after flips and turns (#6720)
Found while testing crates/engine/src/commands.rs:117-120 (turn) with crates/engine/src/canvas_geom.rs:224 (Turn::affine); applied in crates/engine/src/lib.rs:807-814 (Session::edit)

Co-authored-by: Claude Haiku 5.5 <noreply@anthropic.com>
2026-10-18 03:34:32 -04:00
denstu473 68dfaf1ae6 Check unclamped transform dimensions against the pixel budget (#6493) 2026-10-18 03:34:21 -04:00
David MannandClaude Opus 5.5 b1de6e8ade Preset Manager: renaming or deleting a brush keeps the current preset and names consistent (#5681) (#5682)
Edit > Presets > Preset Manager renamed and deleted brush presets directly, so a rename
left the current preset pointing at the old name (brush.presets.update then failed), accepted
a case-insensitive duplicate name, and a delete left the current preset dangling. It now uses
the same rules as brush.presets.rename / brush.presets.delete through two small shared
helpers (rename_at, forget_deleted_preset). Patterns and custom shapes are unchanged.

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-18 03:33:50 -04:00
f6f53f657d Reject notes.set moves that make the popup non-finite (#6174)
Found while testing crates/engine/src/notes_cmds.rs:105

Co-authored-by: Claude Haiku 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 03:33:40 -04:00
David MannandClaude Opus 5.5 a002e66824 Snapping: layer targets ignore the selection, so a moved selection snaps to guides and the document (#6290) (#6355)
Snap To Layers built each layer's edges and centre from transform_bounds, which clips the
layer to the selection. With a selection, every layer's targets were the selection's own
bounds, so dragging the outline with a marquee snapped it back to where it started (shown
as layer alignments) and beat nearby guides and document edges. Layer targets now use the
layer's whole content bounds, as in Photoshop; the moving box (layer_rect) is unchanged.

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-18 03:33:14 -04:00
NetterSilverandClaude Haiku 5.5 275d4cbcf7 Reject out-of-range leading, baseline shift and scales in type styles (#6196)
Found while testing crates/engine/src/type_cmds.rs:287

Co-authored-by: Claude Haiku 5.5 <noreply@anthropic.com>
2026-10-18 03:33:09 -04:00
FinnNkandClaude Opus 5.5 9ae0f624fc test(type): PostScript names guessed apart from their installed family resolve exactly (#5471)
Covers #5044's two names: BMDoHyeon (family "BM DoHyeon", guessed "BM Do Hyeon") and Daum_SemiBold (guessed "Daum_Semi Bold"). Both resolve to their installed faces and aren't reported missing on main, through the exact PostScript lookup (#1844) that missing-font checks follow since #608.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 03:07:55 -04:00