Commit Graph

79 Commits

Author SHA1 Message Date
Gert Pellin - GPE
ba40454b18
Logging: a second launch leaves the running instance's log alone (#6432) (#6478)
logging::rotate renamed photocraft.log up the chain on every start without
knowing whether another instance still wrote to it. The running instance
kept its handle, so after a second launch its live log was .1.log, after a
third .2.log, and a fourth launch deleted it.

The instance that writes photocraft.log now holds an exclusive lock on
photocraft.lock until it exits (a separate file, since Windows locks are
mandatory and would keep the log itself from being opened). A start that
finds that lock held leaves the chain alone and writes
photocraft-<pid>.log, marked by photocraft-<pid>.lock. Those side-by-side
logs are pruned to the newest KEEP once their instances have exited; a
held one is never touched. Rotation and pruning run under rotate.lock so
two starts at once can't interleave. On a file system without locks every
start rotates as before.
2026-10-17 11:57:39 -06:00
Gustavo Ziger
d900daa0ba
fix(web): File › Place Embedded is enabled in the browser and places the picked file's bytes (#1209, #5749) (#5585)
With a document open in the web app, Place Embedded was disabled because its menu enablement inherited the engine's native-filesystem check, although the menu path reads the picker's bytes and never touches a path. Place Embedded is now enabled whenever a document and a file-dialog service exist; Place Linked keeps the engine's native guard, since the web could only link to a bare file name (caught in #5817 by Higgs32584). An enablement test runs with the native check forced off, and a file-dialog test covers the browser answer shape, cancel, invalid bytes and the Free Transform handoff.

