8 Commits
Author SHA1 Message Date
25a82978b2 fix(segment): let Remove Background's cancel stop Select Subject's min cuts (#4878)
Select Subject ran every graph cut (the GrabCut iterations and the
full-resolution band re-cut in `refine_band`, tile by tile on rayon)
through the non-cancellable `grid_cut`, so the Remove Background job only
saw Cancel/Esc before and after the whole "Finding subject" step.

Add `select_subject_with`, `grabcut_with` and `finish_region_with`, which
take an `Interrupt` and return `Err(Cancelled)`: each band tile checks it
before reading pixels and its cut runs through `grid_cut_cancellable`, so
in-flight tiles stop at the max-flow's next poll and unstarted ones never
start. Remove Background runs the step under `JobCtx::stage`. The old
functions keep their signatures and results (they pass `Interrupt::NONE`),
so `select.subject`, Object Selection and the layer menu are unchanged.

Co-authored-by: lloyd <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-13 12:56:21 -04:00
1735c81e67 fix(segment): stop push/relabel looping forever on unreachable nodes (#4869)
Remove Background (and Select Subject's full-resolution refinement) could
hang at "Finding subject" on some photos. One refinement tile's max-flow
never finished, and the job ignores Cancel there.

Push/relabel's admissibility test was `dist[i] == dist[j].saturating_add(1)`.
A global relabel gives nodes reachable from neither terminal the height
u32::MAX, and MAX.saturating_add(1) == MAX, so two such neighbours counted
as admissible for each other. When f32 rounding strands excess on them
(source capacities near 3e8 have an ulp of 32), they pushed it back and
forth forever. Use checked_add so an unreachable node is never a push
target; its excess stays put and the loop ends.

The regression test is a 6-node graph that looped before and now matches
the Boykov-Kolmogorov cut.

Co-authored-by: lloyd <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-13 11:56:34 -04:00
539bf7ba14 Color panel: HSB on sRGB values, like Photoshop (#3323) (#3327)
The Color panel converted between the foreground and its hue, saturation and
brightness with egui's `Hsva`, which works in linear light. So a 50% grey
(128) read as Brightness 22%, and the colour field's left edge halfway down
was drawn as 188, not 128. Photoshop's HSB, and the Color Picker dialog's,
work on the sRGB-encoded values.

`srgb_hsva` / `hsva_srgb` now use the Color Picker's `rgb_to_hsv` /
`hsv_to_rgb`, and the field, hue strip, HSB slider tracks and swatch draw
through a new `hsva_color32` instead of egui's linear `Color32::from(Hsva)`.
`Hsva` only holds the numbers now.

Co-authored-by: lloyd <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-11 15:45:50 -04:00
76c7d723f4 Move tool: double-click type on the canvas to edit it (#1132) (#3300)
Since Photoshop CC 2019 a double-click on a type layer with the Move tool starts
editing its text without switching tools by hand. PhotoCraft only offered this
from the Layers panel thumbnail. A double-click with the selected Move tool now
picks the topmost visible type layer under the pointer (the Type tool's own hit
test), makes it active and opens an edit session with the caret on the glyph
there, switching to the Type tool. Elsewhere the double-click does nothing, ⌘
held over another tool doesn't count, and an open transform box keeps its
double-click (commit).

The type tests' harness steps a quarter second per frame, past egui's
double-click delay, so the new test uses a 60 fps harness.

Co-authored-by: lloyd <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-11 14:28:05 -04:00
b814fd5241 Zoom and Hand tools: right-click the canvas for the view menu (#3242) (#3255)
Right-clicking the image with the Zoom or Hand tool opens Zoom In, Zoom Out,
Fit on Screen, Fill Screen, 100%, 200% and Print Size. Zoom In/Out step about
the right-clicked point (or centre it with Zoom Clicked Point to Center); the
other rows run their View menu command. Outside the image the pasteboard menu
still wins. The canvas keeps the view a menu row sets instead of writing its
frame copy back over it. Agents get the same menu through ui.pointer and
ui.context.choose.

Co-authored-by: lloyd <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-11 12:50:01 -04:00
c732ecd5b0 Dropdowns: press, drag onto an option and release to choose it (#2735) (#2854)
A dropdown needed two clicks: egui's ComboBox opens its list on a whole
click and its rows only take clicks, so press-drag-release (as the menus
do since #775) chose nothing. press_menu::PressCombo gives combo boxes the
gesture without replacing egui's widget:

- after the box, a primary press on it opens the closed list (egui counts
  a popup opened in a pass as open, so it is drawn from the next frame);
- a row released on by that press-drag is chosen and closes the list;
- on the click that ends the press (pointer still on the box), egui would
  toggle the list shut, so it is closed just before the box and egui's
  toggle reopens it, drawn that frame: a plain click still leaves the list
  open for a second click.

widgets::dropdown (and its hovered/wheel variants), dropdown_with_tooltips,
the font family picker, the kerning presets, the Gradient Map presets, the
shape stroke choices and the credits sort all use it.

Co-authored-by: lloyd <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-11 07:46:46 -04:00
d166450c77 Hue/Saturation: Colorize at 0% saturation gives grey, not a 25% tint (#2343)
Colorize floored saturation at 25% on both the CPU (compose::adjust::hue_saturation) and the
GPU shader, so dragging Saturation to 0 with Colorize ticked still tinted the image. Photoshop
leaves only the lightness at 0%. The floor is gone from both paths, so 1-24% now tint
progressively too.

The floor also stood in for Photoshop's 25% starting saturation when Colorize is switched on.
The dialog's checkbox already sets that; the hueSaturation params now do the same when a call
turns Colorize on without giving a saturation, so {"colorize": true, "hue": 30} still tints.

Fixes #2326.

Co-authored-by: lloyd <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-10 13:16:26 -04:00
LloydNicholsonandClaude Opus 5.5 be446f89d3 Transparent PNG, TIFF and other flat images open as Layer 0, not Background (#1913)
A flat image with transparency (alpha channel or tRNS) opens as a normal, unlocked Layer 0, as in
Photoshop; opaque images still open as the locked Background. A lone Layer 0 saves back to PNG natively.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-09 15:43:14 -04:00