Commit Graph
3466 Commits
Author SHA1 Message Date
David MannandClaude Opus 5.5 c136d2dd3d Guides: Ctrl/Cmd drags a guide with every tool that moves guides, and the cursor shows it (#6288) (#6294)
With Ctrl (Cmd on the Mac) held over a guide, Photoshop's tools that turn Ctrl into
the Move tool show the guide cursor and drag the guide. Three gaps remained:

- The Frame and Artboard tools showed the guide cursor under Ctrl but took the
  press themselves (a new frame or board), so the guide never moved. Their pointer
  handlers checked the selected tool instead of the effective one.
- The Gradient tool's live mode took a Ctrl press sent with the event (automation,
  `ui.pointer` with `command: true`) instead of handing it to the Move tool.
- With Window > Modifier Keys' latched Ctrl, the press took the guide but the
  pointer kept the tool's own cursor: `command_guide_at`, the decision the cursor
  and the guide context menu use, knew only the selection tools.

`command_guide_at` now covers every `hold_keys::cmd_moves` tool, and not during a
stroke or type editing. Frame, Artboard and live Gradient step aside for a Ctrl
press when they have no drag of their own under way. Tools that keep Ctrl for
themselves (Pen, Hand, Zoom, Crop, type, shapes, slices) and Lock Guides are
unchanged.

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-19 14:20:16 -04:00
David MannandClaude Opus 5.5 5e1d90d86e Preferences: a wrongly typed value resets only that field (#6611) (#6641)
One value of the wrong type in prefs.json (say "recentFileCount": "ten")
made the whole file fail to deserialize, so every preference fell back
to its default for the session, and since the file was never rewritten
it happened again on every launch.

load_prefs_json now reads leniently when the file doesn't deserialize as
a whole: it starts from the defaults and lays the file's values over
them one at a time, descending into section objects and map entries,
keeping every value that still reads and leaving out only the ones that
don't. Colour settings get the same treatment instead of resetting as a
whole. It returns the left-out values (keys and original value); text
that isn't a JSON object still fails as before.

On launch the app shows a notice naming the reset paths and saves once:
the three-way merge base gets the unreadable values, so the merge
replaces (or removes) exactly those in the stored file and keeps
everything else, including keys this version doesn't know.

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-19 14:20:09 -04:00
David MannandClaude Opus 5.5 77b845e098 Actions: loading a set renames each Play target once (#6806) (#6807)
Load Actions applied each rename as its own pass over every action in the set, so chained
renames ("A" -> "A 2", "A 2" -> "A 2 2") retargeted a step twice, and with replace:false it
also rewrote steps of actions already in the set. Now one original->new map is applied once
to the newly loaded actions' Play and Conditional (then/else) steps and to function keys.

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-19 14:20:02 -04:00
MarekSpodBiedry 2cdc8cb73d Content-Aware Fill: preview at full resolution so it matches the result (#6530) (#6838) 2026-10-19 14:19:55 -04:00
FinnNkandClaude Opus 5.5 e3224000ca fix(psd): make packbits::decode_into crate-private and overflow-safe (#6950)
decode_into computed out.len() + expected unchecked. With a non-empty
buffer and an expected length near usize::MAX it panicked in debug builds
and, in release, wrapped and returned Ok having decoded nothing. Compute
the target with checked_add and report LimitExceeded, and test each packet
as len > target - out.len() so the bound checks cannot overflow either.

The only callers are decode and decode_rle in this module, and the crate
README documents packbits::{encode, decode} as the public API, so
decode_into becomes pub(crate).

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 14:19:48 -04:00
David MannandClaude Opus 5.5 bf638a7a22 Crop: Enter during a drag ends the drag (#7055) (#7056)
Pressing Enter while the frame is still being dragged commits the crop; commit_crop now ends the drag, so the release no longer restores a frame in the old document's coordinates as a pending crop.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 14:19:42 -04:00
David MannandClaude Opus 5.5 b67502e20f Reveal All: one History step named "Reveal All" (#7145) (#7147)
image.revealAll ran image.crop, whose step is labelled "Crop". It now
runs it inside analysis_cmds::compound with the label "Reveal All".

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 14:19:35 -04:00
Michael HigginsandClaude Opus 5.5 73b79953e6 Print: Save as PDF gets a Browse… button (#5328) (#7586)
Save as PDF was only a free text field, so saving a PDF meant typing a
full path by hand. Browse… opens the standard Save dialog (pick_save),
suggesting the document's name as a PDF, and puts the chosen path in the
field. file_ui::body now gets the dialog id so the answer can be written
back into the Print dialog on a later frame.

Refs storytold/photocraft#5328

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 14:19:28 -04:00
NetterSilverandClaude Haiku 5.5 5a649f8e4e Apply background jobs to the layers they started on (#7620)
Fixes #7366

Co-authored-by: Claude Haiku 5.5 <noreply@anthropic.com>
2026-10-19 14:19:22 -04:00
David MannandClaude Opus 5.5 2cc107d05f Crop: a ui.pointer click on the frame leaves it alone (#7740) (#7741)
`to_press` and `control_point` read a point in the press-time view through
`remap`, which rounds it through f32 and the view even when the view hasn't
moved. A `ui.pointer` down + up at the same point then came back slightly
apart: the frame moved by a hair and a box Undo step was pushed that undid
nothing visible. Skip the remap while the view is still the press view.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 14:19:15 -04:00
98d3ebc1f5 perf(codecs): Avoid redundant PNG filter passes (#7690)
* perf(codecs): Avoid redundant PNG filter passes

Keep the previous filter path as an exact byte oracle and measure the filter kernel independently of compression.

* docs(perf): measure PNG filtering on real images

* perf(codecs): stop PNG candidates at the winning cost

* Drop the local benchmark report

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-19 14:19:05 -04:00
209a384ef0 Home screen: fit the card and stack the buttons in a narrow window (#5774)
* Home screen: fit the card and stack the buttons in a narrow window

The Home screen's card was a fixed 460 points wide, centred on the canvas, with two
190-point buttons side by side. A tiling window manager can give PhotoCraft a canvas
narrower than that (Hyprland on Omarchy gave a 728-point window, below the 760-point
minimum), so the New document and Open buttons were clipped at the edges.

The card is now as wide as the canvas when the canvas is narrower than 460 points. The
buttons are sized from their labels (so longer translations still fit) and are stacked
when two of them don't fit side by side; the card grows by one row when they stack.

Adds start_screen_buttons_stay_inside_a_narrow_canvas, which fails before this change
(New document starts at x = -1 in a 390-point canvas).

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

* contributors: add TaylorBurke

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

---------

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-19 14:18:33 -04:00
dfe94d6ab9 Actions: export Box Blur, Surface Blur, Flatten Image and Black & White under ids Photoshop runs (#6330) (#6331)
Save Actions wrote these four steps under the four-char codes BoxB, SrfB,
Fltn and BlnW, which Photoshop does not run: executeAction fails with "the
command is not currently available" (measured in Photoshop on a document
with a filled pixel layer; Fltn resolves to the flatness property, not the
flatten event). The names boxblur, surfaceBlur and blackAndWhite and the
code FltI run.

- Box Blur, Surface Blur and Black & White now export by name
  (boxblur, surfaceBlur, blackAndWhite); Flatten Image exports as FltI.
  The descriptors are unchanged.
- Import accepts the names, FltI and flattenImage, and still accepts the
  old codes (a LEGACY_CODES table consulted only for four-char codes), so
  actions PhotoCraft saved before keep loading.
- Tests: each step exports the new id and imports back to the same command
  and params under the new id, the name and the legacy code; a set mixing
  Photoshop-shaped and legacy steps loads and re-saves with the new ids;
  near-miss ids (case, padding, empty, NULs) stay Photoshop steps; broken
  descriptors don't panic.

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-19 14:18:18 -04:00
Vikram BhaskaranandBrandon Thomas 7200f64bf1 Explain the Memory Usage budget and GPU caps (#6546) (#6892)
* Explain the per-document memory limit and automatic GPU budget caps.

* Add catalog entries for the Memory Usage explanation (#6546).

The new preference strings need a row in every complete-menu language
for the i18n tests.

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-19 14:17:49 -04:00
Omar SaidahmedandBrandon Thomas cec0e843e6 refactor(algo): route the splitmix64 copies through one function (#7603)
* route the splitmix64 copies through one function

* document the splitmix64 generators

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-19 14:17:32 -04:00
Ernie SmithandClaude Sonnet 5.5 0b6480cdc5 Address review: adapter override process, wasm build, saved-name warning
- PHOTOCRAFT_GPU_ADAPTER now counts as a per-process launch option, so a
  second launch with it set doesn't forward to the running window and drop it.
- Gate RenderState::available_adapters to native targets (it doesn't exist on
  wasm, which broke the web build).
- Share the adapter name matcher (engine prefs) between startup and
  Preferences, so a saved fragment like "nvidia" isn't flagged as missing when
  it matches a listed adapter.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-19 14:13:55 -04:00
Michael Higgins 5e14702c09 Merge branch 'storytold:main' into fix/6884-pen-tool 2026-10-19 14:08:22 -04:00
aiongg 3f509f06ed Integration: regenerate docs/scorecard.md 2026-10-19 18:01:48 +00:00
2ef8a9d3f5 Reject exporting a partial Play Action call to .atn (#7602)
Fixes #7186

Co-authored-by: Claude Haiku 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-19 13:45:07 -04:00
Korg1chandBrandon Thomas 990483a95e Actions: Apply Layer Comp is recorded by the comp's name, so it plays in any document (#7181) (#7790)
* fix(engine): record Apply Layer Comp by the comp's name, not its document-local id (#7181)

* test(engine): a recorded Apply Layer Comp applies the comp of that name in another document (#7181)

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-19 13:44:51 -04:00
Hector VillarrealandClaude Opus 5.5 f11e51b899 Re-run CI
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 11:34:06 -06:00
Khawar Zamman WaniandClaude Opus 5.5 caa1539030 Tools: Sample All Layers is each tool's own, as in Photoshop (#5524) (#5570)
One ToolOptions::sample_all_layers was shared by twelve tools, so ticking
it for the Magic Wand or Paint Bucket also turned it on for Blur, Sharpen
and Smudge. A dab of those on an annotation layer then pasted an opaque
copy of the composite there, which sat above the picture and hid every
later blur of it (the sharp round patch in #5524).

ToolOptions::sample_all is now a SampleAllLayers with one flag per tool,
all off by default: Magic Wand, Quick and Object Selection (and Object
Finder), Paint Bucket, Magic Eraser, Spot Healing, Patch, Content-Aware
Move, Remove, Blur, Sharpen and Smudge. Each options bar and command
reads its own tool's flag, and Reset Tool resets only that tool's. The
new key means older preferences drop the shared flag, so every tool
starts with it off.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 13:33:14 -04:00
FinnNkandClaude Opus 5.5 8c3c3237f4 refactor(engine): drop the second damage pass after single-edit duplicates and pastes (#5880)
Session::edit has reported a new layer's bounds as damage since #3847, so
Duplicate Layer, Duplicate Layers and Paste no longer need to keep the
document from before the edit and work the damage out again with
note_insert. Paste Into still does: it is three edits.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 13:33:06 -04:00
Omar Saidahmed 1c74da7baa keep layer-based slices when duplicating a document (#6133) 2026-10-19 13:32:59 -04:00
David MannandClaude Opus 5.5 3cafc63656 Free Transform: W and H take a typed length in percent view (#6284) (#6293)
W and H showing percent rejected a typed length ("40px", "2 in", "5cm"),
since the shared unit parser has no length to convert a percentage
against. The fields now carry the box's original side as the length 100 %
stands for, so a typed length becomes that share of it at the document's
resolution, and "50%" typed into a length view is half the original side.
The field keeps its chosen unit; the aspect link and the scale limits
apply as before. Other percent fields (opacity, fill) still reject lengths.

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-19 13:32:51 -04:00
Josephand00xJ0 80cd36846e GPU canvas: redraw mip levels of 128 px or less whole; fixes AMD Vulkan corruption after partial updates (#6418) (#6438)
* GPU canvas: redraw mip levels of 128 px or less whole; AMD Vulkan corrupted them after partial updates (#6418)

On an RX 6900 XT with Vulkan (driver 26.8.1), a regenerate_mips pass that drew only the damaged
part of a small mip level corrupted texels outside its scissor, at the level's top-left; DX12 on
the same card was fine. A sweep of 15 canvas sizes x 8-bit/float x 6 dabs found 66 of 180 cases
corrupt on Vulkan, every corrupted level at most 64 px; sampling the levels in a shader shows the
same pixels, so it is the texture contents, not the readback.

mip_damage (regenerate_mips's damage arithmetic) now redraws levels of at most WHOLE_MIP_SIDE
(128 px) whole on every adapter: no new passes or textures, only a larger scissor on the
smallest levels. With it the sweep is clean on both backends.

Tests: small_mip_levels_are_redrawn_whole (the rule, no GPU) and
partial_updates_match_a_full_rebuild_at_every_mip_level (fails on the Radeon over Vulkan without
the change, passes with it, like canvas_mips_preserve_opaque_pixels_and_partial_updates).

* contributors: add 00xJS (GitHub profile name)

* Mip sweep test: hold the GPU lock like the other GPU tests (#6438 review)

---------

Co-authored-by: 00xJ0 <56056764+00xJ0@users.noreply.github.com>
2026-10-19 13:32:42 -04:00
Marco Arnulfo a012894505 Layer Mask Properties: the Feather slider has fine steps near 0 px, as in Photoshop (#6545) (#6727) 2026-10-19 13:32:35 -04:00
MarekSpodBiedry daa9abfe43 Disable Save for Web while a Free Transform is pending (#6737) (#6832) 2026-10-19 13:32:29 -04:00
Vikram Bhaskaran e1c209eadb Hide the Hyphenate checkbox until layout hyphenates (#6710) (#6869)
* Hide the Hyphenate checkbox until layout actually hyphenates (#6710).

The flag still round-trips to PSD and ⌥⇧⌘H still toggles it, but the
Paragraph and Properties panels no longer advertise a no-op.

* Assert Hyphenate stays hidden until layout hyphenates (#6710).

The Paragraph properties and paragraph style options editor now have
tests that fail if the no-op control is shown again.

* Drop the duplicate #[test] on hyphenate_is_not_shown (#6710).

Clippy -D warnings failed on duplicate-macro-attributes and unused mut
on the harness that only queries the label.
2026-10-19 13:32:21 -04:00
Korg1ch 8c2cfdba6a fix(linux): keep crash-recovery autosaves in $XDG_STATE_HOME, out of the config folder (#6612) (#6941)
* fix(linux): keep crash-recovery autosaves in $XDG_STATE_HOME, out of the config folder (#6612)

* test(linux): cover the XDG state recovery folder (#6612)
2026-10-19 13:32:14 -04:00
David MannandClaude Opus 5.5 4252fb7743 Presets: Migrate Presets reads tool presets like the launch loader (#7375) (#7376)
Migrate Presets deserialized the incoming tool presets all or nothing, so
one damaged entry dropped every good one, and it skipped the checks the
launch loader applies: blank or overlong names were imported and then lost
on restart, and a large file pushed the library past MAX_PRESETS. Read the
list with `presets::tools::load` and stop at MAX_PRESETS.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 13:32:07 -04:00
zrsix1andClaude Opus 5.5 c78ec47f4b Type: a click inside the paragraph box off the glyphs moves the caret (#7606)
While editing paragraph text, a click inside the box that missed the glyphs
(left of a line's start, or below the last line) committed the edit. Like
Photoshop, it now places the caret at the nearest position; a click outside
the box still commits.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 13:31:56 -04:00
NetterSilverandClaude Haiku 5.5 fe64e5a2bd Keep arbitrary canvas rotation interpolation in ATN export (#7630)
Fixes #7504

Co-authored-by: Claude Haiku 5.5 <noreply@anthropic.com>
2026-10-19 13:31:49 -04:00
David MannandClaude Opus 5.5 cbe6c4e131 Guides: colours follow Crop, Canvas Size and rotations (#7724) (#7725)
transform_doc_marks moved guide positions but not their colour entries,
which are keyed by exact position: after a crop the coloured guide fell
back to the preference colour and a guide that landed on its old place
took the colour. Map the entries with the same expressions as the
guides (swap orientation on quarter turns, clear on arbitrary angles).

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 13:31:41 -04:00
Khawar Zamman WaniandClaude Opus 5.5 81d5c514e2 perf(photomerge): build multi-band bands in bounded batches (#7758) (#7763)
* perf(photomerge): build multi-band bands in bounded batches (#7758)

panorama::multiband built every source's Laplacian and weight pyramids
at once and kept them all until accumulation, so Photomerge's peak
memory grew with the image count (~5 GB for four 24 MP sources). Build
the bands in batches under a 2 GiB estimate (at least one image) and
add each batch before building the next. Each pixel still adds the
bands in image order, so the output is bit-identical.

Planes now move into the pyramids instead of being cloned, and the
coverage and push-pull masks are freed once used: with one image built
at a time those clones were serial passes. seam_blend shifts its ROIs
in place instead of cloning every source for the blend.

Peak during the blend: 4x24 MP 5.04 -> 2.81 GB, 8x12 MP 5.50 -> 2.61 GB,
at +5% time (2x24 MP unchanged). New example bench_multiband measures it.

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

* perf(photomerge): let up to four 24 MP sources build their bands together

A 2 GiB band budget built every 24 MP source alone, which made the usual
2-4 image Photomerge 12-36% slower on a 32-thread desktop. The native
budget is now 5 GiB, so those still build side by side and only larger
stacks are batched. wasm32 has no threads, so it adds each band as soon
as it is built.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 13:31:33 -04:00
5b1f937287 Actions: a function key can take Shift and Command (#7646) (#7674)
Photoshop's Action Options pair the function key with Shift and Command
checkboxes. The Actions row menu offered plain F-keys only, and .atn import
dropped any key with a modifier. The row menu gets Shift and Command (Ctrl off
the Mac) toggles, and .atn load and save keep the modifiers.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-19 13:31:26 -04:00
acb3a66fd5 Crop: a preset picked in Tool Presets fits the frame to its ratio (#7143) (#7144)
select_tool_preset only merged the preset's tool options, so a crop
preset's ratio was set without refitting the frame; the options bar's
dropdown already refits. It now compares the ratio before and after and
calls crop_ui::apply_ratio when it changed, as the dropdown does.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-19 13:31:10 -04:00
7b99c32923 Color Lookup: warn when it opens without its usable table (part of #1763) (#6322)
* fix(io): warn when a Color Lookup opens without its usable table

A Color Lookup whose table is shorter than its size-cubed grid (or
missing altogether) silently rendered as the identity. Native .pcraft
loads and native-path automation opens carried no import warnings, so
the loss was silent. The new io::open_warnings reports it naming the
layer, and both load seams surface it.

Also add a Photoshop-layout clrL regression test: a version-1 block
carrying a CUBE descriptor round-trips through PSB and reopens as a
working Color Lookup that recolors a red backdrop (#1763).

* cargo fmt

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-19 13:30:56 -04:00
aiongg acc853b517 Merge upstream main into integration-5
docs/file-format-parity.md: keeps both 2026-10-15 rows, main's #6063 PNG sTER row first, then #5976's PSD guides row
2026-10-19 17:20:29 +00:00
aiongg b8e856dffe Integration: #7022 write_region stays serial; the text rasterizer uses the parallel variant 2026-10-19 17:18:46 +00:00
Ernie SmithandClaude Sonnet 5.5 b892c0412e Choose the graphics adapter on multi-GPU Linux machines (#1885)
With the backend on auto, Linux installed no adapter selector, so wgpu took
the first discrete GPU the driver listed. On a machine with an eGPU, a
laptop dGPU and an iGPU that was not the one the user wanted.

- performance.gpuAdapter: a case-insensitive piece of the adapter's name
  (several words must all appear); empty is automatic.
- PHOTOCRAFT_GPU_ADAPTER overrides it for one launch.
- Preferences > Performance > Advanced > GPU adapter lists the adapters the
  instance found; a saved adapter that is absent stays listed, marked.
- A missing adapter, or one that can't present to the window, falls back to
  the automatic choice with a note in System Info. CPU mode ignores it.
- Shown on Linux only; tested on Linux only. Windows and macOS need their own
  work (the startup code honours the setting there, but it is untested).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-19 13:16:53 -04:00
125d15961e Actions: Smart Blur quality uses Photoshop's SBQL/SBQM/SBQH codes (#6312) (#6313)
A Smart Blur step saved to .atn wrote its quality as `Hgh `/`Mdm `/`Low `,
codes Photoshop ignores (it plays them back with its fallback, low
quality), and Photoshop's own `SmBQ` values did not import as a quality.

The quality table now writes `SBQL`/`SBQM`/`SBQH` (enum type `SmBQ`) and
reads both those and the old `Hgh `/`Mdm `/`Low `, so actions PhotoCraft
wrote before still load. Unknown codes stay kept-verbatim Photoshop steps.

Measured in Photoshop by rendering: Smart Blur applied through Action
Manager with each code; SBQH and SBQM each give a distinct image, while
`Hgh `, `Mdm `, `Low `, a nonsense code and SBQL give the identical
(fallback) image. The smart filter Photoshop saves uses the same SmBQ
values (applied through Action Manager to a smart object, saved, filterFX
read back).

Tests: every quality round-trips through .atn as SBQ*; Photoshop-shaped
SmrB steps and the legacy codes import as the right quality; unknown,
wrong-type, NaN and missing values never panic; Smart Blur joins the
export/import round-trip table.

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-19 13:08:50 -04:00
Hector VillarrealandClaude Opus 5.5 f4a81d3c16 Add Liquify's Show Backdrop Use and Mode (#7609) (#7644)
* Add Liquify's Show Backdrop Use and Mode (#7609)

Show Backdrop only had an Opacity: it always drew the other layers'
composite behind the image, faded. Photoshop's Liquify also has Use (All
Layers, or one layer) and Mode (In Front, Behind, Blend). Use and Mode
are now in the View Options and the control channel (backdropUse,
backdropMode), and each mode layers the backdrop and the image as
Photoshop's does: In Front lays the backdrop over the image at the
Opacity, Behind the image over the backdrop, Blend fades the image to
100 - Opacity under the backdrop. All Layers is the document as it is,
as in Photoshop.

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

* Liquify backdrop: show a hidden Use layer, forget the layer between documents

A layer picked in Use, or one in a hidden group, now shows: the
one-layer backdrop makes it and its groups visible, as Photoshop does.
The remembered settings keep the backdrop Mode and Opacity but no longer
a layer id, which belongs to one document: Liquify opens on All Layers,
as Photoshop's does.

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

* Liquify backdrop: a control-channel Use naming no layer is All Layers

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

* Liquify backdrop: normalize an unknown Use even when another setting fails

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 13:04:59 -04:00
David MannandClaude Opus 5.5 5c1b9425d1 Gradient Overlay: repaint the whole layer when a stroke moves its frame (#4053) (#5340)
A Gradient Overlay (and gradient strokes, linked patterns, linked bevel
textures) is laid out over the layer's bounds. Painting grows those
bounds, but the stroke commands reported only the stroke as damage, and
the canvas's committed-stroke path repainted only what the live stroke
had not shown. The canvas therefore kept the old gradient around earlier
strokes: one gradient per stroke instead of one over the whole layer.

Session dispatch now grows a command's reported damage by
photocraft_compose::relayout_damage(before, after): the whole composite
of any visible layer whose effects span its frame, when that frame moved.
The canvas adds the same area to a committed live stroke. Strokes inside
the existing bounds and frame-independent effects keep tight damage.

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-19 13:04:52 -04:00
David MannandClaude Opus 5.5 bc4f2a783d Scripts: Flatten All Layer Effects bakes with the document's Global Light and patterns (#5719) (#5720)
`bake` rendered each layer through `render_layer`, which has no document context (default Global
Light, no patterns, the render rect as canvas). It now uses the new doc-aware `render_layer_in`.

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-19 13:04:46 -04:00
Michael HigginsandClaude Opus 5.5 ac78e0bbca Color Settings: the working-space menus list the installed profiles of their space (#6323) (#6359)
* Color Settings: the working-space menus list the installed profiles of their space (#6323)

Like Convert to Profile and Proof Setup, Edit › Color Settings' RGB, CMYK and Gray working-space
menus now list the profiles installed in the system after the built-ins, by description, so a
press condition (GRACoL, SWOP, FOGRA39) can be the CMYK working space as in Photoshop. Each menu
lists only profiles of its colour space; RGB lists display and colour-space profiles, not
scanner or printer ones. The engine already took .icc paths for these settings.

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

* rustfmt: format after merging main (CI Format step)

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 13:04:38 -04:00
Perchenko Anton bf321f0f5e Affinity: bound compound geometry expansion and reject cycles (#6486)
* fix(affinity): bound compound geometry expansion and reject cycles

* fix(affinity): charge every compound operand slot before filtering
2026-10-19 13:04:32 -04:00
David MannandClaude Opus 5.5 566dbb3f83 Histogram: count the document's pixels, not a blurred thumbnail (#6538) (#6724)
The Histogram panel binned a 384 px area-averaged thumbnail, so the cell
holding a black/white edge came out mid-grey: a spike near 128 and
slightly wrong Mean/Std Dev. Documents up to PROXY_THUMBNAIL_PIXELS are
now composited in bands and every pixel is counted (Cache Level 1, as
Photoshop's uncached histogram); larger ones count a nearest-neighbour
proxy composite, which only holds values the layers have, and report its
cache level. The Pixels row shows the pixels actually counted.

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-19 13:04:26 -04:00
codexistence 7efaa50455 fix: match Photoshop Gradient Fill scale limits (#6797) 2026-10-19 13:04:20 -04:00
Vikram Bhaskaran 4bf0625b04 Export and import saved tool presets (#6560) (#6889)
* Include tool presets in export and import so crop presets are portable.

* Pass presets_io_body to the harness without a redundant closure (#6560).

CI clippy -D warnings failed on clippy::redundant_closure in the
presets export/import regression.
2026-10-19 13:04:14 -04:00