Commit Graph
3466 Commits
Author SHA1 Message Date
skylar taylor-barrick fd1faf4e13 Merge pull request #6381 from dylanmtaylor/ci/shared-freebsd-workflow
ci: delegate FreeBSD workflow to craft-common-actions and add nix check
2026-10-15 21:34:22 -06:00
skylartaylor 8f58ea4629 ci: integrate current main while retaining strict test gates 2026-10-16 05:25:24 +02:00
Higgs32584andClaude Opus 5.5 5c0e40784a rustfmt; merge main (CI Format step)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 22:29:42 -04:00
Higgs32584 36236b908a Merge remote-tracking branch 'origin/main' into fix/4962-web-save-dirty 2026-10-15 22:28:59 -04:00
Higgs32584andClaude Opus 5.5 5f8d92f9f0 rustfmt; merge main (CI Format step)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 22:27:09 -04:00
Higgs32584 ad620fba50 Merge remote-tracking branch 'origin/main' into fix/5884-delay-load-windows-data-pdf 2026-10-15 22:26:18 -04:00
Higgs32584andClaude Opus 5.5 c972acf98f rustfmt: format after merging main (CI Format step)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 22:16:12 -04:00
Higgs32584andClaude Opus 5.5 76f29f67d5 rustfmt: format after merging main (CI Format step)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 22:16:03 -04:00
Higgs32584 3eef8af459 Merge remote-tracking branch 'origin/main' into fix/help-search-arrows-space 2026-10-15 22:09:43 -04:00
Higgs32584 f46bd2a907 Merge remote-tracking branch 'origin/main' into fix/1056-path-selection-canvas-menu 2026-10-15 22:09:40 -04:00
Dylan M. Taylor 0cc161e2a8 ci: delegate FreeBSD workflow to craft-common-actions and add nix check 2026-10-15 22:04:09 -04:00
Higgs32584andClaude Opus 5.5 7bc556ec02 Web: a requested download no longer marks the document saved (#4962)
The web writer only triggers an anchor download, which the user can cancel
and which reports nothing back, yet Save treated it as a confirmed write:
the document was marked clean and the leave-page guard disarmed. Services
gains `write_unconfirmed` (set by the web shell); a save through such a
writer names the document but keeps its changes unsaved.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 21:54:09 -04:00
jonathnwxh f22d08da8c style: apply rustfmt to upstream smart-filter regression 2026-10-16 09:32:47 +08:00
David MannandClaude Opus 5.5 a9ba86898e PSD: Hue/Saturation smart filters open and save like Photoshop (#6267)
A Hue/Saturation smart filter (Fltr class HStr, filterID 'HStr') opened
as psd.unsupportedFilter, so re-rendering the smart object (replace
contents, relink) dropped it, and a Hue/Saturation smart filter made in
PhotoCraft was left out of PSD saves.

The new hue_sat_filter_map module maps Photoshop's layout (measured on a
Photoshop-saved file: presetKind, Clrz, Adjs = master Hst2 {H, Strt,
Lght} plus one Hst2 per edited range with LclR, BgnR, BgnS, EndS, EndR,
all longs) to image.adjustments.hueSaturation with the command's params,
and writes it back in that layout. Anything outside it (unknown keys,
duplicate or unknown ranges, values the dialog can't hold) stays an
opaque unsupported filter written back verbatim. smart_map.rs only gets
the dispatch hooks.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-16 03:18:42 +02:00
David MannandClaude Opus 5.5 b59b0cd1d5 PSD: Perspective Warp smart filters open and save like Photoshop (#6268)
A Perspective Warp smart filter (Fltr class perspectiveWarpTransform) fell
through to psd.unsupportedFilter, so a re-rendered smart object (e.g. after
Replace Contents) lost its perspective. Each quad now becomes a plane of
layer.smartObjects.perspectiveWarp (src = vertices, dst = warpedVertices),
and PSD save writes Photoshop's descriptor back: the original item with
only the geometry rewritten, or a new item in the layout Photoshop writes.
Inconsistent descriptors stay opaque, as before.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-16 03:18:18 +02:00
PerchenkoAnton 3cb3580a46 fix(psd): recover layered imports with damaged merged data (#6170) 2026-10-15 19:14:05 -06:00
jonathnwxh 7bff38c176 Merge remote-tracking branch 'origin/main' into repair/5413-upstream-schema 2026-10-16 09:13:54 +08:00
David MannandClaude Opus 5.5 0ce7e00fee Brush: the Dual Brush tip keeps its proportion at tiny main sizes (#4809)
Photoshop scales the Dual Brush diameter by the main size ratio, unrounded,
with a 5000 px cap and no lower floor: main 100 / dual 20 resized to 1 px
gives a 0.2 px dual tip, and back at 100 px it is 20 px again. PhotoCraft
already scaled the dual tip (#4856) but floored it at 0.5 px, so a trip to
1 px and back left a 50 px dual tip. Drop the floor (rendering bounds the
diameter itself), keep the cap, and leave a non-positive or NaN result alone.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-16 03:06:07 +02:00
Higgs32584andClaude Opus 5.5 cf3d26881d Windows: start without Windows.Data.Pdf.dll; delay-load it for printing (#5884)
winprint's PdfCreateRenderer made photocraft.exe and photocraft-cli.exe
import Windows.Data.Pdf.dll at load time, so Windows images without the
PDF component (NTLite-stripped, some N/LTSC builds) refused to start:
"windows.data.pdf.dll was not found". Both apps now delay-load it, and
PDF printing checks the DLL is installed first and reports an error
instead of aborting.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 20:41:08 -04:00
skylar taylor-barrick b630d7cb9a Security: enforce the automation read boundary on stored smart-filter params (#5684)
* Security: enforce the automation read boundary on stored smart-filter params

Smart filters store a command id and params inside the document, so a
crafted .pcraft could resolve stored ambient paths (displace mapPath,
color-lookup LUT files) at render time without the authorize gate:
synchronous smart-object rendering has no Session argument, spawned job
threads drop the requesting session's authority, and a trusted UI
render could warm the process-wide stack cache with a stage the gate
never saw.

Thread the requesting session's gate through a thread-local in jobs.rs
(entered around command execution, reinstalled on spawned workers and on
job apply, restored on drop), re-check stored filter commands in
render_in before source resolution and any cache lookup and in
apply_filter_to_surface, and re-authorize layer.smartFilter.setParam
with the complete merged params after merging stored document params —
including edits whose request contains no path and actions.play steps.
Denial is conservative: the document is unchanged and no worker starts.

Regression tests in the automation crate cover rerender, cached-stage
reuse, merged-param edits, action playback, background jobs, and filter
transfer, for both the displace map and color-lookup LUT vectors.

* Review: carry render authority through Session::edit and surface denials

Session::edit is the engine's universal mutation entry (linked-file
refresh, transforms, place); renders reached through it ran with no
authorization scope installed, so stored smart-filter params read
ambiently there even after the execute-path fix. Install the scope in
Session::edit as well, keyed on the session's authorize gate.

apply_filter_to_surface also conflated a denied filter with an unknown
one: authorize_render_command's Result was discarded with .ok()?, so a
denial silently rendered unfiltered pixels and the stage entered the
process-wide stack cache. The signature becomes
Result<Option<Surface>>: Err for denials (callers propagate), None for
ids that aren't pixel filters (pass-through semantics unchanged), and an
unreadable map keeps its deliberate pass-through. apply_smart_filter and
apply_smart_filters propagate instead of cur.clone().

Two tests: a refresh driven outside command execution (Session::edit)
is denied and leaves the document unchanged; a denied render caches no
stage. Nine authorization tests pass, engine suite and strict Clippy
clean, panic hunt green.

* ui_set description: name the layerFilter field main added

The Layers-panel filter dropdown (#5048) added layerFilter to the
control method's UI_SET_FIELDS without extending the ui_set tool's
description, so the drift guard (mcp.rs, ui_set_description_names_every_
accepted_field) fails on Windows CI for any branch built on main. Name
the field with its patch shape: by (kind, name or mode), kinds (a list
of layer kinds), name (matches part of the layer name), mode (a blend
mode name). The 40 MCP tests pass; strict Clippy clean.

* wasm size: compose and algo join cms at opt-level "z" to clear the 24 MiB gate

Main's browser build is 171 KB over the 24 MiB wasm gate in CI
(25,337,343 bytes), so every branch built on current main fails the
web size check. Demote three pixel crates in the wasm-release profile:

- cms: transforms are built once and cached (transform::cached),
  so "z" costs little runtime (measured -42 KB).
- compose: the browser app composites per frame on the GPU (crates/gpu,
  WebGPU/WebGL2), so the CPU compositor is not the browser's hot path
  there (measured -122 KB).
- algo: filters run per pixel on every interactive filter application.
  "z" there is the lever of last resort (measured -331 KB), taken
  because the gate is enforced in CI; if browser filters feel too slow,
  restore 3 here and reclaim the bytes from a feature-gated decoder.

Final size 24,811,949 bytes locally, 354 KB under the gate; the native
profiles are untouched. If a maintainer prefers keeping algo at 3, the
alternative is raising the self-imposed 24 MiB early-warning limit
(Cloudflare's hard cap is 25 MiB) - a policy call in packaging/web/
package.sh, not a code fix.
2026-10-15 18:19:26 -06:00
itsF4LCON dedf89feae Type: move the caret test next to its helper, away from the file's end 2026-10-16 01:33:24 +02:00
Higgs32584andClaude Opus 5.5 8237d76170 Help search test: take the first Levels result and move the cursor to the end
"levels" now finds two results (Image › Adjustments › Levels… and the
Levels adjustment layer), and one → only reaches level|s, so the test
needs a second → before typing the space.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 19:28:58 -04:00
itsF4LCON 67602017f8 Merge remote-tracking branch 'upstream/main' into feat/6278-replace-color-result
# Conflicts:
#	crates/ui-egui/src/i18n/ko.tsv
#	crates/ui-egui/src/i18n/zh-hans.tsv
#	crates/ui-egui/src/i18n/zh-hant.tsv
2026-10-16 01:21:27 +02:00
David Mann ee0da9fbac Merge main into docs/scorecard-stale-rows-2
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

# Conflicts:
#	docs/scorecard.md
2026-10-16 01:13:28 +02:00
David Mann e6d636ce75 Merge main into feat/ui-217-4-animated-zoom
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

# Conflicts:
#	docs/scorecard.md
2026-10-16 01:02:02 +02:00
David Mann 9aa9064ce9 Merge main into feat/ui-217-4-save-in-background
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

# Conflicts:
#	docs/scorecard.md
2026-10-16 00:54:47 +02:00
David Mann d94381edf0 Merge remote-tracking branch 'origin/main' into feat/tool-214-12-shape-feather
# Conflicts:
#	docs/scorecard.md
2026-10-16 00:46:45 +02:00
David MannandClaude Opus 5.5 f4f1065785 Move tool: Ctrl-hover shows the distance to another layer (#4946)
With Smart Guides on and the Move tool, holding Ctrl (Cmd on the Mac) with no
button down and hovering another layer now draws magenta measurement lines
with labels between the selected layers' bounds and that layer's: the gap
between facing edges, or the distances between corresponding edges on an axis
where the two overlap. Over empty canvas, the Background or the selection
itself the distances run to the canvas edges. While an Alt-drag duplicates the
layers, the distance from the original to the copy shows.

Overlay only: no command, no history state. The hovered layer is the
Auto-Select hit test, cached per document revision, selection and pointer
pixel. Values use the ruler unit.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-16 00:46:44 +02:00
itsF4LCON a76d3295ba Type: the text caret shows on a white background (part of #6085)
In the Photoshop themes the caret was a 1.5 pt white line with a 0.75 pt
black hairline inside, so on a white background only a faint grey line
showed. It is now a solid 1 pt black line inside a 3 pt white halo, readable
on any background, in every theme.
2026-10-16 00:38:48 +02:00
Tanish Manku 2b3d05636f Follow-ups to #5801/#5859: precheck docs name the group nesting limit; precheck test covers valid ids and null; Free Transform refusals translated; stranded-backup error tested (#6365) 2026-10-15 15:32:08 -07:00
Higgs32584andClaude Opus 5.5 944d0cf722 Path Selection: the canvas right-click opens the path menu, as the Pen's does (#1056)
The Path Selection and Direct Selection tools had no canvas menu, so Make Selection, Fill and
Stroke Path, Free Transform Path, Delete Path and the combine and style rows needed the Pen tool
or the Paths panel. Like Photoshop, their right-click now opens the path menu for the selected
path, with the Pen menu's rows and the same enablement.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 18:27:56 -04:00
itsF4LCON 0752cd8365 Merge remote-tracking branch 'upstream/main' into feat/6278-replace-color-result
# Conflicts:
#	crates/ui-egui/src/i18n/ko.tsv
#	crates/ui-egui/src/i18n/zh-hans.tsv
#	crates/ui-egui/src/i18n/zh-hant.tsv
2026-10-16 00:27:03 +02:00
Tanish Manku fdaa9b8316 Free Transform: New Layer applies the transform first; tests for switching tool, layer or New Layer (#5665, #5667) (#5859)
* Tests: Free Transform applies when another tool, layer or New Layer is picked (#5665, #5667)

Whole-app regression tests for the paths #5665 and #5667 report (a moved box, and a placed image moved or untouched; toolbar tool click, Layers panel row click, New Layer). They pass on main, so these issues don't reproduce here; the tests pin Photoshop's behaviour (apply, never silently cancel) while the reporters narrow down their environment.

* New Layer while Free Transform is open applies the transform first (History: the transform, then New Layer; a placed layer's step no longer swallows New Layer); tests: a real placed fixture, the New Layer button, History order

* Free Transform + New Layer: check the command can run before applying the transform; a refused transform keeps the box and the placed layer and returns the refusal

* Free Transform + New Layer: Session::precheck validates the call (params shape, New Layer's values, precondition) before the box is applied

* Free Transform + New Layer: commit-first only for PRECHECKED commands; precheck shares dispatch's admission checks (named-layer precondition, jobs, hidden target) and checks a named layer exists; Group from Layers documents its layer param; test: precheck agrees with execute

* Free Transform + New Layer: precheck resolves a layer given by name as the command does; Layer via Copy/Cut leave PRECHECKED (their failures show only when they run); rebased (lib.rs mod lines)
2026-10-15 15:26:13 -07:00
Tanish Manku 8376363d8c Shortcuts without the UI: shell commands and tools in the engine, reserved keys, set files (#5801)
* Shortcuts without the UI: shell commands and tools in the engine, reserved keys, set files

A retro against "everything is drivable headless; the UI is a layer" found the shortcut
work leaning on the UI. Fixed:

- The shell's command table (UI_COMMANDS: View › Zoom In, the panels…) moves into the
  engine as data (shell_commands::SHELL_COMMANDS; menus::UI_COMMANDS is now an alias), with
  the tools (shell_commands::TOOLS: id, Photoshop name, default key; a test keeps it equal
  to the toolbar). edit.keyboardShortcuts binds, lists, imports, exports and checks them for
  conflicts with no UI: a headless .kys import takes tool keys, and an export is the whole set.
  Tools may share a key (Shift cycling); a command given a tool's key takes it from them.
- Reserved system keys are refused by the engine (the dialog uses the same check) and left
  out of imported sets.
- importKys / exportKys / summarize take {"path": …} to read or write files, like
  actions.save; the automation gate refuses those paths for MCP.

* Keyboard shortcuts: bindings commit only after their exports are written (a bad target changes nothing); docs: the command knows the tools

* Keyboard shortcuts: both export targets are resolved and checked before either file is written; a failed later write removes the earlier one

* docs: reserved keys per platform

* Keyboard shortcuts export: files are staged next to their targets and moved into place only when all are written; a failure leaves the user's files untouched

* Keyboard shortcut exports all or nothing: unique create_new staging and backups beside each target, restored on any failure; the same target twice refused; docs: Ctrl+Shift+Esc

* Shortcut exports: a failed backup move removes its placeholder; undo restores backups without deleting first and names any it couldn't; read-only targets refused up front
2026-10-15 15:26:09 -07:00
Tanish Manku b09aeae2e2 Low-Latency Drawing preference (vsync off, opt-in), plus present-mode overrides for testers (#1343) (#5274)
* PHOTOCRAFT_PRESENT_MODE / PHOTOCRAFT_FRAME_LATENCY launch overrides for latency A/B tests (#1343)

Linux traces put the remaining stroke latency in presentation queueing (the app's frame fits
in one refresh; the rest waits on the swap chain), and an NVIDIA user cured it by forcing
vsync off. Testers can now compare without rebuilding: PHOTOCRAFT_PRESENT_MODE (fifo,
mailbox, immediate, autovsync, autonovsync) and PHOTOCRAFT_FRAME_LATENCY (1-3), applied over
the platform default. Bad values are logged and ignored. The active mode is logged at start,
kept in GpuInfo.present and shown in every stroke trace line.

RTX 5090, Vulkan, 120 Hz: default autovsync/latency 1 waits 6 ms per 8.3 ms frame; mailbox
renders every 2.3 ms with 0.2 ms wait, so each vsync shows the newest frame.

* docs: present mode and frame latency overrides

* Present mode overrides never request a mode the driver may lack (Mailbox crashed on X11/NVIDIA)

* Preferences › Performance › Low-Latency Drawing, on by default: no vsync wait while drawing (#1343)

Testers' traces put the ~50 ms stroke lag in the swap chain's vsync wait. With vsync off it
disappears (X11/NVIDIA; Windows/NVIDIA via the driver), and a shorter queue doesn't help. The
surface now presents with AutoNoVsync by default: wgpu picks Immediate, else Mailbox, else
Fifo by what the driver offers, so no unsupported mode is ever requested. Turning the
preference off brings vsync back (smooth, but late). It applies at the next launch, like the
other graphics settings. Where only Immediate exists the canvas may tear while it changes;
frames render as fast as input arrives during a stroke and stop when idle.

* Runtime and launch controls: ui.set strokeTrace, --stroke-trace, --present-mode, --frame-latency

The retro's remaining gap: the stroke trace and the presentation overrides were env-only. The trace now switches at runtime over the control channel (ui.set {"strokeTrace": true|false|"stderr"|path}) and with --stroke-trace; --present-mode / --frame-latency sit beside their env vars and win over them. Bad values are reported and ignored; none fails a launch.

* docs: strokeTrace beside the other ui.set fields

* docs: regenerate the scorecard after rebasing onto main

* Low-Latency Drawing: frames capped at 240 fps without vsync (after #4472); ui.set strokeTrace takes on/off only (a CSV path is launch-only); tearing documented as usual (Immediate); pref hidden on web; doc comment order

* stroke_trace: from_value doc says ui.set passes only on or off

* Low-Latency Drawing ships off (opt-in), per the owner's decision

* docs: present mode default is vsync (Low-Latency Drawing ships off)
2026-10-15 15:23:47 -07:00
itsF4LCON c04da11bb3 Smart filters: rustfmt 2026-10-16 00:16:06 +02:00
itsF4LCON 8176b613ca Smart filters: the dialog's preview window shows the edited filter too (#6324)
The filter dialog's own preview window matched the canvas preview by the
filter's command, but an edited smart filter previews as
layer.smartFilter.setParams, so the window kept the unfiltered image.
smart_ui::preview_command names the command the preview runs.
2026-10-16 00:15:50 +02:00
itsF4LCON 8369b4b55c File › Open: a file type filter per format (#6337)
After All Formats the Open dialog listed only PhotoCraft, OpenRaster, Krita,
SVG and Paint.NET. Every format it reads now has its own filter (Photoshop,
Affinity, HEIF, PNG, JPEG, TIFF, Portable Bitmap, OpenEXR, Radiance HDR,
camera raw, …), and together they cover All Formats exactly.
2026-10-16 00:04:50 +02:00
skylar taylor-barrick af54f4aed1 ci: limit the font pass to font-consuming crates (#6127)
Keep the full workspace and feature build selection while running the craft-fonts pass only for text, io and ui-egui. Ordinary workspace test coverage remains in the first test pass.
2026-10-15 16:03:39 -06:00
skylar taylor-barrick 1a4167936e ci: run tests with cargo nextest (#6118)
Run workspace tests with pinned cargo-nextest, a CI fail-fast profile and a ten-minute per-test hang limit. Keep cheap checks first and doctests in a separate step so failures propagate on Windows.
2026-10-15 16:02:57 -06:00
itsF4LCON f71eb28f84 Replace Color: a Result swatch whose Color Picker sets the sliders (#6278)
Photoshop's Replace Color shows the replacement colour under the Hue,
Saturation and Lightness sliders; picking one in its Color Picker sets the
sliders so the sampled colour becomes it. tone::replace_color_result gives
the swatch (the sampled colour after the shift) and tone::replace_color_shift
the slider values for a picked colour.
2026-10-15 23:55:58 +02:00
David Mann bbd5ed2205 Merge remote-tracking branch 'origin/main' into fix/5559-bitmap-dialog
# 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/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
2026-10-15 23:52:42 +02:00
David Mann ce19e018b9 Merge remote-tracking branch 'origin/main' into feat/tool-214-12-shape-feather
# Conflicts:
#	docs/ui-parity.md
2026-10-15 23:52:29 +02:00
itsF4LCON 0504331c51 Smart filters: an edited filter's dialog previews again (#6324)
Re-opening a smart filter dropped the dialog's Preview, since the live
preview ran the filter command and would have stacked a second copy. The
preview now runs layer.smartFilter.setParams on the proxy, as OK does, at
full size and reduced afterwards (the smart object renders again from its
source, which the proxy doesn't scale), so it shows the filter replaced in
its stack.
2026-10-15 23:46:54 +02:00
jonathnwxh f4842a04e8 Merge upstream multi-layer blending and preserve viewport refresh limits 2026-10-16 05:44:29 +08:00
itsF4LCON 4258089716 Replace Color: the sample eyedropper is armed when the dialog opens (#6279)
Photoshop's first click on the image after opening Replace Color samples the
colour. The dialog now opens (and resets) with its canvas eyedropper active;
the pipette button still turns it off and on.
2026-10-15 23:38:01 +02:00
Tanish Manku 54f4baf6f9 Layers panel: blend mode, Opacity, Fill and locks change every selected layer; Photoshop's History names (#5856)
* Layers panel: blend mode, Opacity, Fill and locks change every selected layer; Photoshop's History names

With several layers selected, the Layers panel's blend mode, Opacity, Fill and lock controls
changed only the active layer, silently (the layers audit's top panel gap). They now change
every selected layer, as in Photoshop, in one undo step. layer.setProps takes
"layers": [id, …] for that (headless too); a list with an unknown id, a bad shape, or a
name for several layers is an error that changes nothing.

History names follow Photoshop for a single kind of change: Opacity Change, Fill Opacity
Change, Blending Change, Rename Layer (a mix stays Layer Properties).

* Layers panel with several selected: Pass Through only for groups (refused for other layers), the Basic lock toggle and the blend-mode hover preview take every selected layer, the controls grey with the Background selected; History icon for opacity/fill/blend changes

* Layers panel: leave the Background-in-a-selection behaviour as it was until Photoshop's is confirmed (#5856)

* Layers panel: a selected Background is left out of several-layer Opacity, Fill, blend and lock changes until Photoshop's behaviour is confirmed (#5856)
2026-10-15 14:33:51 -07:00
Higgs32584andClaude Opus 5.5 a008842a85 Help › Search: ← / → and Space edit the field instead of driving the menu
While a text field in an open menu has the keyboard, Nav::keys leaves
← / → (cursor) and Space (a typed space) to it: ← / → no longer switch
Help to Window / File, and Space no longer runs the lit result. ↑ / ↓,
↩ and Esc are unchanged. Follow-up to #5996 / #6030 (letters).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 17:32:01 -04:00
David MannandClaude Opus 5.5 b43be75264 PSD: Wind, Extrude and Tiles smart filters open and save like Photoshop (#6314)
Wind, Extrude and Tiles applied as smart filters were dropped from PSD
saves with a warning, and the same filters in a Photoshop PSD opened only
as verbatim, uneditable entries. smart_map.rs now maps them both ways in
filterFX:

- Wind (`Wnd `, filterID 1466852384): `WndM` enum (`Wnd `/`Blst`/`Stgr`)
  <-> method; `Drct` enum: `Left` <-> fromRight (streaks extend to the
  left), `Rght` <-> fromLeft.
- Extrude (`Extr`, 1165522034): `ExtT` enum (`Blks`/`Pyrm`) <-> type,
  `ExtS`/`ExtD` long <-> size/depth, `ExtR` enum (`Rndm`/`LvlB`) <->
  depthMode, `ExtF`/`ExtM` bool <-> solidFrontFaces/maskIncompleteBlocks.
- Tiles (`Tls `, 1416393504): `TlNm`/`TlOf` long <-> count/maxOffset,
  `FlCl` enum (`FlBc`/`FlFr`/`FlIn`/`FlSm`) <-> fill; the entry's
  `FrgC`/`BckC` <-> foreground/background.

Our random seed has no PSD key and is not written. Missing keys, wrong
value types, out-of-range numbers and unknown enum values keep the entry
verbatim; params with no PSD equivalent are reported, never guessed.

Measured in Photoshop: applied through Action Manager to a smart object,
saved, filterFX read back; each code also checked on a test image (pixels
compared). Scorecard FILE-215-2 note and file-format parity updated.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-15 23:30:11 +02:00
David MannandClaude Opus 5.5 badfd6df70 PSD: Mezzotint and Color Halftone smart filters open and save like Photoshop (#6316)
Mezzotint and Color Halftone applied as smart filters were dropped from PSD
saves with a warning, and the same filters in a Photoshop PSD opened as
unsupported (verbatim) smart filters. smart_map.rs now maps them both ways:

- Mezzotint: filterID/class `Mztn`, `MztT` enum (FnDt, MdmD, GrnD, CrsD,
  ShrL, MdmL, LngL, ShSt, MdmS, LngS) <-> `type` (fineDots .. longStrokes).
  PhotoCraft's `seed` has no Photoshop equivalent and is not saved.
- Color Halftone: filterID/class `ClrH`, `Rds ` and `Ang1`-`Ang4` longs <->
  `maxRadius`, `channel1`-`channel4`, written within the dialog ranges
  (4..127, -360..360).

Unknown enum codes, wrong value types and out-of-range values are kept
verbatim as unsupported filters, never a panic.

Measured in Photoshop: applied through Action Manager to a smart object,
saved, filterFX read back; codes confirmed by rendering each option.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-15 23:27:41 +02:00