Commit Graph
468 Commits
Author SHA1 Message Date
FredApps 36bfc50fd3 Merge branch 'main' into feat/image-processor-open-documents
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.
2026-11-11 11:10:40 +01:00
Tom InglingandClaude c77f589184 Merge storytold main (0d08cb7dd) into pdf-spot-plates
One conflict, the gaps.md header: main's line (2026-11-08, G9 Column
Size now leads it) with this branch's G10 spot-plate item in front.
Main's changes to control-protocol.md and pdf/text.rs merged cleanly;
the doc.save row keeps `pdfSpotPlates`.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-11-10 13:48:01 -05:00
FredApps 96581039a8 Merge main into feat/ui-217-4-columns-unit 2026-11-10 18:41:49 +01:00
Tsieg e0cb757a64 Merge main into Image Processor source branch 2026-11-10 20:09:45 +07:00
Tom InglingandClaude 9b5568ec92 Merge storytold main (2a950d64d) into pdf-spot-plates
Conflicts were the two docs headers only: gaps.md and
file-format-parity.md each take main's line (Fujifilm compressed RAF
now leads it) with this branch's PDF spot-plate items in front. Main
did not touch the PDF writer or the save commands since 009b93c5e.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-11-10 07:42:12 -05:00
Tsieg 9f1e7a50cc feat: support current and open documents in Image Processor 2026-11-10 17:37:13 +07:00
Tom InglingandClaude Fable 5.1 d08a0bfaba Merge storytold main (009b93c5e) into pdf-spot-plates: plates on artboard pages
Main rewrote the PDF writer since e4e2607c8: a page per artboard with
page labels, writer cancellation (`ctl`), an export memory budget, and
PDF import. This branch adds spot plates, an optional page image and an
XMP packet. The merged writer keeps both:

- `print_pdf_pages` numbers its objects dynamically: per page its page,
  contents, image and one XObject per plate; then the shared font, ICC
  profile, overprint ExtGState and XMP metadata stream, each only when
  present, the selection font and last the page-label tree. The XMP
  packet is a parameter now, not a page field, since it is the
  catalog's.
- `export(doc, spot_plates, ctl)` / `export_with(doc, spot_plates, xmp,
  ctl)`. An artboard page carries its own region of every plate
  (`plates_of` takes the page rect), so a two-board document separates
  into two pages of named plates; a Multichannel artboard document is
  pages of plates alone. The export budget counts one byte a pixel per
  plate alongside the composite. New test:
  artboard_pages_carry_their_region_of_each_plate.
- lib.rs passes `ctl` through; the PDF arm keeps the XmpEmbed gate.
- file_cmds.rs: main's `maximizeCompatibility` and this branch's
  `pdfSpotPlates` both listed in the four spec strings and the import.
- docs: gaps.md and file-format-parity.md headers take main's line with
  this branch's items in front; the PDF parity row keeps main's new
  read column and notes and this branch's plate notes, plus that an
  artboard page carries its region of each plate; control-protocol.md's
  doc.save row lists both new params.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-09 14:46:47 -05:00
David Mann 80b6f3d9b3 Merge remote-tracking branch 'origin/main' into pr-6215
# Conflicts:
#	docs/gaps.md
#	docs/ui-parity.md
2026-11-09 17:06:19 +01:00
David Mann 68547285b5 Merge main into #7377 (Fujifilm compressed RAF) 2026-11-09 17:06:04 +01:00
David Mann e06ba09af4 Merge remote-tracking branch 'origin/main' into pr-6215
# Conflicts:
#	docs/gaps.md
#	docs/ui-parity.md
2026-11-09 14:38:19 +01:00
719ce3cfda Merge main into #9798 (Cylinder warp editor)
Conflict only in docs/gaps.md's status line: both entries kept.

