* Clone Stamp and Healing Brush clone from a source set in another open document
Alt-clicking a clone source in one document and painting in another sampled the
painted document at the source point: a clone source slot held a point and a layer
but not the document. Slots now record the document (and its layer) they were set
in, and painting elsewhere samples that document, as in Photoshop.
- Engine: CloneSource gains `document` (serde default, so older slots read as
"the document being painted"). Clone Stamp, Healing Brush, the live stroke, the
Pattern Preview wrap and the brush-tip preview read the source document. Pixels
are converted to the painted document's colour mode and depth like Edit > Paste
(no ICC conversion, same as Paste). Current Layer / Current & Below use the
layer the source was set on (that document's active layer if it was deleted);
All Layers uses its composite.
- A source whose document was closed is an error asking for a new source, and
stores no aligned anchor, instead of silently sampling the wrong document.
- cloneSource.set takes an optional `"document": index` (with `source`);
cloneSource.list and set report each slot's document index.
- UI: the Clone Source panel names the source document (or says it was closed),
the source crosshair isn't drawn over another document, and the brush preview
cache includes the source document's revision.
Tests: engine tests for 8/16/32-bit with and without wrap, colour-mode conversion
(Gray > RGB16, RGB > CMYK), sampled layer, Healing Brush, live stroke vs commit,
preview, closed/invalid documents and cloneSource.set/list; a UI test that
Alt-clicks in one document and paints in another.
* Clone test: the aligned stroke past the smaller source document's edge leaves the destination unchanged
Review follow-up: with "aligned": false the stroke sampled inside the source, so it didn't test the edge its comment named, and nothing was asserted.
* Clone Stamp / Healing Brush: an empty Clone Source slot refuses to paint
clone_params fell back to ui.clone_source / ui.clone_offset when the active
slot was empty. Those coordinates carry no document, so after Alt-clicking in
one document and picking an empty slot in another, the stroke read the first
document's coordinates from the second one's pixels.
* fix: reject stale cross-document clone previews
* Clone Source: a copy opened after the source document closed isn't its source
A native copy keeps its document's ID, and a reopened document may keep its ID. A slot set in
a document that was then closed resolved that ID again once a copy was open, and painted from
the copy instead of reporting the closed source (CodeRabbit, clone_source.rs).
Session::close now marks the slots set in the closing document closed (CloneSources::forget_doc,
next to the other per-feature forget_doc calls). A closed slot refuses the stroke and its live
preview with the closed-source error, shows "Source: document closed" in the panel, reports no
`document` in cloneSource.list/set, and draws no overlay or crosshair, whatever document has the
ID now. Setting a new source clears it.
* Use craft-psd from craft-libs; remove crates/psd
* Regenerate the scorecard (28 crate roots)
* Take craft-psd at craft-libs 55d120f2
* Regenerate lockfile for current shared format crates
* Pin shared format crates to current craft-libs main
* Match brush import cap to shared decoder budget
* xtask: update layer fixtures after PSD extraction
* paint: refuse a sequential-dab stroke whose working copy is too large
apply_dab_stroke read the whole rectangle enclosing a stroke's dabs into
two dense f32 copies (and a third for the mix), sized by the area
between the dabs rather than what they paint. Two distant points asked
for gigabytes on a sparse document.
Check the three planes against a 4 GiB budget (1 GiB on 32-bit) before
reading, and paint nothing past it.
* address review: apply_dab_stroke returns Result so over-budget dab strokes fail with 'stroke too large'
* fix tests: apply_dab_stroke returns a Result
* fix tests: handle the Result of the dab-stroke functions
* Engine: check dense working buffers against a budget before expanding sparse content
A native document stores only the tiles it holds, so a few distant tiles (or a
huge empty canvas) make a small file whose content bounds span trillions of
pixels. Several commands read those bounds, or the whole canvas, into dense
f32 buffers without checking the size first, and ran the process out of memory.
- allocation::check_dense refuses an area whose dense buffers would pass
MAX_DENSE_BYTES (the Image Size allowance), before anything is allocated.
- Adjustments run through process_surface, Apply Layer Mask, channel sources
(Apply Image / Calculations / Load Selection), face detection and Multichannel
conversion (both directions) check their buffers first.
- Auto-Blend checks the final work area too, not only the layers' union.
- Liquify's selection blend works over the part of the content the selection
reaches, on a copy of the old pixels, and checks that area.
- Floating a selection visits only stored source or selection tiles when a
default is empty, and checks the full grid otherwise.
- Frame from Layers and the Frame tool fill their mask without a dense buffer
and check its area.
- Image Size checks every surface's resampled content bounds, off-canvas
pixels included, not only the canvas.
* cargo fmt
* address review: propagate budget error out of adjust_as_filter
* address review: budget merged CMYK channel planes, frame mask GRAY8 source, Duotone inks and the stored surface in Multichannel conversions
* address review: budget the Frame mask at its tile footprint and Auto-Blend stack pyramids
* address review: non-seamless stack blend skips the fusion pyramids in its budget; canvas refresh returns smart-object refresh errors
* address merge: take main's resample_doc signature in Bitmap; fmt
* address merge: Image Size over distant tiles is bounded by main's resample budget; drop the duplicate test
* address merge: keep brush_clear_background_tests test-only
* address CI: a smart object that cannot re-render keeps its resampled fallback (main's tested behavior); drop the refresh error propagation
* Actions: charge skipped steps to the playback budget, bound batch nesting, clip the set override
- actions.play charges every stored step of a call (run, disabled or before
`from`) to the playback tree, and refuses a call that no longer fits before
copying the action, so repeated calls to a large disabled child stop at the
shared limit.
- file.automate.batch and runDroplet refuse to nest more than MAX_BATCH_DEPTH
deep, checked before the droplet is read; nested batches charge the shared
step budget like batches inside played actions.
- actions.load clips the `set` override to the importer's name limit before
copying it into every action.
* address review: check the script step limit before charging skipped playback steps
* address review: nested batches keep the shared playback count, and nesting or playback limits fail the enclosing input
* address review: a limit stop inside a called action or nested batch fails the enclosing input
* address review: keep begin_playback's signature; the call that starts mid-action uses begin_playback_from
* address review: keep child results in limit failures, history after the script-limit check, trim a clipped set name, open-document batches carry the nesting state
* fix(engine): authorization for nested scratch sessions and preset calls, CSV and PostScript text escaping
* address review: Batch checks the authorization gate for each save point
* address merge: restore the #[test] attribute
* address merge: batch save points answer to the authorization gate in the rewritten batch
* address review: neutralize leading line feeds and full-width formula characters; test the preset command gate
* address review: check formula prefixes after leading spaces
* fix(format): limit zstd windows, set bundle permissions through the handle, refuse symlinked presets
* test(engine): create the symlink test's directory
* address review: preset writes never follow a symlink swapped in after the check
* address review: a zstd window past the limit is a limit error; replacing a link never reads its target's attributes
* test: replacing a link to a FIFO does not block
* Place: refuse a scale that makes the placed image exceed the render pixel limit
* address review: bound the rounded-up resampled size when placing at a scale
* address review: the oversized-scale error names the placing command
live_path rounds a star's outer points and leaves its inner points
sharp corners, as the reference application does: a 5-point star at
Star Ratio 50 % with corner radius 10 has 15 anchors instead of 20.
Plain polygons keep rounding every vertex.
The on-canvas corner radius widget shows knobs at the outer points only
and takes its largest radius from them, so it previews the same path
as shape.create.
The Convert Point tool names a whole-path drag as the reference does (#12741). Any other stepName is refused, as with path.convertPoint and path.moveHandle.
paint.bucket returns the reference's error text before it reads the
region or copies the document, so nothing changes and no History state
is added. A canvas click shows the text as a modal alert with OK.
paint.healingBrush healed an Indexed Color document with colours outside
its colour table. It now refuses there with the reference application's
words, and the canvas shows them in an alert with OK at the Alt-click
that sets the source and at a stroke, changing neither the source, the
pixels nor the History. Bitmap, Duotone and Multichannel weren't
measured and heal as before.
Resolve conflicts by keeping both the Image Processor source parameter
and main's pdfSpotPlates text, and by combining the Last updated
headers in docs/gaps.md and docs/target-app-parity.md.