Commit Graph
24 Commits
Author SHA1 Message Date
aiongg fe28d9d43c Merge PR #7395: Auto-Blend Layers: mask and blend the whole layers, past the canvas too
crates/engine/src/align_cmds.rs (tests module): four interleaved hunks of new tests; resolved as all of ours, then all of theirs, each test whole.
2026-10-22 02:24:08 +00:00
aiongg 18bc4ab876 Merge PR #5816: Auto-Blend Layers: blend a layer the canvas crops to all-overlap (#5745)
crates/engine/src/align_cmds.rs (tests module): two interleaved hunks of new tests; resolved as all of ours, then all of theirs, both tests whole.
2026-10-22 02:23:50 +00:00
aiongg 698aef6bab Merge PR #7396: Auto-Blend Layers: Seamless Tones and Colors turns on gain compensation
crates/engine/src/align_cmds.rs (tests module): both sides' appended tests kept whole.
2026-10-22 02:23:39 +00:00
Michael Higgins 3ae0678678 Merge branch 'storytold:main' into fix/5745-auto-blend 2026-10-21 11:21:07 -04:00
Branch Sync 133ea2b1ec Merge remote-tracking branch 'refs/remotes/upstream/main' into HEAD 2026-10-21 11:18:46 -04:00
Branch Sync 25634c1794 Merge remote-tracking branch 'refs/remotes/upstream/main' into HEAD 2026-10-21 11:18:33 -04:00
Michael Higgins da307f8f17 Merge branch 'storytold:main' into fix/auto-blend-background-mask 2026-10-21 11:09:47 -04:00
Higgs32584andClaude Opus 5.5 3c2ef8b893 Auto-Blend: budget the work area past the canvas by its buffers' memory
The pixel-only cap (2^27) still let two RGBA layers past the canvas with
about 11 GiB of working buffers. Count what Auto-Blend holds per pixel
(each layer's pixels, luminance, alpha and weight, plus the merged picture
and depth map) against a 4 GiB budget; over it, keep to the canvas as
before. The per-layer region reads and luminance/alpha buffers now use the
fallible allocation helpers, so running out of memory is an error.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 20:55:10 -04:00
Higgs32584andClaude Opus 5.5 109c34b273 Auto-Blend: cap the work area past the canvas independently of layer bounds
A sparse layer (a dot at each far corner) has huge bounds that also raise
the content estimate, so the whole union was chosen and its buffers could
exhaust memory. Past the canvas, work on at most MAX_BLEND_PIXELS; beyond
that keep to the canvas as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 10:30:07 -04:00
Higgs32584andClaude Opus 5.5 e8848e8ba2 Auto-Blend Layers: turn the Background into a layer before masking it
Refs storytold/PhotoCraft#5745, split out of #5816.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 09:32:05 -04:00
Higgs32584andClaude Opus 5.5 dfaf48e801 Auto-Blend Layers: Seamless Tones and Colors turns on gain compensation
Refs storytold/PhotoCraft#5745, split out of #5816.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 09:28:16 -04:00
Higgs32584andClaude Opus 5.5 2fc4360d67 Auto-Blend Layers: mask and blend the whole layers, past the canvas too
Refs storytold/PhotoCraft#5745, split out of #5816.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 09:24:35 -04:00
Higgs32584andClaude Opus 5.5 62ca145979 Auto-Blend: keep this PR to the seam fix for #5745; split the rest out
Per review, the canvas-wide area, Seamless gain compensation and Background
conversion move to their own PRs. Adds an engine test for the crop case.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 09:18:37 -04:00
David MannandClaude Opus 5.5 af5e77b150 Auto-Align: warp layer masks as masks, keeping what lies outside revealed (#6776)
Auto-Align warped a moving layer's mask with the colour warp (warp_placed), which
returns a grey + alpha surface defaulting to 0, so a reveal-all mask hid everything
outside its painted area. Masks now go through warp_gray (planar placements) or
warp_mesh_gray (cylindrical/spherical), keeping one channel and the mask's default.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-16 23:01:14 +02:00
Higgs32584andClaude Opus 5.5 a43f8b5ece Auto-Blend Layers: blend a layer the canvas crops to all-overlap (#5745)
Panorama seams skipped an image lying wholly inside the composite, so after
Auto-Align moved the second photo partly off the canvas it got an all-black
mask and nothing changed. Such an image now takes the side of the seam it
lies towards; blending covers the layers past the canvas; Seamless Tones and
Colors evens out exposure; the Background becomes a layer to take its mask.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 17:25:06 -04:00
Valentin KleinandClaude Opus 5.5 706638b284 Tests: bad-layer-id checks use an id that can't exist (#3343) (#3351)
* Tests: bad-layer-id checks use an id that can't exist (#3343)

Layer ids come from one process-wide counter (shared with document and
smart-object ids, and bumped by ensure_ids_above on file loads). A full
`cargo test` run passes ids like 9999, so a layer a test just created can
get the "bad" id, the command succeeds and the assertion fails at random
(seen on CI in type_extra_cmds::tests::vertical_orientation_renders_columns_inside_the_canvas).

Use u64::MAX, which the counter never reaches, for every "no such layer"
sentinel in the engine and ui-egui tests. Test-only change.

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

* Tests: use u64::MAX for bad-layer-id sentinels added on main since (#3343)

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 01:59:43 -04:00
ragtuxandClaude Fable 5.1 995bc89c40 Auto-Blend Stack Images: a depth map by depth from focus, for Lens Blur (#2073)
* Auto-Blend Stack Images: a depth map by depth from focus, for Lens Blur

`edit.autoBlendLayers` with `method: stack` and `depthMap: true` adds a hidden gray Depth layer, the layer each pixel is sharpest in (black = the bottom layer, white = the top), from `photocraft_algo::dff`: the ring difference filter as focus measure, guided-filter aggregation with the merged picture as guide, a streaming peak search with Gaussian interpolation and a confidence, edge-aware weighted least squares solved by multigrid-preconditioned conjugate gradients, and guided upsampling. Once on a layer mask it drives Lens Blur's depth map.

Closes #2067.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* Depth from focus: a guide or plane of the wrong length is the empty map

Review follow-up on #2073: `depth_from_focus` is public and indexed
`guide[i]` without checking that `guide` and every `luma` plane are
`w × h` long. It now returns the empty map (depth 0, confidence 0) for a
mismatch, before anything is read, and the test covers a short guide, a
short plane and a size that matches neither.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-13 13:23:35 -04:00
458870dadc Auto-Blend Stack Images: fuse the merged layer per pyramid level by region energy, with halo control (#2044)
* Auto-Align Layers: register focus and exposure brackets by intensity when there is nothing to match

Feature matching fails outright on layers that nearly coincide (a focus bracket: sub-pixel shifts, up to a few percent of scale from focus breathing, much of every frame defocused). `photocraft_algo::direct` registers the target to the reference coarse-to-fine on a Gaussian pyramid of the luminance, the cost the mean-removed RMS of the warped difference over the pixels both cover, by a bounded Nelder-Mead search with a per-level schedule; the pose is a similarity about the centre, affine or projective per the projection, and resolution independent, so the search stops short of full resolution. `edit.autoAlignLayers` gets `registration`: auto (features, then intensity when nothing matches), features, intensity; layers register outward from the reference, each to the previous one aligned.

Closes #2064.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* Intensity registration: reject a no-fit, search on coarsened planes

Review follow-up on #2065:
- `direct::cost` also returns the correlation of the reference with the
  warped target over the overlap; `Registration.correlation` carries it.
  Unlike the mean-removed RMS it is exposure independent, so it tells a
  bracket (near 1) from unrelated pictures (near 0).
- `register_direct` fails with the existing "couldn't find enough matching
  detail" error when a layer's best pose overlaps its neighbour by less than
  a quarter of the plane or correlates below 0.5, instead of placing it at
  whatever pose the search box ended at.
- The luminance planes are built at the search scale (`luma_alpha` with the
  reduction factor, ≤ 2048 px across) and the warp of each registered layer
  runs there too; the pose is resolution independent and its matrix at full
  resolution places the layer. Nothing full-resolution is held.
- The reference index is checked once with `get`; no unclamped indexing.
- Tests: correlation at an exposure step, for unrelated textures and a flat
  plane; the engine rejects unrelated layers and a mostly empty one.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* Auto-Blend Stack Images: fuse the merged layer per pyramid level by region energy, with halo control

The merged layer of Edit › Auto-Blend Layers › Stack Images was the per-pixel masks blended with a multiresolution spline, so a wrong pick in the mask reached every scale. `pyramid::fuse_stack` fuses the layers on the Laplacian pyramid instead: per coefficient the layer with the largest region energy of its luminance band wins (Adelson et al. 1984's multifocus composite with Wang & Chang 2011's rules), the residual follows local deviation + entropy, and with halo control the levels coarser than the guide are weighed by the guide's energy so a bright object's defocused glow stays out. `haloControl` (0..8, default 2) on `edit.autoBlendLayers`; the masks and the panorama method are unchanged.

Closes #2043.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 04:13:11 -04:00
ragtuxandClaude Fable 5.1 ec60f43b5b Auto-Align Layers: register focus and exposure brackets by intensity when there is nothing to match (#2065)
* Auto-Align Layers: register focus and exposure brackets by intensity when there is nothing to match

Feature matching fails outright on layers that nearly coincide (a focus bracket: sub-pixel shifts, up to a few percent of scale from focus breathing, much of every frame defocused). `photocraft_algo::direct` registers the target to the reference coarse-to-fine on a Gaussian pyramid of the luminance, the cost the mean-removed RMS of the warped difference over the pixels both cover, by a bounded Nelder-Mead search with a per-level schedule; the pose is a similarity about the centre, affine or projective per the projection, and resolution independent, so the search stops short of full resolution. `edit.autoAlignLayers` gets `registration`: auto (features, then intensity when nothing matches), features, intensity; layers register outward from the reference, each to the previous one aligned.

Closes #2064.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* Intensity registration: reject a no-fit, search on coarsened planes

Review follow-up on #2065:
- `direct::cost` also returns the correlation of the reference with the
  warped target over the overlap; `Registration.correlation` carries it.
  Unlike the mean-removed RMS it is exposure independent, so it tells a
  bracket (near 1) from unrelated pictures (near 0).
- `register_direct` fails with the existing "couldn't find enough matching
  detail" error when a layer's best pose overlaps its neighbour by less than
  a quarter of the plane or correlates below 0.5, instead of placing it at
  whatever pose the search box ended at.
- The luminance planes are built at the search scale (`luma_alpha` with the
  reduction factor, ≤ 2048 px across) and the warp of each registered layer
  runs there too; the pose is resolution independent and its matrix at full
  resolution places the layer. Nothing full-resolution is held.
- The reference index is checked once with `get`; no unclamped indexing.
- Tests: correlation at an exposure step, for unrelated textures and a flat
  plane; the engine rejects unrelated layers and a mostly empty one.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-11 04:02:26 -04:00
28ed1c1147 Handle allocation failures in large pixel operations (#2530)
* Make large pixel operations allocation-fallible

* Warp: drop the fixed 400 MP cap; fallible reservations already cover large transforms

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

* Adapt #2530 to main: fallible steep-Distort warp, Result from mask paste in float moves; regenerate scorecard

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-10 13:26:07 -04:00
Brandon ThomasandClaude Opus 5.5 4218d07f40 Never crash: the engine is panic-free (Rule 9) (#54)
Every unwrap/expect/unreachable in non-test engine code now returns an
EngineError (or a predicate's Err string) or uses a real fallback:
- enabled predicates read the active layer through active_layer_of()
  instead of unwrap() after has_layer();
- new raster layers get their pixels through pixels_mut()? instead of
  surface_mut().expect("raster");
- layer paths use split_last().ok_or(..)? (Merge Down, Arrange, Ungroup,
  Release Clipping), Auto-Align/Auto-Blend/Photomerge carry placements
  instead of re-unwrapping them, and "next free name" searches are bounded;
- web export builds its options map directly, quantize_rows has a wasm
  tail, parse_script rejects non-array/object JSON, the shape-layer edit
  returns an error instead of unreachable!, and the gray composite
  profile falls back to sRGB.
The engine denies unwrap/expect/panic/unimplemented/todo/unreachable.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 13:26:26 -04:00
Brandon ThomasandClaude Opus 5.5 f7eb6ea9e7 Format: adopt the craft-family rustfmt.toml and format the workspace
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>
2026-10-04 11:31:17 -04:00
Brandon ThomasandClaude Opus 5.5 aa87012aab Analysis, notes, proof views, workspaces (+ in-progress work of other agents)
Image > Analysis (ruler, count, measurement scale/log/record, scale
marker), notes (PSD Anno round trip, Notes panel, import), Modifier Keys
panel, Proof Setup plates / Legacy Mac RGB / colour blindness (Vienot),
32-bit preview options, custom pixel aspect, extras options, New/Delete/
Lock Workspace. Parity 600/625, FLOOR 595.

Checkpoint: also contains compiling in-progress work from the parallel
panels, type styles, File menu, photo, filters, UI-parity and fidelity
agents, plus photocraft-cli serve.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 22:49:07 -04:00
Brandon Thomas 536a7346fb more algorithms, progress 2026-10-01 08:25:06 -04:00