Fixes #1209. Fixes #5749.
2026-10-16 17:38:56 -07:00
JGF
2a952f164c
Windows/Linux: open files in the running PhotoCraft instead of a second instance (#3708) (#3755)
Fixes #3708; part of #1540 (Flatpak needs its own follow-up: each sandbox has its own loopback).

A second launch hands its files to the running instance over an authenticated loopback channel (random port, 32-byte token in a private instance.json, constant-time check, bounded clients, requests and paths). Files are acknowledged only once the UI has taken them; the instance shuts the hand-off down before a CPU-compatibility relaunch; it runs only in the per-user settings mode. --new-instance and PHOTOCRAFT_NEW_INSTANCE=1 opt out.

Thanks to @MrCloudy2 (#2358), whose Linux version first handled the relaunch and per-user endpoints, and @Desayuno64 (#5273), whose shutdown flag and read deadline informed the final design.
2026-10-15 22:48:30 -07:00
skylartaylor
8f58ea4629 ci: integrate current main while retaining strict test gates 2026-10-16 05:25:24 +02:00
Tanish Manku
b09aeae2e2
Low-Latency Drawing preference (vsync off, opt-in), plus present-mode overrides for testers (#1343) (#5274)
* PHOTOCRAFT_PRESENT_MODE / PHOTOCRAFT_FRAME_LATENCY launch overrides for latency A/B tests (#1343)

Linux traces put the remaining stroke latency in presentation queueing (the app's frame fits
in one refresh; the rest waits on the swap chain), and an NVIDIA user cured it by forcing
vsync off. Testers can now compare without rebuilding: PHOTOCRAFT_PRESENT_MODE (fifo,
mailbox, immediate, autovsync, autonovsync) and PHOTOCRAFT_FRAME_LATENCY (1-3), applied over
the platform default. Bad values are logged and ignored. The active mode is logged at start,
kept in GpuInfo.present and shown in every stroke trace line.

RTX 5090, Vulkan, 120 Hz: default autovsync/latency 1 waits 6 ms per 8.3 ms frame; mailbox
renders every 2.3 ms with 0.2 ms wait, so each vsync shows the newest frame.

* docs: present mode and frame latency overrides

* Present mode overrides never request a mode the driver may lack (Mailbox crashed on X11/NVIDIA)

* Preferences › Performance › Low-Latency Drawing, on by default: no vsync wait while drawing (#1343)

Testers' traces put the ~50 ms stroke lag in the swap chain's vsync wait. With vsync off it
disappears (X11/NVIDIA; Windows/NVIDIA via the driver), and a shorter queue doesn't help. The
surface now presents with AutoNoVsync by default: wgpu picks Immediate, else Mailbox, else
Fifo by what the driver offers, so no unsupported mode is ever requested. Turning the
preference off brings vsync back (smooth, but late). It applies at the next launch, like the
other graphics settings. Where only Immediate exists the canvas may tear while it changes;
frames render as fast as input arrives during a stroke and stop when idle.

* Runtime and launch controls: ui.set strokeTrace, --stroke-trace, --present-mode, --frame-latency

The retro's remaining gap: the stroke trace and the presentation overrides were env-only. The trace now switches at runtime over the control channel (ui.set {"strokeTrace": true|false|"stderr"|path}) and with --stroke-trace; --present-mode / --frame-latency sit beside their env vars and win over them. Bad values are reported and ignored; none fails a launch.

* docs: strokeTrace beside the other ui.set fields

* docs: regenerate the scorecard after rebasing onto main

* Low-Latency Drawing: frames capped at 240 fps without vsync (after #4472); ui.set strokeTrace takes on/off only (a CSV path is launch-only); tearing documented as usual (Immediate); pref hidden on web; doc comment order

* stroke_trace: from_value doc says ui.set passes only on or off

* Low-Latency Drawing ships off (opt-in), per the owner's decision

* docs: present mode default is vsync (Low-Latency Drawing ships off)
2026-10-15 15:23:47 -07:00
David Mann
e22c4ec7ab CI: run the tests with cargo nextest (#6117)
cargo test runs the ~258 test binaries one at a time; nextest runs them in parallel. Its new `ci`
profile stops at the first failure (a red PR frees its runner early), fails a test that hangs for
10 minutes instead of letting it hold the runner, and repeats every failure at the end of the log;
the GPU test group still applies. Same --workspace --locked --features heif selection, so the build
is unchanged. Doctests stay on cargo test --doc.

postscript_only_style_shows_actual_subfamily relied on another test in the same process having
started the font scan; under nextest it runs alone, so it starts the scan itself (as #2933 does).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-15 14:40:15 +02:00
Zuujis
df055f3dcb
CI: fix the tests that keep main red, and ship the wasm as compressed parts (#5926, #5302) (#5941)
Some checks are pending
Documentation / mdBook build (push) Waiting to run
Packaging lint / lint (push) Waiting to run
Packaging lint / Verified AppImage inputs (push) Waiting to run
* Fix two ui-egui tests broken on main by concurrent landings

- profile_open_tests: #5614 renamed the built-in profile to "(PhotoCraft)" after #5208
  hard-coded "(Photocraft)" in the Use-embedded button label.
- smart_object_curves_preview_keeps_its_size_on_a_reduced_proxy (#5779): the reduced
  square is in the right place, but its corner pixels are 220/255 after the smart
  object's resampling; assert full coverage inside and over half at the corners.

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

* Web: ship the wasm as compressed parts behind a boot module (#5302)

The release site held the app's wasm as one file, which sits at the 25 MiB
per-file cap of Cloudflare Pages, so the 24 MiB gate failed unrelated PRs.

packaging/web/package.sh now gzips the wasm and cuts it into parts of at
most 20 MiB under app/, and ships the page of a new crate,
apps/photocraft-web-boot. That module fetches the parts in order, has the
browser inflate them (DecompressionStream) and passes the stream to the
app's own wasm-bindgen loader, so the browser still compiles while it
downloads. A failed download is retried twice, then the page says so.

The check is now that no file in the site is over 24 MiB. trunk build in
apps/photocraft-web and the Docker image still give one .wasm.

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

* Web boot: inflate the parts in the module where the browser can't (#5302)

* Tests: the adjustment-layer drag preview test follows #5778 (#5926)

#5778 caps a live adjustment-layer preview by the view's zoom, so at 100 %
the drag shows full resolution even with Low Resolution Previews on. The
test still expected the reduced copy there. It now checks that at 100 % every
column shows by default, and moves the low-res on/off comparison to 50 %,
where the reduced copy is still used: on, the drag shows only the (inverted)
black columns; off, it matches the committed view.

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-15 09:21:30 +02:00
jonathanwxh-cell
8d2806f66c
Expose shared Smart Object instances in inspection and replacement results (#4832)
* Smart Objects: inspect and Replace Contents show shared instances (#4075)

Placed layers that share a source have imported as one set of contents
since #2222, and Replace Contents changes every instance, but nothing
reported it: `linkGroup` is Link Layers, and `smartSource` can't tell
copies of one contents from independent copies of the same file.

document.inspect now gives each smart object `smartContents`: the
contents `id` its copies share and how many `instances` there are.
Replace Contents, Relink to File and Convert to Embedded/Linked list the
`instances` they changed.

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

* Test rendered caches after shared Smart Object replacement

---------

Co-authored-by: jonathnwxh <258340642+jonathanwxh-cell@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 12:46:04 -04:00
Roberto Dutra
887dcd7ef9
Linux: a virtual keyboard claiming BTN_TOOL_PEN no longer sends the window through Xwayland (#5284) (#5295)
* Linux: a virtual keyboard claiming BTN_TOOL_PEN no longer sends the window through Xwayland (#5284)

On Wayland the app opens its window through Xwayland when a pen is attached
(#639), and it took any input device whose key bitmap has BTN_TOOL_PEN for a
pen. A virtual keyboard made with uinput (RustDesk's, for one) claims every
key code, that one included, so the window went through Xwayland on machines
with no pen at all. On a fractionally scaled desktop (GNOME 46 at 125%) the
X11 window is drawn at the logical size and upscaled by the compositor, so
the UI came out blurred even though Auto UI Scale already followed the
system's 1.25 (#1237): native Wayland, where winit negotiates
wp_fractional_scale_v1 and renders at the physical 3840 px, was never used.

A pen now also needs ABS_X and ABS_Y in its absolute-axis bitmap, as
libinput asks of a tablet tool; a keyboard has no axes. Regression tests
with the virtual keyboard's own bitmaps, a pen beside it, and a pen display.

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

* Docs: on a fractionally scaled Wayland desktop, Xwayland is blurred and native Wayland has no file drops (#5284)

Say where the trade-off is: a native Wayland window is sharp at 125%,
150% and 175% but takes no dropped files (#386); an Xwayland window takes
drops and pen input (#639) but is enlarged by the compositor. List the ways
to open through Xwayland and what counts as a pen.

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

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-14 12:45:30 -04:00
wwew
9df59c96b8
docs: write product name as PhotoCraft in development guide (#5228) 2026-10-14 01:58:14 -04:00
Tanish Manku
583425b4df
Stroke latency trace: PHOTOCRAFT_STROKE_TRACE times each frame of a brush stroke (#1343) (#4403)
Testers report ~50 ms input-to-pixel while painting on Linux and macOS, and asked for a
per-stage breakdown. With PHOTOCRAFT_STROKE_TRACE=1 the app prints, per stroke, p50/p95 of
the frame interval, pointer moves per frame, the live stroke's render, the canvas refresh
(composite + upload), the UI build, eframe's CPU time (UI, tessellation, submission) and the
remaining vsync/swap-chain wait, with the GPU backend; =<file.csv> also appends every frame.
The last summary is in the control channel's perf.strokeTrace.

RTX 5090, Vulkan, 120 Hz: interval 8.33, cpu 2.2 (ui 0.9, stroke 0.8, canvas 0.4), wait 6.0 ms.
2026-10-13 18:09:37 -07:00
Nigel Ihza Firdausy
0df333f623
fix(gpu): require consent to persist CPU recovery (#3539) 2026-10-13 12:46:33 -04:00
Fabio Lissi
70e182b08f
panic_hunt: don't fuzz the print commands into a real printer (#4467)
* panic_hunt: don't fuzz the print commands into a real printer

file.print and file.printOneCopy spool to CUPS, so every run of the adversarial-params test
printed a blank page on the default printer. The hunt now skips those two ids (their params
are validated like everyone else's; spooling is not what the test is for).

* Guard the print spooler itself and the CI environment

- print_cmds: disable_printing() (a process-wide flag the panic hunt sets) and the
  PHOTOCRAFT_NO_PRINT environment variable make spool_pdf refuse before running lp; dryRun
  still renders and reports the command. Test.
- CI: PHOTOCRAFT_NO_PRINT=1 and LPDEST=photocraft-no-such-printer, so a regression fails
  loudly instead of printing. Documented in docs/development.md › Environment variables.
2026-10-12 23:13:42 -04:00
deadmeu
465a663540
feat(ui): developer tile grid overlay (ui.set tileGrid) (#4217)
Draw the active layer's storage tiles over the canvas: a faint
TILE_SIZE grid over the document, every stored tile filled and
labelled tx,ty, tiles with identical pixels in one colour per group
and unique tiles in blue. It shows how sparse a layer is, which tiles
copy-on-write shares, and which ones .pcraft saves once.

It has no menu item and isn't a View > Show item. The control channel
and MCP turn it on with ui.set {"tileGrid": true}; ui.inspect reports
it as view.tile_grid, and View > Extras doesn't hide it.

Grouping hashes each distinct tile allocation once, in parallel (or
serially while jobs hold the pool, #1554), cached per document
revision and active layer. A 36 MP layer of all-different tiles groups
in 3.9 ms (8-bit) and 7.5 ms (16-bit) on 24 cores in release; the
ignored tile_grouping_36mp test reproduces this.

Also documents tileGrid in docs/control-protocol.md and
docs/development.md, and adds my contributors/people.toml entry.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-12 21:31:32 -04:00
Fabio Lissi
3204231d7d
Load and save Photoshop Actions (.atn) (#4356, #219) (#4434)
* Load and save Photoshop Actions (.atn) (#219)

- photocraft-psd: `atn` parser and writer for version-16 action sets, exact round trip,
  truncation/garbage safe, fuzz target and `atn_dump` example. Descriptor fix: `indx` and
  `Idnt` reference items carry a class before the number (real action files failed to parse).
- photocraft-engine: `atn_cmds` maps 92 Photoshop events to commands and back (filters,
  adjustments per channel, image/canvas/crop/trim/rotate/flip/mode, make/set/select/delete/
  hide/show/move/duplicate, fill/stroke, selection, clipboard, purge, profiles, play, stop).
  Other steps load as `actions.photoshopStep` (descriptor kept, written back by save, fails at
  playback by name); tool selections load switched off. Commands `actions.load`,
  `actions.save`, `actions.stopMessage`, `actions.photoshopStep`. `Action` gains `dialogs`
  and `set`. `layer` params accept a layer name, so loaded steps replay in any document.
- UI: Actions panel menu › Load Actions… / Save Actions…; Photoshop steps labelled.
- CLI: `batch --actions set.atn`. Automation policy covers the new path params.
- Docs: file-format parity (.atn R/W), gaps G11, target-app parity, scorecard AUTO-219-2
  partial, development guide.

* Translate the Actions panel's Load/Save strings in every catalog

The eight new panel strings (Load/Save Actions…, the loaded/saved counts, the kept-step label)
were missing from all 19 catalogs; the languages that were complete are back at 100%.

* Show the Actions entries in the default dock's panel menu

The legacy dock renders its own group menu and only knew the Layers and Swatches entries, so
Load Actions… and Save Actions… were missing there.
2026-10-12 18:39:57 -04:00
skylar taylor-barrick
a301e31864 Merge current main fixes and validate craft-print rollout 2026-10-12 19:39:05 +02:00
Brandon Thomas
184c226a76
Fix main: wasm size cap, Windows Export As test, filter preview window (scope and a crash), CLI duplicate font warnings (#4147)
* test(ui-egui): Export As artboards test accepts the Windows path separator

The test added in #3909 compared the second file's path with a literal
'/', but the exporter joins the sibling name with the platform separator,
so Windows CI saw '/out\boards-2-Capsule.jpg' and failed.

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

* fix(ui): only Filter menu dialogs get the in-dialog preview window

#3877 drew the preview window in every schema dialog with a Preview
checkbox, including Image > Rotation > Arbitrary. Photoshop's Rotate
Canvas and adjustment dialogs have no preview window, and the taller
dialog covered the canvas, which broke
arbitrary_rotation_preview::size_changing_rotation_preview_is_visible_and_preserves_the_panned_center
on main. The window and its reserved height now apply to filter.*
commands only.

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

* build(web): opt-level z for large cold crates in the wasm release profile

The browser wasm sat at the 24 MiB gate. The document model, file
parsers (psd, format, affinity), serde_json, usvg and naga are off the
per-frame and per-pixel paths; opt-level "z" for them takes the
release wasm from 25,186,233 to 24,829,840 bytes (-356 KB) with no
behaviour change.

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

* fix(cli): run prints each missing-font warning once

Since #3630 the headless open and save also report missing fonts, so
`run <file> --out` and `run --cmd ... --out` printed the same warning
two or three times and missing_typeface_is_reported_by_run_info_and_convert
failed on main. Warnings from the open and the save now go through the
same already-reported set as the per-command ones.

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

* fix(ui): the filter preview window never uploads a texture past the GPU limit

With low-resolution previews off, the preview window from #3877 uploaded
the whole document as one texture. egui panics on a texture bigger than
max_texture_side, so opening a filter dialog on a large image crashed
(full_res_preview::filter_dialog_previews_at_full_resolution_when_low_res_is_off).
The texture is now reduced to fit; the view maps through UV fractions,
so it still lines up.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 11:48:28 -04:00
skylar taylor-barrick
83fcbe7d9b Merge current main while preserving hardened print contracts through craft-print 2026-10-12 07:36:25 +02:00
Unemars
6ecad20132
Content-Aware tools: faster, cancellable at once, same pixels (P21, P22, P49) (#3395)
* Content-Aware tools: faster, cancellable at once, same pixels

#2202 matched Photoshop's Content-Aware Fill but made the perf nightly slower (#2575): P21
Content-Aware Fill 10 MP 5x, P22 Spot Healing 2.4x, P49 cancel on 24 MP 4x and over its 200 ms
budget. This keeps every result bit for bit and takes the time back:

- The solver works over the hole's bounds grown by a patch: match field, costs, votes and the
  per-target tables are kept there (nothing outside changes or is matched), the vote computes
  each target's weight once and writes into a buffer instead of a Vec per pixel.
- Colour adaptation reuses each patch's own moments (target sums kept per EM step, source sums
  per level where they pay off), summed in the same order as before.
- Exact early outs: SSD per pixel instead of per row (callers only ask "better?"), and the onion
  peel's exhaustive search starts from where the filled neighbours took their sources and stops a
  source once it can only lose (first-of-equals kept).
- PatchMatch sweeps a band in wavefronts of blocks when there are fewer bands than threads: each
  cell sees the same neighbours and random numbers (SplitMix64 skip) as in raster order.
- Parallel pyramid, valid-source and target setup; no copy of the window when there are no
  variants (fill_owned / complete_masked_owned); the job reads the window in parallel bands and
  keeps only the selection's bounds for the apply step.
- Cancellation is checked per row everywhere, also in Auto sampling and in its max flow
  (maxflow_with / grid_cut_cancellable): a 24 MP job now stops in tens of milliseconds.
- algo::par: rayon on native, sequential on wasm.

Proof: the new `content_aware_bench` example hashes every tool's output (Fill with each sampling
and adaptation option, Edit > Fill, Delete and Fill, Spot Healing x3, Patch, Content-Aware Move,
8/16/32-bit, Gray/CMYK/Lab, and the benchmarks' 10 and 24 MP fills): 117 hashes identical to
main, also with 1, 2 and 4 threads. New tests compare the onion peel with a plain scan, full and
clipped patch distances and moments, the valid-source runs with the summed-area test, thread
counts 1/3/16, in-place and copied fills, and cancellation of Auto sampling and max flow.

perf_scenarios --cpu, Ryzen 7 5800X shared with other work (load 4-7), 3 interleaved rounds:
P21 p50 295-306 -> 167-171 ms (max 342-362 -> 180-184); P22 p50 67-68 -> 38-40 ms (max 69-71 ->
40-41); P49 max 378-389 -> 54-77 ms (p50 94-110 -> 47). retouch_bench: Content-Aware Move
-47..-64 %, Spot Healing Content-Aware -40..-61 %.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Content-Aware: faster Spot Healing dabs than main with #2805, same pixels

Against main with #2805 the branch was slower for small Spot Healing dabs (size 50:
+25-30 %, single-threaded +60 %): its onion peel walked all 49 offsets of every candidate patch,
checking each against the known pixels, and had no bound at the hole's edge.

- Onion peel: the target's known pixels are gathered once (#2805), the search starts from the
  filled neighbours' shifted sources or every 16th source at the hole's edge (#2805), the limit
  a candidate must stay under is a precomputed sum (no division per pixel), and candidates are
  summed four at a time in step: their sums are independent, so the processor overlaps them.
  Each is still given up only against the best from before the four, which can only improve.
- Vote: each target's weight and match centre are taken once; a pixel's patches are visited
  over the clipped offset range, with the channel count and the colour adaptation fixed at
  compile time. Same sums, same order.
- Valid sources: sliding counts per row and stacked runs per column instead of a 7-row check
  per pixel; the k-th source is found through per-row and per-64-column counts, so no level
  but the coarsest lists them all.
- `integral` stays out of line: inlined into `synthesize` it made Create Texture slower.

Same results: `content_aware_bench -- hash` (112 cases) and `-- hash-big` (the benchmarks' 10 and
24 MP fills) equal main's, also with 1 and 3 threads; the onion peel tests (#2805's and ours)
compare with the plain scan. `content_aware_bench bench spot` runs retouch_bench's Spot Healing
dabs; with `CAF_SPOT_TYPE` (comma-separated) other types, with page faults per dab.

Ryzen 7 5800X, shared (load 5-20), interleaved with main (3af4c46a), min of rounds,
retouch_bench, all threads / one thread:
Spot Healing Content-Aware 50: 49 -> 31 / 204 -> 117 ms; 100: 62 -> 35 / 164 -> 98 ms;
200: 118 -> 67 / 250 -> 158 ms; 300: 212 -> 119 / 411 -> 285 ms. Content-Aware Move -47..-62 %
(one thread -23..-45 %). Rows that don't run this code stay within noise, except Proximity
Match 200 after the Content-Aware dabs in the same process (+8..23 %): it takes ~5800 page
faults there against ~300 on main, because main's heavier dabs left more of the heap faulted
in; alone, or after Content-Aware dabs in `bench spot`, it is equal.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-12 00:11:10 -04:00
skylar taylor-barrick
055e4fee1b Merge remote-tracking branch 'upstream/main' into codex/photocraft-craft-print-sync
# Conflicts:
#	crates/ui-egui/src/i18n/cs.tsv
#	crates/ui-egui/src/i18n/de.tsv
#	crates/ui-egui/src/i18n/el.tsv
#	crates/ui-egui/src/i18n/es.tsv
#	crates/ui-egui/src/i18n/fr.tsv
#	crates/ui-egui/src/i18n/id.tsv
#	crates/ui-egui/src/i18n/it.tsv
#	crates/ui-egui/src/i18n/ja.tsv
#	crates/ui-egui/src/i18n/ko.tsv
#	crates/ui-egui/src/i18n/nl.tsv
#	crates/ui-egui/src/i18n/pl.tsv
#	crates/ui-egui/src/i18n/pt-br.tsv
#	crates/ui-egui/src/i18n/ru.tsv
#	crates/ui-egui/src/i18n/uk.tsv
#	crates/ui-egui/src/i18n/zh-hans.tsv
#	crates/ui-egui/src/i18n/zh-hant.tsv
2026-10-12 04:10:55 +02:00
Gert Pellin - GPE
b0e555c60f
automation: export layers, data sets and CSVs through the granted roots (#3216) (#3230)
* automation: doc.place reads a file through the read root (#2810)

file.placeEmbedded takes an ambient path, so automation refuses it and an
agent could not place a file into the open document at all. doc.place
resolves the path beneath the read root the way doc.open does and hands the
bytes it already read to the public file_cmds::place_bytes, so the existing
cap-std rules stay the only path rules: no new scope logic, and the ambient
command keeps its refusal. Trusted-local callers still run the command.

* automation: export layers, data sets and CSVs through the roots (#3216)

file.export.layersToFiles and file.export.dataSetsAsFiles take an ambient
dir, file.import.variableDataSets an ambient path, so all three are refused
in automation even for files inside the granted roots, and an agent could
not export a single file or a data set at all.

Both halves already existed: the engine writes bytes it has just encoded
(save_doc is encode + write_file, and the data-set exporter encodes with
photocraft_io::export before writing), and the automation side already
holds a write capability — AuthorizedWorkspace::export_sequence creates the
directory, refuses to overwrite, accepts only single-component names and
removes every file it created when a step fails, which is what Render Video
uses. So each command body moves behind the same sink callback
render_video_with takes: the command keeps writing the bytes it is handed
to its dir, the new methods write the very same bytes through the root, and
the two implementations cannot drift.

---------

Co-authored-by: switch87 <8284204+switch87@users.noreply.github.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 21:01:26 -04:00
skylar taylor-barrick
ccee4894bd Use craft-print for PhotoCraft system printing 2026-10-12 02:28:40 +02:00
Gert Pellin - GPE
886372812d
automation: doc.place reads a file through the read root (#2810) (#3215)
file.placeEmbedded takes an ambient path, so automation refuses it and an
agent could not place a file into the open document at all. doc.place
resolves the path beneath the read root the way doc.open does and hands the
bytes it already read to the public file_cmds::place_bytes, so the existing
cap-std rules stay the only path rules: no new scope logic, and the ambient
command keeps its refusal. Trusted-local callers still run the command.

Co-authored-by: switch87 <8284204+switch87@users.noreply.github.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 19:33:29 -04:00
Valentin Klein
009bef76e7
CLI: batch plays an action from an Actions set with the whole set loaded (#2786) (#2792)
`batch --actions` ran each file in a fresh headless session with no
actions, so a step that plays another action (`actions.play`) failed for
every file, and the Actions panel's own actions.json could not be passed
at all. `--actions` now also takes an Actions set; `--action <name>`
picks the action to play and every per-file session gets the whole set.
A failed step inside a called action fails the file instead of saving it.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 06:01:07 -04:00
Ivan
39904a27ce
Docs: CI runs nextest, the craft-fonts tests have their own job
Comments and docs still said CI runs cargo test, that the craft-fonts tests run a second time in
the Linux job, and that CI's debuginfo matches the dev profile everywhere. development.md also
notes that test builds compile ui-egui unoptimized.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 11:32:35 +03:00
skylar taylor-barrick
d0a18cfd83 UI.9: keep browser bundle under size budget on current main 2026-10-10 20:56:34 -06:00
Brandon Thomas
939c8f2c70
Progress docs: target-app parity, gaps, alpha gate, readiness by audience (#2696)
Progress docs per craftrules standards/progress-docs.md: target-app parity (merges parity-estimate.md), gaps, roadmap with alpha gate, readiness by audience (full / mainstream / essentials) with Opus 5.5 hours, and UI, brush, format, hardware and localization parity docs. docs/parity.md is now docs/parity-checklist.md.

The Windows test failure (file_dialog::tests::save_dialogs_remember_where_each_document_was_last_saved) is pre-existing on main: the test hard-codes POSIX paths.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 15:09:04 -04:00
Erica Franco
31def413cc
fix(docs): Linux build prerequisites only name Debian packages (#2437)
The prerequisites listed `libxkbcommon-dev ... libgtk-3-dev`, which exist only
on Debian and Ubuntu: on Arch none of those names resolve and `pacman -S` fails
on the whole line, and on Fedora and openSUSE they all need `-devel`. Nothing
else in the repository gives the equivalents.

The app already speaks four distros at runtime: linux_libs.rs carries a
{soname, debian, fedora, arch, suse} table, reads ID and ID_LIKE from
/etc/os-release and prints the matching apt, dnf, pacman or zypper command when
a runtime library is missing. This brings the build instructions up to that.

Also adds the prerequisites to the book's Building page, which said only to
install the Rust toolchain.

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-10 14:35:12 -04:00
David Mann
3092d3b6f9
Tests: line-tables-only dev debuginfo, nextest GPU group, faster loop docs (#2204) (#2414)
- [profile.dev] debug = "line-tables-only", matching CI's CARGO_PROFILE_DEV_DEBUG; halves link
  time and PDB size for test binaries.
- .config/nextest.toml: optional cargo-nextest runs with the GPU tests capped at 4 concurrent
  processes (unlimited ones time out in wgpu).
- AGENTS.md / docs/development.md: iteration commands (--lib --tests, --test <file>, nextest,
  doctests separately). CI is unchanged.

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-10 14:03:30 -04:00
Sir Starch
a737281fad
Distance fields on threads: bevel size changes 2-3x cheaper (#2119) (#2162)
* Distance fields: the Euclidean passes run on threads, results unchanged (#2119)

The chiselled bevel fields (#2006) and the precise glow fields are built
from an exact Euclidean distance transform, a few sweeps over the whole
map that all ran on one thread. They now run on scoped OS threads, in
groups of rows or columns handed out from a queue: edt_nearest (the
column pass, then the row pass), sharp_edges (rows), refine_edge_normals
(one edge pixel each) and the final per-pixel maps of the bevel and plain
Euclidean fields. Not rayon, as for the blurs (rows_par): maps are built
from inside rayon tiles, where a waiting worker steals other tiles (#276).
Maps under 65536 pixels stay on the calling thread, wasm always does.

Every unit is computed from its own inputs, in the same arithmetic as
before, so the fields are bit for bit the old ones whatever the thread
count: 216 fingerprints (bevel in and out, plain in and out, bevel_maps on
bars at 0 to 150 degrees, thin bars, discs, rounded rectangles, soft,
patchy and half-opaque masks, 40 stars, tiny and 1 pixel maps) are equal
at 1, 7, 16 and the default threads, and equal to the code before.
edt_nearest returns the nearest pixel as u32 (it was usize), which takes its
working memory from 20 to 16 bytes per pixel; a map past 2^32 pixels or a slice that does
not match its size gets "far" distances instead of a panic.

CPU compositor, 12 MP, bevel size change (ms, bevel_bench), main -> this:
ellipse 474 -> 217 (size 8), 990 -> 318 (250); text 747 -> 214 (8),
1110 -> 267 (250). A distance_field call on a 256x256 map goes from 2.2 to
1.8 ms, on 512x512 from 8.0 to 2.8, on 2048x2048 from 99 to 15.

Tests: par_units hands every unit to the closure once with its own
slices (short last unit, no second slice, units of different sizes, 1 to
64 threads); the nearest-pixel transform equals itself on 1, 2, 3 and 8
threads on awkward sizes and returns a seed at the distance it reports;
degenerate sizes; sharp edges and the four Euclidean fields are bit-equal
on 1, 2, 5 and 16 threads on discs, rounded rectangles, bars at several
angles, patchy opacity and an edge on the border. The nested-rayon
deadlock test (#276) now has a chiselled bevel layer in its document.

* GPU effect maps: the Euclidean fields are one window, not bands (#2119)

A bevel size change rebuilds the distance fields over the whole effect
region (the region grows with the size). The GPU path cut that into bands
of max(128, halo) rows, each computed from a window grown by the halo,
which for the bevel fields is reach + 10: at size 250, 262-row bands from
windows of about 786 rows. Every pixel went through the transform and the
edge refinement about three times, a 12 MP region gave about 12 tasks for
32 threads, and the two bevel fields ran one after the other.

The Euclidean fields (chiselled bevels, precise glows) now come from one
window, and distance_field spreads its own passes over the threads. A
rebuild is the whole region, which is also what the CPU compositor
computes, so the GPU canvas gets exactly the CPU's field: bands resolved
ties between equidistant edge pixels per window, and on a rebuild they
differed from the whole region by up to 0.07 px (ellipse) and 1.3 px (600
discs) at size 250, on a few dozen pixels in 12 MP. A partial update (a
brush dab) still computes the damage grown by the halo, from one window.
The chamfer fields (shadows, strokes, spread) are a sequential sweep and
keep the bands, and so does wasm, where there are no threads to spread
over and bands keep the memory small.

RTX 4080 SUPER, release, 12 MP, ms per size change on the GPU canvas
(bevel_bench from #2118, p50, median of 3 runs, before #2006 / main /
this), ellipse: 90 / 103 / 98 (8), 162 / 188 / 119 (50), 286 / 446 / 219
(250); text: 83 / 139 / 113, 140 / 215 / 130, 222 / 489 / 165. Alone in the
process (--no-cpu) ellipse at 250 is 407 -> 157 and text 468 -> 169.
36 MP ellipse at 250: 779 -> 382. Peak RSS: 12 MP at 250 1604 -> 862 MiB,
36 MP at 250 2987 -> 1726 MiB; at size 8 on 36 MP it is 163 MiB more
(1380 -> 1543). Precise glow fields on 12 MP: 46-84 ms -> 26 ms at reach
52-252 (32 ms at 12).

The window test now checks that the whole region is bit for bit the CPU's
field, and that a too small halo is noticed on a part of the region.

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-10 06:38:33 -04:00
JohnnYoru
897f9f37ad
Add the Remove tool with non-local patch completion (#2193)
* Add the Remove tool with non-local patch completion

Tool::Remove (J flyout) paints a hole that paint.remove fills from its
surroundings as a background job, in one undo step. A stroke around an
object also removes what it encloses; the selection limits the hole.

The fill (photocraft_algo::nonlocal) follows Newson, Almansa, Gousseau and
Pérez (IPOL 2017): texture features in the patch distance, onion-peel
initialisation, match fields upsampled between levels and a best-patch
final copy, plus gradient-domain hiding of edge and interior seams.
Sparse holes are filled in tiles. inpaint::complete (Content-Aware Fill,
Spot Healing, Content-Aware Move) is unchanged.

On 20 holes in public-domain photos (remove_quality) the fill keeps 0.89
of the texture it replaces, against 0.54 with the Wexler/PatchMatch
completion. 24 MP (remove_bench): 100 px ring 120 ms, 1000 px ring 2.5 s.

* Translate the Remove tool's label and tighten comments

Adds "Remove Tool" to every UI catalog, following each one's pattern for
tool names and its existing word for Remove. Corrects comments that no
longer matched the code: the benchmark's description, the tile and margin
minimums, and the reasons given for two parameters.

* Put the Remove tool first in the J group

The J flyout's slot now shows Remove until another J tool is picked, and
J from another group selects it; repeated J goes on to Spot Healing Brush,
Healing Brush, Patch, Content-Aware Move and Red Eye.

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-10 05:19:11 -04:00
Nigel Ihza Firdausy
d502c5de27
ci(web): validate browser builds and wasm size on pull requests (#1689)
* ci(web): build browser release and enforce wasm gate on PRs

* docs(web): describe PR browser build and size checks

* CI web gate: run only when web-relevant files change (main always)

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-09 20:05:15 -04:00
Harrison O'Connor-Hoover
269d8fedc7
Keep startup responsive while recovering autosaved documents (#2115) 2026-10-09 19:50:53 -04:00
Sir Starch
819bd47bf1
perf: bevel_bench, the cost of changing a chisel bevel's size (#2006 follow-up) (#2118)
#2006 made a bevel size change dearer on purpose (+10-75% on the GPU canvas,
2x for CPU text bevels) and asked for a bench of it under perf/.

`bevel_bench` renders a 12 MP document with one Chisel Hard inner bevel on
an anti-aliased ellipse (a smooth outline) or on a text paragraph (many thin
edges). Each case warms the caches one pixel below the size, then times
refreshes with the size growing by one pixel each, so the GPU canvas
rebuilds its distance fields every time (it keeps them while the size
shrinks) and the CPU compositor misses its map cache. Sizes 8, 50 and 250,
GPU and CPU, with `--scene`, `--size`, `--reps`, `--no-cpu` and `--json`.

Registered in perf/budgets.toml as P52-P59 with no budgets (track only,
like P45 and P46): GPU for ellipse and text at 8, 50 and 250, CPU at 8.
docs/scorecard.md is regenerated; the rows read "not run" until the next
baseline.

RTX 4080 SUPER, release, 9 reps, p50 ms, before #2006 (3bc49f5e) -> after:
ellipse GPU 88 -> 105 (8), 169 -> 199 (50), 305 -> 460 (250); text GPU
119 -> 140, 141 -> 252, 238 -> 557; CPU text at 8 402 -> 797. Runs vary by
about 20% on this machine.

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-09 19:39:45 -04:00
Alex
590c1496b4
Affinity import: special shapes and sample corpus (#1606) (#2035)
* Import Affinity issue 1606 special shapes

* Affinity corpus: give the #1606 sample ceilings a margin for font differences between machines

The samples' labels render with whatever fonts are installed, so the measured
differences move by up to 0.8 between machines; each ceiling is now the higher
measurement plus about 1. Also use as_chunks in on_white (clippy with --features corpus).

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-09 19:27:35 -04:00
Anton
66b022cddd
Web: fonts served next to the app, loaded on demand (#1875)
A host lists fonts in fonts/manifest.txt beside index.html (craft-fonts' manifest format, paths
kept inside the site); the web shell lists those families in the font menus and fetches a family
when it is picked or a layout needs it, then re-renders the affected type layers without a history
step. No manifest changes nothing; package.sh copies one in with PHOTOCRAFT_WEB_FONTS_DIR. The
style menu also lists the weights of a variable font's wght axis.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 13:14:47 -04:00
Juan Manuel
2b139ebd5c
inspect: report each smart object's source (#1785)
document.inspect (and photocraft-cli info) now gives every smart object a
smartSource: embedded with its file name, or linked with a file path or,
for a PSD placed layer, the Idnt uuid its duplicates share. A script can
then tell duplicates of one placed file from independent contents, e.g.
to check that every location in a mockup template has its own art.

Co-authored-by: Juan Manuel Verges Solanas <jmverges@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 22:08:07 +07:00
Elvis33LE
adc1f76df4
Indexed Color: integer counts, palette stops and asynchronous previews (#1741)
Indexed Color accepted fractional colour counts and computed its live preview on the UI thread.
Counts are now whole numbers in 2-256 with a linear slider snapping at 8/16/32/64/128/256, and on
native builds the preview runs on one background worker that coalesces slider drags and rejects
stale results. Nearest-colour lookup uses an exact kd-tree above 64 entries (identical indices,
256 colours at 4.2 MP: 3018 -> 1061 ms). Web and inline sessions stay synchronous.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 10:33:13 -04:00
初心Yearth
c63d04a09c
docs: explain Windows source build prerequisites and app-control failures (#1692)
Document the MSVC toolchain and C++ build tools workload, and how to diagnose
Smart App Control blocking Cargo or build outputs (os error 4551), with a
pointer to the packaged release. Fixes #1585.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 08:04:05 -04:00
Nigel Ihza Firdausy
351736ef6b
docs(mcp): grant scoped file roots in headless registration examples (#1673)
The PHOTOCRAFT_AUTOMATION_*_ROOT variables are read only by the desktop app, so
headless photocraft-cli mcp/serve need the --automation-*-root flags. Say so,
and pass explicit roots in the .mcp.json and claude mcp add examples.
Fixes #1551.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 08:03:58 -04:00
callmeBalloch
6deb1e0934
AF.3: attribute the Affinity corpus, one sample count, run it on --changed
From the review on storytold/photocraft#1607:
- ATTRIBUTION.md gets a row for corpus/affinity: the four upstreams at
  their pinned commits, with authors and licences (CC0-1.0, and MIT by
  Nick Beeuwsaert, Jeremy Cantu and DAIKI).
- The README said 176 sample documents in the provenance and 189 in the
  limits. It now says 189 distinct public documents in both places; the
  reader opens all of them (298 files with duplicates, no failures).
- crates/affinity/ joins CRITICAL, so test-corpus --changed runs the
  corpus when the reader changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GQPh8UDgMyARR5Hat9UchC
2026-10-09 07:41:49 +00:00
callmeBalloch
a8d7c6ef3f
AF.3: put the Affinity fuzz targets and corpus back in CI
The maintainers approved the new crate, its fuzz targets and the corpus
upstreams in principle (storytold/photocraft#1607), so this undoes the
interim opt-in: ci.yml fuzzes preview and import_preview again, corpus
--all fetches the Affinity set, and its test runs under the normal
corpus feature. The provenance section stays.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GQPh8UDgMyARR5Hat9UchC
2026-10-09 06:38:40 +00:00
callmeBalloch
edb42922c1
AF.3: keep Affinity out of CI's corpus and fuzz jobs; state the clean-room provenance
Review on storytold/photocraft#1607 asked maintainers to decide on new
fuzz targets in ci.yml and four third-party corpus upstreams in
corpus --all. Until they do, neither touches CI: ci.yml is main's again
(the targets stay in crates/affinity/fuzz, run locally), and the
Affinity corpus is fetched only by corpus --affinity and tested with
--features corpus-affinity, so --all and test-corpus are unchanged.
The plan is to move those files into photocraft-corpus.

The crate README now spells out the provenance: no Affinity software,
afread's MIT description re-described without translating code, every
structure confirmed on public files (CRC-32 per entry, self-typed
fields), semantics fitted to Affinity's own thumbnails, and how the
sample documents were licensed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GQPh8UDgMyARR5Hat9UchC
2026-10-09 05:19:56 +00:00
callmeBalloch
57ae2fb1a3
Merge storytold/main into AF.3 native Affinity import
Conflicts: File › Open and batch extension lists (SVG, swatches and
Affinity together); the import dispatcher (upstream's type-layer
preparation with the new result fields); the pinned EXR and Affinity
corpora side by side; README, book and development docs.

Affinity type layers now draw through text_import::prepare, so they get
the same raster bound as other imported type, and SVG import fills the
two new ImportResult fields.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GQPh8UDgMyARR5Hat9UchC
2026-10-09 04:26:18 +00:00
BFlat
b693d690e1
docs: MCP tool names and job tools, codec README JPEG metadata and deep EXR (#1552, #1553, #1559) (#1573)
- architecture.md listed dotted MCP tool names (doc.open, command.run...) that the server
  rejects; it now uses the advertised underscore names, adds the missing tools, and points
  out that dotted names belong to the JSON control protocol.
- development.md's MCP tool inventory gains jobs_list and jobs_cancel.
- crates/codecs/README.md: oversized JPEG EXIF/XMP is dropped with a warning (XMP first sheds
  its layered-document properties), not an error; supported deep EXR opens flattened with a
  DeepFlattened warning, and only deep ZIP and mip/rip-map tiles are unsupported.


Claude-Session: https://claude.ai/code/session_01UHgFEHEsefoQoBParH47JA

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-09 10:23:19 +07:00
Filip Płacheciński
f0ae12dd22
Self-host the web app with Docker (nginx, community-maintained) (#1334)
A multi-stage Dockerfile builds photocraft-web with Trunk and serves dist/web from an
unprivileged nginx on :8080, with /healthz, precompressed gzip, the wasm MIME type and
immutable hashed assets. Rust and Trunk follow the release workflow, and the
PHOTOCRAFT_BUILD_SHA/DATE build args stamp About. Documented in packaging/web/README.md;
not built in CI.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 23:10:55 -04:00
BFlat
652b9726be
Pen tablets: Xwayland for a pen on Wayland, macOS monitor after NSApp (#639, #759) (#1070)
Wayland compositors give a pen only to clients that bind zwp_tablet_v2,
which winit 0.30 doesn't, so on Wayland the pen did nothing in the
window. With a pen attached (BTN_TOOL_PEN in /proc/bus/input/devices),
$DISPLAY set and the X11 libraries installed, the window now opens
through Xwayland, where the X11 reader gets pressure, tilt and the
eraser. PHOTOCRAFT_NATIVE_WAYLAND=1 keeps native Wayland.

macOS: the AppKit event monitor was installed before winit created the
shared NSApplication; it now goes in from eframe's app creation. The
pen's button-up (pressure 0) no longer zeroes the pressure of the
stroke's last moves, which reach the UI in the same frame.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 05:08:44 +07:00
Gert Pellin - GPE
cbed56b7f1
Desktop: write log records to standard error and a rotating log file (#1143)
* Desktop: write log records to standard error and a rotating log file

The desktop app installed no logger, so every log::warn!/log::error! in
the workspace was discarded: GPU startup fallbacks, a lost wgpu device,
the crash guard's panic report. A launch from a desktop menu has no
terminal either, so there was nothing to attach to a bug report.

apps/photocraft/src/logging.rs (no new dependency):
- standard error plus <settings dir>/logs/photocraft.log; each launch
  rotates the previous log to photocraft.1.log and .2.log, so the log of
  a run that crashed survives the next start; the file is capped at 16 MiB
- records logged before the settings directory is known are buffered
  (bounded) and written once the file is attached
- default levels: info for photocraft*, warn for everything else;
  RUST_LOG replaces them with env_logger-style directives
- never panics: an unusable directory or a failed write leaves standard
  error as the only sink

The logger is installed first in main (so the panic hook is recorded) and
the file is opened after argument parsing (--version and usage errors
leave no file). Documented in docs/development.md (Logs).

* Contributors: credit switch87 as Gert Pellin

* Desktop: report an unusable log folder through the logger

eprintln! panics when standard error is a broken pipe (a GUI launch on
Windows); log::warn! reaches standard error the same way once the file
sink has given up, and never panics. Same fix as storytold/vectorcraft#465.
2026-10-08 16:09:32 -04:00
Tanish Manku
964a41dc02
Corpus: pin the deep OpenEXR test images and check them in CI (#881)
The deep EXR decoder (#642) was verified against the OpenEXR project's deep test images, but those
lived in a hand-copied `corpus/exr/` and the test skipped when they were absent, so CI never read
them. `cargo xtask corpus --exr` (and `--all`) now fetches the five `*.deep.exr` files of
openexr v3.5.2 `src/test/bin/test_images`, sha256-verified against `xtask/exr-corpus.sha256`.

`real_deep_files_decode` is behind the `corpus` feature and fails when the corpus is missing. It
checks each file's sample total, largest per-pixel count, cumulative counts and channel lengths,
and that the decoded u32 ID channels hold exactly the IDs listed by the upstream `.txt` sidecars
(an independent oracle), plus the flattened decode. FILE-216-3 moves to partial (EXR read in CI;
TIFF and RAW still are not).
2026-10-08 16:08:56 -04:00
lionel
69c01c1bff
Startup: preserve the GPU backend after windowing errors (#1228)
* fix(startup): preserve GPU backend after non-graphics errors

* fix(startup): retain prior crash evidence on windowing errors
2026-10-08 15:32:54 -04:00