Co-Authored-By: TimothyBritt <TimothyBritt@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 14:36:07 +01:00
David Mann 3369f427ba Merge main into #7377 (Fujifilm compressed RAF)
Only the docs status lines conflicted: main's text, with the compressed RAF entry kept first.
2026-11-09 14:26:32 +01:00
Tim BrittandClaude Fable 5.1 5872941b12 Warp mode: the cylinder's grid and handles as the reference's, and a stored warp previews unwarped (#9798)
The cylinder is outlined and cut into a 3 × 2 grid (two horizontal curves and
the centre line) that hugs the warped picture. The handle in the middle of the
upper arc sets the bend of both arcs, the one in the middle of the lower arc the
lower edge's height, and the wrap handle sits at the grid's centre and slides to
the right (starting 15 % along), stretching the middle of the picture and
squeezing its sides round the back.

A smart object's stored warp used to enter Warp mode with the layer's already
warped render as the preview texels, which the mesh then warped again, so the
picture never sat on the grid: the session now previews the source placed
without the warp over the warp box. Edit › Transform › Warp carries a check
while the active layer is warped, as the reference's does.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-09 04:36:43 +00:00
Tim BrittandClaude Fable 5.1 5e7bccc4d8 Address review: match the reference's Cylinder editor (#9798)
As a maintainer compared it with the reference application: Bend, H and V stay
in the bar, greyed (no Top/Bottom/Wrap fields); the starting cylinder has its
four corners on the box and both curves 50 % deep; five handles (the top-right
and bottom-left corners, the front of each curve, a wrap handle near the middle
dragged sideways); a drag on the surface moves the whole cylinder; the overlay
is the two curves and the centre line, not a grid.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-09 04:36:28 +00:00
Tim BrittandClaude Fable 5.1 4243427390 Warp mode: edit Photoshop's Cylinder warp with handles and fields (#4112)
The Warp menu lists Cylinder after the presets (not on type). Picking it starts
a cylinder fitted to the box; handles set its seven values: the corners move a
silhouette and an edge, the middle of a silhouette moves it alone, the middle of
the top and bottom edge sets that ellipse's depth, and a drag across the surface
turns the contents round the cylinder. The options bar shows Top, Bottom and
Wrap in place of Bend, H and V, with Split, Grid and the orientation switch
greyed. The warp stays a cylinder through the commit, so a smart object keeps it
as `warpCylinder`.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-09 04:36:28 +00:00
David MannandClaude Opus 5.5 4ebd2da168 Regenerate scorecard after merging main; preference counts of 199
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 23:55:07 +01:00
David Mann 61dfc33a04 Merge remote-tracking branch 'origin/main' into HEAD
# Conflicts:
#	docs/gaps.md
#	docs/scorecard.md
#	docs/ui-parity.md
2026-11-08 23:28:28 +01:00
David Mann 0523777541 Merge origin/main into fix/average-smart-filters 2026-11-08 22:24:23 +01:00
6afd603e53 Merge main into #7377 (Fujifilm compressed RAF)
Only the doc status lines, histories and raw tables conflicted. The
status lines take main's with the #7377 entry kept first; histories keep
both sides. The RAF table row keeps this PR's compressed support next to
main's new Hasselblad 3FR row and its sidecar status, and points at the
moved test file tests/it/raf.rs. The Camera RAW row keeps main's 3FR and
Nikon text and no longer lists compressed RAF as open.

Co-Authored-By: Yorgos S. <smyrgeorge@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 20:35:13 +01:00
David Mann 55498d45ba Merge remote-tracking branch 'origin/main' into pr-9077
# Conflicts:
#	docs/gaps.md
2026-11-08 18:55:24 +01:00
David Mann 85971ce95f Merge remote-tracking branch 'origin/main' into pr-5224
# Conflicts:
#	crates/ui-egui/src/panels/brush_panel/tests.rs
#	crates/ui-egui/src/panels/brush_sections/tests.rs
#	docs/brush-parity.md
#	docs/gaps.md
2026-11-08 14:51:51 +01:00
David Mann d95faebaa3 Merge remote-tracking branch 'origin/main' into pr-9077
# Conflicts:
#	docs/gaps.md
#	docs/target-app-parity.md
2026-11-08 03:04:43 +01:00
David Mann ce7706f041 Merge remote-tracking branch 'origin/main' into pr-5224
# Conflicts:
#	crates/ui-egui/src/panels/brush_panel/tests.rs
#	crates/ui-egui/src/panels/brush_sections/tests.rs
#	docs/brush-parity.md
#	docs/gaps.md
2026-11-08 02:49:33 +01:00
960bd438e3 Merge origin/main into PR #5250 (Both Axes on the Scatter header)
Co-Authored-By: augrclk <augrclk@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 02:37:20 +01:00
David Mann 9849f778d8 Merge remote-tracking branch 'origin/main' into HEAD
# Conflicts:
#	docs/gaps.md
#	docs/scorecard.md
#	docs/ui-parity.md
2026-11-08 01:43:45 +01:00
c751712a53 Merge origin/main into PR #5250 (Both Axes on the Scatter header)
Main added Brush Tip Shape Angle/Roundness tests and doc rows at the same places as
this PR's Scatter header tests and rows; both sides are kept, and the doc status
lines name both changes.

Co-Authored-By: augrclk <augrclk@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 23:45:02 +01:00
David Mann 977f80dc42 Merge origin/main into PR #9058 (raw: Hasselblad 3FR/FFF)
# Conflicts:
#	docs/gaps.md
2026-11-07 23:30:32 +01:00
David Mann b8b539c845 Merge origin/main into PR #9058 (raw: Hasselblad 3FR/FFF) 2026-11-07 19:37:23 +01:00
7f09b2013d Merge current main into #9077 (Average on Smart Objects)
Resolves the status-line conflicts in docs/gaps.md and
docs/target-app-parity.md (both sides kept) and the module list in
crates/engine/tests/it/main.rs.

Co-Authored-By: arjen <76012639+ArjenS-LA@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 18:48:12 +01:00
David Mann 7892e148b2 Merge origin/main into PR #5250 (Both Axes on the Scatter header)
Main moved brush_sections.rs to panels/, its tests to brush_sections/tests.rs and
split widgets.rs into modules. The PR's tests now live in brush_sections/tests.rs and
scrub_label_within is crate-visible from widgets/scrub.rs; both doc status lines and
history rows keep both sides.
2026-11-07 17:30:24 +01:00
a3712435c3 Merge main into #5269 (Brush Tip Shape Angle/Roundness layout)
main moved brush_sections.rs to panels/ with its tests in
panels/brush_sections/tests.rs, and gave the Angle and Roundness fields
accessible names. Keep the PR's left column and right-hand widget with
main's accessible names, move the PR's three layout tests and
section_harness_width into the new tests file, and find the captions with
main's caption_rect (get_by_label now also matches the named fields).
Docs: both sides' status entries kept.

Co-Authored-By: augrclk <217665348+augrclk@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 17:07:55 +01:00
David Mann fd4f9c9703 Merge remote-tracking branch 'origin/main' into pr-5224
# Conflicts:
#	crates/ui-egui/src/panels/brush_sections.rs
#	docs/brush-parity.md
#	docs/gaps.md
2026-11-07 15:09:34 +01:00
b9323e117e Merge main into #7377 (Fujifilm compressed RAF)
Main moved crates/raw/src/testgen.rs into testgen/ and the integration
tests into tests/it/, and its preview fallback now also opens a larger
JPEG XL preview. The compressed RAF test generator moves to
testgen/raf.rs; the JPEG fallback and the damaged-data fallback both go
through preview_image (Adobe RGB tag from the EXIF) under the caller's
decode limits. Doc status lines and histories keep both sides; G14 and
the Camera Raw row no longer list compressed RAF as missing.

Co-Authored-By: Yorgos S. <smyrgeorge@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 13:43:08 +01:00
RickiNano 1aa85bd796 Windows: the pen's eraser end selects the Eraser (#2285)
The pointer hooks read PEN_FLAG_INVERTED from GetPointerPenInfo and
report the pen end through Services::pen_end, so flipping the pen
switches to the Eraser and the tip restores the previous tool, as on
macOS. A WH_CALLWNDPROC hook beside WH_GETMESSAGE catches
WM_POINTERLEAVE, which Windows documents as sent, not posted.
PEN_FLAG_ERASER alone is an eraser button on the barrel and does not
switch tools.
2026-11-06 19:45:17 +01:00
David Mann e8bb14bb0f Merge remote-tracking branch 'origin/main' into HEAD
# Conflicts:
#	docs/scorecard.md
#	docs/ui-parity.md
2026-11-06 14:05:34 +01:00
Gotya-OFFCIAL e7241d2e26 raw: decode Hasselblad 3FR/FFF (X2D II 100C, uncompressed)
The X2D II 100C's 3FR files develop at full sensor resolution instead of
falling back to the camera's embedded JPEG preview. A 3FR is a TIFF whose
IFD0 holds the camera's JPEG and whose CFA image sits in a SubIFD with
DNG-style tags but no DNGVersion and no CFA-pattern tag (neither the
TIFF/EP pair nor the EXIF one), which is why the generic TIFF/EP path
reported "CFA pattern not recorded". The new decoder reuses the shared
strip reader, takes BlackLevel, WhiteLevel, DefaultCropOrigin/Size and
AsShotNeutral from the file, and takes the Bayer phase from a measured
per-body table (the X2D II 100C: RGGB at the data origin, measured
against the camera's own embedded JPEG; unmeasured bodies warn).
Lossless-JPEG 3FR stays preview-only, like uncompressed-only ORF and RAF
before it.

Clean-room: the TIFF 6.0 and DNG specifications, the sample file's bytes
and its embedded JPEG; no code or tables from dcraw, LibRaw, rawspeed,
rawler or darktable.

Adds round-trip and hostile tests through a mirrored synthetic builder
(tests/it/hasselblad.rs, testgen::Hasselblad3frSpec) and updates the
support matrix (file-format-parity, gaps G14, target-app-parity).
2026-11-06 12:05:10 +08:00
David Mann 0fb63e2f58 Merge commit '9bef3b2dc8b84c1b6cefc8ce0dc2b01d089cd9e8' into HEAD
# Conflicts:
#	docs/gaps.md
#	docs/scorecard.md
#	docs/ui-parity.md
2026-11-06 02:07:26 +01:00
ArjenS-LA a38fc6ff5b Refresh Average Smart Filter stack and verify selection and channel values 2026-11-05 15:53:24 -08:00
ArjenS-LA 19b5ef0fb3 Merge current main and strengthen Average selection regression 2026-11-05 15:52:30 -08:00
Tom InglingandClaude Fable 5.1 0679f71795 Merge storytold main (e4e2607c8, spot ink preview #10276) into pdf-spot-plates
docs/gaps.md: the header takes main's line with the G10 PDF spot-plate
item in front. In the G10 body both sides moved a half of FILE-215-17
out of the Missing list: the preview (#10249) and the PDF Separation
plates. Both Progress entries stay, newest first, and the Missing item
keeps only what main left open, the preview's ink order at overlaps and
its look at solidity 0.5 and 1 against reference files.

The compose multichannel rewrite keeps the ink polarity (spot value =
ink, alpha-type channels 1 - value), so the PDF plates are unchanged.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-05 17:44:05 -05:00
e4e2607c85 Preview spot inks over the RGB/CMYK composite as an overprint (#10276)
* PSD: a flat file's named extra channels are not its transparency

A PSD without layer records had its first extra channel read as the merged
transparency, so the first spot or alpha channel of a flattened document
vanished and its ink punched a transparent hole in the image (psd-tools
cmyk-alpha-spot.psd, cmyk-spot.psd). When the alpha channel names (1045, or
1006 when 1045 is missing) name every extra channel and the first is not
"Transparency", there is none. The import also reads channel names from
1006 when 1045 is missing, and a spot whose DisplayInfo colour space is
unknown takes its ink from the Alternate Spot Colors (1067).

* channel_map: read 1067 records with as_chunks

* Preview spot inks over the RGB/CMYK composite as an overprint

Visible spot channels of RGB and CMYK documents print over the canvas
composite with the Multichannel ink formula, in Channels panel order,
instead of a tint; alpha channels and Quick Mask still tint over them.
Spot channels open visible, and the PSD thumbnail (1036) prints them.
flatten, export and the merged image stay free of spot inks. Ink
colours go through the document's CMYK profile, and into an RGB
document's own profile.

Refs #10249

* channel_view: test solidity over a gray picture

* Thumbnail: matte only inked pixels; test the merged planes directly

Review feedback on #10276: pixels without spot ink keep their
transparency in the thumbnail buffer, and the thumbnail test checks the
saved merged process planes instead of a re-imported flatten.

* Thumbnail spot inks: sample big boxes on a 4x4 grid

Review feedback on #10276: reducing each spot channel no longer scans the
whole document; a thumbnail pixel averages at most 4x4 samples, so a huge
document costs about h x 4 row reads per spot.

* Spot overlay: composite a proxy for the first draw of big documents

The overprint needs the composite under the inks. Rendering the full-resolution
composite to sample every factor-th pixel cost ~1 s for a 24 MP CMYK document.
A proxy document picks the same pixels (nearest-neighbour downsample) for about
1/factor² of the work. Documents with layer effects, vector masks or feathered
masks keep the full-resolution path.

* Proxy: downsample rows in parallel; the overlay proxy leaves out unused channels

The spot overlay's proxy shrank the spot channels, selection and Quick Mask that an
RGB or CMYK composite never reads: about two thirds of its cost. downsample now
reads its rows in parallel, 64 at a time.

* Spot overlay proxy: keep the full-resolution composite under uncached pattern fills

A pattern fill renders its tiles at their own size on a proxy, so the proxy would
sample other pattern pixels. Unless the fill's matching cache is shrunk with the
proxy, use the full-resolution composite.

* Spot overlay proxy: hidden layers don't decide the composite path

Hidden layers and the contents of hidden groups are not composited, so a hidden
feathered mask or pattern fill no longer forces the full-resolution composite
(the same rule as proxy_faithful).

* Update crates/compose/src/multichannel.rs

Co-authored-by: aiongg <67543387+aiongg@users.noreply.github.com>

* Spot overlay: only inked pixels replace the canvas; inks through the display transform

Where every visible spot is 0 the overlay stays transparent, so the normal
canvas (display LUT, proofing, 32-bit preview, full resolution) shows; an
empty spot renders no composite. Printed pixels go through the canvas
display transform (sRGB encoding of linear composites, monitor profile,
Proof Colors), keyed into the texture. Ink colours come from one InkSpace
per render: the document's profiles are parsed once, not per spot per
damage rect.

* test(compose): cover hidden spot preview and document approximation

* fix(ui-egui): preserve solo spot channel display when process colours are hidden

Clicking a spot channel row in the Channels panel targets the channel alone
and hides all process colour channels (`vis == 0`). Overprint inks were
previously extracted from `overlays` before checking `vis == 0`, leaving
`overlays` empty and causing the solo channel view to fall back to an active
layer mask (or return `None` if the layer had no mask).

Keep the spot channel in `overlays` so `vis == 0` can select it as the solo
grayscale picture, separating overprint inks only when process colours are
visible (`vis > 0`).

Adds a regression test targeting an existing spot channel in RGB and CMYK on
an unmasked layer and editing it.

* channel_view: document the spot overprint preview as an approximation

* test(ui-egui): solo spot channel wins over an unrelated layer mask

* channel_view: sRGB spot inks, lazy display transform, inked-area composite

Review fixes for the spot overprint preview:

- RGB spot colours are sRGB, like the CMYK, Lab and gray ones: InkSpace now
  converts them into an RGB document's own profile too. In a linear (EXR/HDR),
  Adobe RGB or ProPhoto document an RGB ink showed the wrong colour (brightened
  in a linear one once the canvas sRGB-encoded it).
- The display transform is looked up only when the view is redrawn and inks
  are shown, not every frame before the cache check; display_transform_for
  borrows the proof state (proof_ref) instead of cloning a default CMYK profile.
- Visible spot channels without ink, with nothing else shown, draw no texture:
  render returns None and ensure remembers it, instead of uploading an
  all-transparent document-sized texture. A damage re-render that finds nothing
  clears its rect, so erased ink never lingers.
- Printing a few inked pixels composites only their bounding box when that is
  cheaper than the whole (proxy) composite, and a cached composite is borrowed
  instead of copied.

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

---------

Co-authored-by: aiongg <67543387+aiongg@users.noreply.github.com>
Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 23:34:12 +01:00
Tom InglingandClaude Fable 5.1 ca9e0b3ddc Merge storytold main (e55e2f357, background Place decode) into pdf-spot-plates
Only docs/gaps.md's header conflicted: main's line with the G10 PDF
spot-plate item kept in front.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-05 16:59:39 -05:00
5c157e8400 Build the GPU compositor off the UI thread (#9969)
* Prepare GPU compositor in background

* Add compositor startup performance scenarios

* Generate scorecard in scratch CI

* Fix pipeline build type inference

* Address compositor startup review and document measurements

* Run startup performance checks on scratch CI

* Remove scratch performance job

* Count failed GPU attempts in frame timing

* Make shader fingerprint test portable

* Address review: run first-document scenario as a guarded group; share COMPOSITOR_BUILDING constant

* Handle compositor startup benchmark failures

* Fix frame timing after upstream merge

* Keep GPU startup failures on CPU fallback

* Record the failed GPU attempt of a deferred redraw, and bound --reps

A redraw deferred after a failed GPU composite carries that attempt's time to its end, where it is
recorded once beside the CPU time. Perf::record clears the attempt so a direct CPU refresh doesn't
inherit the last one's. perf_scenarios caps --reps.

* Refresh cached canvases after compositor build

* Expose canvas cache key to GPU completion handler

* ci: drop the GPU compositor probe job

AGENTS.md: feature and fix PRs don't add or change CI, and app workflows
only call the shared craft-common-actions workflows. The probe example
stays as a manual tool (cargo run -p photocraft-gpu --example
compositor_build_probe).

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

---------

Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 22:49:12 +01:00
6326117c53 Web build: big operations that won't fit fail with an error instead of trapping, and a trap says so (#10129)
* Raster: ask the allocator whether a big operation fits; PSD save reserves its whole output

New photocraft_raster::headroom: ensure(need) tries to reserve the need plus a margin and frees it
at once (web only; native always passes), below(bytes) tells whether less than that can be
allocated, and simulate_available fakes the answer for tests and diagnostics.

PsdFile::to_bytes reserved only the pixel data, so embedded smart-object data and other tagged
blocks grew the output by doubling, which on the web is one unrecoverable huge realloc. It now
reserves the exact encoded length.

* Big operations that won't fit in memory fail with an error instead of trapping

Place, Image Size, a smart-object re-render and Save now ask the allocator whether their estimated
need can be allocated before they start (photocraft_engine::memory_gate, estimates per operation,
photocraft_io::estimate_export_bytes for saves). A refusal changes nothing and says what the
operation needed and what to do (save, Edit > Purge > All, close documents).

A standing notice warns when less than 512 MiB can be allocated, with hysteresis, so the user can
save before anything traps. Notices can now be sticky, which keeps them past the auto-hide delay.

* Web build: a trap says so instead of freezing the page, and ?free-mb simulates low memory

index.html holds a hidden, static 'PhotoCraft stopped' message with a Reload link. web.rs reveals
it when the module traps (the window error event, or an unhandled rejection from a spawned
future), tells running out of memory from an internal error by the panic message a chained panic
hook kept, and skips the Leave site prompt once stopped. ?free-mb=N makes the memory gates and the
low-memory warning see only N MiB, for checking them in a browser.

* Docs: web build memory handling in the gaps list and the parity assessment

* Engine: use is_multiple_of in the megabyte formatter

* Cargo.lock: the web shell depends on photocraft-raster

* Web boot page: carry the stopped message too, as the test that keeps the two pages equal requires

* Save check counts one copy of the document, and runs once per save

The encoded file's buffer is reserved fallibly, so a save it doesn't fit
in fails with an error instead of trapping; counting it as well refused
saves that would have fitted. On the background-save path the check ran
in write_document and again in start_save, which other callers reach
directly; it now runs only in start_save there.

* Review fixes: warning check allocates nothing, PSD merged image counted, automation saves checked

The low-memory warning's periodic check reads the size of linear memory
instead of reserving a block: wasm memory never shrinks, so a trial every
few seconds raised the high-water mark by up to 768 MiB for nothing. The
operation checks keep their trial reservation.

PSD and PSB saves always build a canvas-sized merged image (a placeholder
without maximum compatibility) and hold it raw and encoded; the save
estimate now counts it. The synchronous automation save checks memory
before encoding, as File > Save does, and its test asserts the memory
error.

The trap listeners go in before the app is built, so a trap while
building it shows the stopped message too, and a memory access out of
bounds reads as unexpected rather than as running out of memory.

* Cover layered PSD preflight limitation

* Fix layered PSD test iteration

* Docs: layered PSD preflight limitation in the gaps list

* Web OOM gate: gate direct exports, truthful advice for unbounded PSD sources (#10024)

* Web OOM gate: estimate layered PSDs per layer instead of refusing every real PSD

The PSD check admitted a file only when its color-mode data, image
resources and layer section were all empty. Every Photoshop file has
image resources (at least the resolution), so on the web nearly every
real PSD was refused for Place, PSD smart objects could not render,
and Image Size was refused for documents holding one, whatever the
free memory (u64::MAX never fits).

The color-mode data and image resources hold no pixels, so they are
now only skipped. A layered file reads its layer count from the layer
info (signed, absolute value) and adds a canvas-sized surface and mask
per layer to the flat estimate. Files that are not 8-bit RGB or
grayscale, keep their layers in a Layr tagged block, or whose sections
can't be read stay unbounded and refused, as before.

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

* Web stopped page: match trap messages ignoring case

Safari reports the allocation-failure abort as "Unreachable code
should not be executed" and Firefox says "out of memory", which the
case-sensitive matching missed: the trap was not recognised, or was
shown as an unexpected stop instead of running out of memory.

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

* Export estimate: count the merged image OpenRaster writes

The ORA writer keeps the layers but also flattens the document for
mergedimage.png, a 16 B/px composite of the canvas, which the
estimate left out.

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

---------

Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 22:04:59 +01:00
Tom InglingandClaude Fable 5.1 d50be910ec Merge storytold main (405b56c3c, integration-batch-17) into pdf-spot-plates
The Batch spec string takes main's rewritten form with pdfSpotPlates
reinserted before calledActions; docs/gaps.md's header is main's line
with the G10 PDF spot-plate item kept in front.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-05 12:46:22 -05:00
David Mann a4a10166df Merge remote-tracking branch 'origin/main' into integration-batch-17
# Conflicts:
#	docs/gaps.md
2026-11-05 14:33:32 +01:00
Tom InglingandClaude Fable 5.1 e19640354c Merge storytold main (a38e544fa, tile-sparse Select and Mask) into pdf-spot-plates
Only docs/gaps.md's header conflicted: main's line with the G10 PDF
spot-plate item kept in front.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-05 08:27:31 -05:00
Khawar Zamman WaniandFredApps a38e544faf Make Select and Mask refinement and previews tile-sparse (#10732)
* Make Select and Mask refinement and previews tile-sparse

* Preserve finite mask extent when copying sparse pixels

* Keep solid preview coverage shared during conversion

* Preserve alpha sharing and document sparse refinement limits

* Refine mixed tiles in bounded parallel batches

* Skip guide sampling for constant refinement halos

* Balance sparse refinement batches using measured queue size

* Preserve default alpha and cancel sparse refinement cooperatively

* Address review follow-ups: cancellation after completed flush, stroke assertions, and docstrings

* Fix merge: bind tile bytes to avoid temporary borrow in matting

---------

Co-authored-by: FredApps <fredapps@users.noreply.github.com>
2026-11-05 14:22:03 +01:00
Tom InglingandClaude Fable 5.1 fe619d5777 Merge storytold main (0350b5de1, sky selection top border) into pdf-spot-plates
Only docs/gaps.md's header conflicted: main's line with the G10 PDF
spot-plate item kept in front.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-05 07:23:31 -05:00
David Mann 4e0d487568 Merge remote-tracking branch 'origin/main' into integration-batch-17
# Conflicts:
#	docs/gaps.md
2026-11-05 13:21:45 +01:00