crates/engine/src/edit_menu_cmds.rs: fade(): kept main's checked_sub for the previous state and took the PR's FadeTarget-based surface_of lookups and messages.
* 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.
image.autoTone accepts a target param but edits the active layer, so an
explicit channel target recorded the unchanged channel for Fade. Only
commands channel_cmds::routed sends to channels now record one.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An explicit target (edit.fill with "target": "pixels" while an alpha
channel is targeted) edits the layer, but Fade recorded the channel from
the Channels panel. after_command now resolves the target as the command
did, with inject_target: the explicit target, else the panel's, and only
for the commands it routes to channels.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
FadeSource only knew a layer and whether its mask was edited. With an
alpha channel or the Quick Mask targeted, filters, adjustments and paint
change that channel, but Fade blended the untouched layer back and left
the channel as edited. It now records what the command changed, as a
FadeTarget (a layer's pixels or mask, an alpha channel, or the Quick
Mask), and fades that surface.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* 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>
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>
Conflicts:
- edit_menu_cmds.rs: main's #4133 (Sample All Layers off unless the active layer has no pixels
in the window) now decides `sampleAllLayers` inside the workspace's fill plan; the job keeps
the plan-based fill, the spec keeps the workspace's parameters.
- lib.rs: both module lines (caf_ui, cam_preview).
- hu, tr, vi catalogs: both sides' rows. Dropped de's High/Low (main now has them) and hu's
older "Vanishing Point" row main reworded. Added the 15 workspace strings to sr.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Sample every visible layer in Content-Aware Fill by default.
An empty layer above the photo used to report a successful fill and stay empty, because only the active layer was sampled. The result is still written to the active layer. sampleAllLayers can be set to false to keep the old behaviour.
Fixes#4026
* Sample other layers in Content-Aware Fill only when asked or the layer is empty.
Reuse the Remove tool's composite, which writes the active layer's format, and leave Photoshop's Sample All Layers checkbox off unless the window has no pixels on this layer. An explicit false still reads only this layer.
---------
Co-authored-by: Brandon Thomas <echelon@gmail.com>
#3132 refused type.edit on a Lock All layer, but type.insertText,
edit.checkSpelling "change", Paste Lorem Ipsum (and the other
type_extra_cmds::with_text edits) and Find and Replace Text "change"
on a layer locked after the find still changed its text and recorded
history. The check is now one pub(crate) helper,
type_cmds::check_text_unlocked, called by every one of these paths
with the same "...is locked" error as type.edit.
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
After Replace Text the find cursor collapsed to the end of the replacement.
A backward search takes the last match ending at or before the cursor, so
when the replacement still matched (e.g. "cat" -> "Cat", case-insensitive)
it found the replaced text again and never reached earlier matches.
Collapse the cursor at the start of the replacement when searching backward
(the end when forward), mirroring how forward search skips it.
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- The fill area and sampling area (`content_aware_plan_with`) are worked out
inside the background job, cancellable row by row as #3395 made the rest of
the fill: the window and selection are read in parallel bands, Auto sampling
is `auto_sampling_with`. The params are still checked before the job starts.
The workspace's preview builds its plan with its own cancel flag.
- The apply step is #3395's: only the fill area's bounds keep the original
pixels and coverage. `CafPlan::layer_px` is kept only when Sample All Layers
samples something else than the layer.
- `fillEdits` and `samplingStrokes` take at most 1000 entries and 200 000
points in all (MAX_CAF_EDITS, MAX_CAF_POINTS).
- `paint_strokes` leaves a mask that does not cover its area alone, writes
through `get_mut`, and visits each segment's own bounds instead of the whole
stroke's for every segment (the same pixels). The workspace's
`sampled_at`, `in_hole` and outline read their buffers with `get`.
- Content-Aware Scale's transform box (#3096) and the Vanishing Point
workspace keep their menu entries next to the Content-Aware Fill workspace.
- Vietnamese labels for the workspace.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Content-Aware Scale: transform box with live preview (#3096)
Edit > Content-Aware Scale opened a parameter dialog. Photoshop has no
dialog: it puts a scale-only transform box on the layer (or the
selection's bounds). The menu now opens that box.
- The box reuses Free Transform. Corner and edge drags scale and an
inside drag moves; dragging outside does not rotate, Cmd does not
skew or distort, and the Warp switch and the Transform modes are off.
- The options bar shows X/Y and W/H, then Amount, Protect (an alpha
channel) and Protect skin tones.
- The live preview runs the command's own carving
(ContentAwareScalePreview) on a 384 px copy on a worker thread. A
run for a box that has changed since is cancelled after 150 ms, and
the latest result is drawn through the box.
- Enter runs edit.contentAwareScale with the box's size and its new
x/y params as one history step; Esc cancels.
- With params the command still runs directly.
Scorecard TOOL-214-7: done.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Content-Aware Move's transform box: set the new content_aware field
#3095 (Transform On Drop) builds a TransformSession by hand; this PR adds
content_aware to it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* Content-Aware tools: Photoshop's behaviour, measured
Photoshop 25.4 was measured as a black box (synthetic and public-domain
images, results read back exactly) and the content-aware family follows it:
- Fill (inpaint): coarse to fine with the coarser level's matches scaled up
and voted with the finer level's own pixels, the coarsest level filled
from the hole's edge inwards, a shallower pyramid. The fill is pieces of
the image copied pixel for pixel with narrow seams, as Photoshop's;
before, it was visibly blurred. Pixels have roles (hole, context,
source, void), so excluded areas stay context instead of being filled.
- Colour adaptation: Image Melding's per-patch gain and bias, limited per
level, instead of a low-frequency membrane pull.
- Sampling window: Photoshop's square of side 4·√(max(w,50)·max(h,50))
centred on the selection, slid inside the canvas; Edit › Fill,
Rectangular, Auto, Delete and Fill and Spot Healing use it.
- Auto sampling area: what looks like the selection's surroundings (local
colour and contrast, one min cut), fitted to Photoshop's overlay.
- Patch Tool: a Content-Aware mode (Structure, Color, Sample All Layers).
- Content-Aware Move: Structure 3..7 alike, a band about a patch wide;
Color as a gain/bias limited to about 0.04 per step.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Remove tool: keep its own sampling margin (fill_cmds::sampling_margin is gone)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Edit › Content-Aware Fill… opens Photoshop's workspace (measured on 25.4)
instead of the generic dialog: a full-window dialog like Liquify's.
- Tools: Sampling Brush (B), Lasso (L), Hand (H), Zoom (Z), each with its
options bar.
- Document view: the sampling area tinted green at 50 % and the fill area's
marching ants.
- Preview panel: the fill computed in the background after each change,
opening framed on the sampling window.
- Content-Aware Fill panel: Sampling Area Overlay (Show, Opacity, Color,
Indicates), Sampling Area Options (Auto, Rectangular, Custom, Sample All
Layers), Fill Settings, Output Settings, and the reset buttons.
- OK, Apply, Cancel.
As Photoshop's:
- The Sampling Brush subtracts in Auto and Rectangular and adds in Custom,
which starts empty; Alt reverses. Its first size is a tenth of the
window's side. Painting keeps the option, and added strokes may lie past
the window.
- The lasso edits the fill area (New, Add, Subtract, Intersect, Expand,
Contract); the window and the Auto area follow.
- Apply fills and stays open; Cancel keeps what was applied. Ctrl+Z undoes
a stroke or an edit. Output opens on New Layer; settings are remembered.
The workspace holds plain data. `edit.contentAwareFill` takes it as
`samplingStrokes`, `fillEdits`, `sampleAllLayers` and `layer`, and both
the preview and OK run one plan (`content_aware_plan`), so the preview is
what gets written. New strings are translated in every catalog.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Photoshop 25.4 was measured as a black box (synthetic and public-domain
images, results read back exactly) and the content-aware family follows it:
- Fill (inpaint): coarse to fine with the coarser level's matches scaled up
and voted with the finer level's own pixels, the coarsest level filled
from the hole's edge inwards, a shallower pyramid. The fill is pieces of
the image copied pixel for pixel with narrow seams, as Photoshop's;
before, it was visibly blurred. Pixels have roles (hole, context,
source, void), so excluded areas stay context instead of being filled.
- Colour adaptation: Image Melding's per-patch gain and bias, limited per
level, instead of a low-frequency membrane pull.
- Sampling window: Photoshop's square of side 4·√(max(w,50)·max(h,50))
centred on the selection, slid inside the canvas; Edit › Fill,
Rectangular, Auto, Delete and Fill and Spot Healing use it.
- Auto sampling area: what looks like the selection's surroundings (local
colour and contrast, one min cut), fitted to Photoshop's overlay.
- Patch Tool: a Content-Aware mode (Structure, Color, Sample All Layers).
- Content-Aware Move: Structure 3..7 alike, a band about a patch wide;
Color as a gain/bias limited to about 0.04 per step.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The automatic sampling margin was three quarters of the selection's larger side, computed
as `ext * 3` in u32 (edit.fill) or i32 (edit.contentAwareFill) before the window cap, so a
loaded document with a far-flung selection overflowed and panicked. edit.contentAwareFill
also had no window cap at all and could try to allocate a canvas-sized window.
The margin is now one shared function computed in u64 and saturated to i32, and both
commands check the window against the same 400 M-pixel cap before reading or allocating.
Content-Aware Move uses the shared margin too.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Right-click with a selection tool and choose Delete and Fill Selection
to fill the selection from its surroundings with Content-Aware Fill's
default settings, without the dialog. Engine command
edit.deleteAndFillSelection, one undo step, translated in every shipped
language.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`commands::int` returns an unbounded i64, and the geometry call sites narrowed
it with `as i32`, which truncates by multiples of 2^32: layer.translate moved
a layer 50 px for `dx = 2^32 + 50` and to a large negative offset for
`dx = 3e9`; select.rect and document.pixel read unrelated coordinates;
image.crop cropped a rectangle unrelated to the numbers passed; artboard
rects landed elsewhere. No panic, no Err - the document was silently
mutated and then autosaved, and the behaviour is identical in debug and
release, so panic_hunt cannot see it either.
- new `commands::int_i32(cmd, p, key)` narrows with a bad-params error
instead of a wrapping cast; select.rect, document.pixel, layer.translate,
image.crop and the artboard rect keys go through it
- image.crop builds its rectangle with saturating_add (x + w overflow)
- edit.contentAwareFill's `area` parser returns a bad-params error for
malformed arrays (wrong length, non-numbers, non-finite) and saturating
arithmetic for huge ones - `[1e30, 0, 1e30, 10]` overflowed the i32
additions before (a debug-build panic, a garbage sampling window in
release)
- filters' integer helper clamps out-of-range values like the neighbouring
filter params instead of wrapping (`3e9` came back as a negative offset)
With allLayers: false the search keeps only the active layer; when that
layer isn't a type layer the list is empty, and the find loop indexed
layers[0] on it (find, change and changeFind). Read the layer with
get() and stop when there is none, so the command finds nothing and
returns {"found": null}, as it does when nothing matches.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
To enlarge, carve_width_with finds seams on a shrinking copy and duplicates them, but it stopped once the copy was 1 px wide, so a 1 px wide image found no seam and came back at its old width. The caller then wrote a short buffer into the new size (the "region data length mismatch" assertion in write_region) or, with a height change too, transpose indexed past the end. The session's guard turned both into "failed with an internal error". The single column is now taken as the seam, so it is repeated; wider images are unaffected, because a round takes at most half the columns.
CI's `cargo fmt --all -- --check` has failed on every run because the
repo had no rustfmt config and was never formatted. Use the same settings
as EffectCraft/FilmCraft (max_width 160, use_small_heuristics Max), which
also keeps the churn small. Formatting only; no code changes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>