The portable data folder's writability check wrote a fixed, PID-named file with
std::fs::write, which follows an existing symlink and truncates its target.
Create the probe with create_new, try another name on a collision, and remove
only a probe this call created.
Resolve conflicts with #5331 (EXR streaming): the block-by-block EXR import now
reads from the same identity-verified handle as the rest of the open path, and
docs/scratch-disks.md keeps both the EXR streaming pointer and the RAM/scratch
admission notes.
Resolve against main's save restructure (Services::save_file/SaveCtl, scratch-integrity checks,
ZipWriter::with_entries, exact encoded_len):
- PSD/PSB saves stream the file model through atomic_write_stream_progress; the length used for
progress and reservations is main's exact encoded_len (the PR's size_bound under-counted lazy
data); export_with/encode_with share checked_stages, so main's scratch checks (previews
included) still refuse unreadable scratch.
- Drop ZipWriter::for_entries in favour of main's with_entries.
- TIFF: take_from_psd moves layer data out of the export's own model (never the document);
body() reserves its exact length, failures are OutOfMemory (not LimitExceeded, which would
trigger the PSB retry).
- Streaming writer: AlreadyExists from the writer is a failed save, not a name collision.
- Remove dead Vec put_widened_rle; channel_data_len is test-only.
Keep the PSD memory admission work and upstream's scratch storage
together. Conflicts resolved:
- Cargo.lock: both sysinfo versions are required. 0.40.1 serves
photocraft-io's adaptive import budget; 0.37.2 serves
photocraft-raster's scratch volume and memory supervision. Cargo
metadata resolved the lock with no other changes.
- crates/engine/src/lib.rs: Session keeps pending_preflights and
next_pending_preflight beside scratch_runtime and scratch_epoch.
Session derives Default, so neither set needs a manual initializer.
Upstream: 655295b2a2
Main now takes the manifest envelope from craft-pcraft-format, which has
no full_composite member. The save writes the member after the envelope
when it writes composite/full.png, and read_full_composite reads it back.
With our fix-up 8a0f959d29 on top of the author's head 072a87e7b0.
Conflicts with the batch resolved: Image Size keeps #10702's point sizes and
writes `scaleStyles` only when true (no key and false play the same in the
reference application, so recorded steps save back unchanged); Convert to
Profile takes #10704's options; new tests carry #10743's `encoding` field;
#10702's absolute-intent test expects the new Convert to Profile flags.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* perf: reuse large buffers so a pinned glibc trim threshold costs no speed (#10479)
With MALLOC_TRIM_THRESHOLD_ set (#8743), glibc maps every allocation over
128 KiB fresh, so tiles, compositor bands and filter scratch were page-faulted
in on every call: viewport compose 3.6x slower, Gaussian Blur up to 1.8x,
.pcraft save 2.4x.
- photocraft_raster::scratch: a process-wide, capped pool of large Vec
buffers (u8/u16/f32/[f32; 4]). A miss drops the oldest buffers worth its
size down to a 256 MiB floor, so peak memory stays near glibc's defaults.
The engine releases the pool after any command that closes a document,
Headless::close does too, and an idle thread releases it after 2 s unused.
- Tiles, compose Buffer/masks/bands, algo Image and the blur paths, engine
adjustment/filter scratch, GPU tile upload and io import buffers go through
the pool.
- .pcraft save reuses one zstd FrameCompressor per worker (byte-identical).
- interactive_bench reports minor page faults per run.
36 MP, trim threshold set vs glibc defaults: viewport 5.6 vs 6.1 ms (was
22.7), Blur r4 445 vs 437 ms (was 852), Curves CPU proxy 35.0 vs 35.7 ms
(was 50), flatten 146 vs 147 ms (was 237), save .pcraft 251 vs 230 ms (was
545). RSS after closing a 5400x7200 document: 21 MB with trim set.
* fix(linux): restart once with glibc's trim threshold set so closed documents return memory (#8743)
With glibc's default malloc, the memory of a closed document stays with the
process: freeing a large buffer raises glibc's dynamic mmap threshold, so
later tile buffers land in heap pages glibc keeps. A session that opened and
closed a 5400x7200 document held ~450 MiB afterwards.
photocraft_engine::malloc_tuning::apply(), called first in both binaries'
main, re-executes the binary once with MALLOC_TRIM_THRESHOLD_=131072 on Linux
with glibc: same PID, arguments, argv[0] and environment, in safe Rust
(CommandExt::exec). A malloc variable or glibc.malloc.* tunable the user or a
package already set is left alone, so setting MALLOC_TRIM_THRESHOLD_ opts
out. No-op on macOS, Windows, musl and wasm.
Depends on the scratch-buffer pool (#10479), without which the pinned
threshold slows filters, previews, flatten and .pcraft save by 26-209%.
* fix: corpus tests build again; return remaining pooled buffers (#10479 review)
- Corpus-gated io tests and the Photoshop-oracle test moved `.px` out of a
`Buffer`, which now implements `Drop` (E0509 with `--features corpus`):
use `into_px()`.
- io: masks, the merged image, extra channels, smart-filter masks,
smart-object pixels and patterns give their `interleave` buffers back to
the scratch pool after writing them.
- compose: recycle the mask values of a pass-through group, `effect_shape`
and both `split_parts` callers.
- Styled Free Transform preview copies each band out instead of moving it,
so the band returns to the pool.
- interactive_bench: the fault count covers each whole run, untimed setup
included; say so.
* fix: corpus tests build again; return remaining pooled buffers (#10479 review)
- Corpus-gated io tests and the Photoshop-oracle test moved `.px` out of a
`Buffer`, which now implements `Drop` (E0509 with `--features corpus`):
use `into_px()`.
- io: masks, the merged image, extra channels, smart-filter masks,
smart-object pixels and patterns give their `interleave` buffers back to
the scratch pool after writing them.
- compose: recycle the mask values of a pass-through group, `effect_shape`
and both `split_parts` callers.
- Styled Free Transform preview copies each band out instead of moving it,
so the band returns to the pool.
- interactive_bench: the fault count covers each whole run, untimed setup
included; say so.
* fix: recycle the masks of adjustment-atop and shape layers (#10479 review)
* fix: recycle the masks of adjustment-atop and shape layers (#10479 review)
* fix: recycle effect hiding masks (#10479 review)
* fix: recycle effect hiding masks (#10479 review)
* fix: recycle knockout, CMYK and GPU shape masks; release the pool when Split/Merge Channels close a document (#10479 review)
* fix: recycle knockout, CMYK and GPU shape masks; release the pool when Split/Merge Channels close a document (#10479 review)
* fix: release the pool when a closed document's cancelled workers exit; idle-release switch for tests; don't pool PSD planes (#10479 review)
* fix: release the pool when a closed document's cancelled workers exit; idle-release switch for tests; don't pool PSD planes (#10479 review)
* fix: release the pool for workers cancelled before their document closes; take the pool lock for budget and idle-release changes (#10479 review)
* fix: release the pool for workers cancelled before their document closes; take the pool lock for budget and idle-release changes (#10479 review)
* fix: idle release rechecks activity under the lock; try_take_exact releases the pool and retries before failing (#10479 review)
* fix: idle release rechecks activity under the lock; try_take_exact releases the pool and retries before failing (#10479 review)
* test: cover a successful try_take_exact miss; name the failed-reservation test for what it checks (#10479 review)
* test: cover a successful try_take_exact miss; name the failed-reservation test for what it checks (#10479 review)
* fix: pool the final GPU effect shape buffer and return it after use (#10479 review)
* fix: pool the final GPU effect shape buffer and return it after use (#10479 review)
* docs: describe scratch pool lifecycle regression invariants
* test: verify polling releases cancelled document scratch buffers
* perf: recycle remaining transform selection and PSD scratch buffers
* fix: free PSD gather bands before allocating channel planes
* test: assert exact scratch take reuses pooled storage
The three version 12 fixtures were tracked inside crates/psd/fuzz/corpus,
which crates/psd/fuzz/.gitignore ignores and cargo-fuzz rewrites (cmin) or
deletes; the psd and engine test binaries include_bytes! them, so cleaning
the corpus broke the build. They move to crates/psd/tests/fixtures/atn with
a README (sizes, purpose, sha256; the seeds note folded in) and an
ATTRIBUTION.md row; the fuzzer takes the folder as an extra corpus dir.
photocraft-cli recognised an extensionless Actions file only by version
word 16, so a version 12 set without .atn (classic Mac OS Photoshop 5)
went down the JSON path and failed with a UTF-8 error. It now accepts 12
and 16, the versions photocraft-psd reads. Input: v12-commands.atn copied
to "Legacy Actions" and passed to load_actions_file.
Co-authored-by: yoriswind <164435053+yoriswind@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Read bounded original SVG through the platform clipboard service for native
Paste options and action playback. Preserve a newer internal copy when an
older OS source remains unchanged, and report failed or panicking reads
without losing the existing copy. Retain ordinary raster clipboard routes.
Add source replacement, mode/output, Undo, action playback, stale-source,
size-bound and panic/retry regressions. Completes the app intake for the
source-backed backend adapted from Skyfuller1303's PR #3409.
Validation: 104 unique UI/Actions/native-service passes, one existing manual
clipboard ignore; actual app-model Pixels/Shape renders and saved Path;
2921 engine package passes (22 ignores), adversarial pass, all-target strict
engine/UI/app Clippy, full format, 31-crate layers and standard wasm/HEIF.
The doc.save row, docs/development.md, the MCP doc_save description, the
file.saveACopy params text, the --pcraft-full-composite help and the
option's doc comments said the option writes composite/full.png. A save
leaves it out when the canvas is past 2^26 pixels, when its pixels cannot
be allocated or encoded, or when a ZIP bundle would pass its limits. They
now say so and point to the manifest's full_composite member, which names
the entry when it was written.
Conflicts resolved as unions of both sides: codecs re-exports (PngRows + TiffBands), smart_cmds source imports
(main's source_surface_from_buffer and cache API with #8571's svg_source_image_in/convert_svg_source; main's
duplicate rendering import dropped), io flat.rs test modules (#8536's banded TIFF tests and main's PNG ones),
menus/invoke.rs (#8571's smart SVG intercept and main's layer.selectUnderCursor), state.rs (#8311's
type_box_overflow and main's type_insertion_style), the canvas_geom test keeps the is_finite guard, docs
status lines keep both change lists, i18n catalogs keep both sides' rows (hi.tsv: main's wording where both
added the same string).
UPDATE_APP (native builds; craft-update is a native-only dependency) carries our 128 px icon and accent (#2f7bf5, its colour in ArtCraft Launcher). The updater, the self-update
helper and craft-update's startup prompt all get them through crate::UPDATE_APP; the
prompt draws the icon, the accent and "by ArtCraft" (craft-update's default developer).
craft-update is pinned to storytold/craft-libs main at 1065653e7 (#434).
Clippy fixes for Rust 1.99 in untouched code (same logic):
- apps/photocraft/src/app_dirs.rs
- crates/codecs/src/codecs/via_image.rs
- crates/engine/src/canvas_geom/tests.rs
- crates/ui-egui/src/panels/blend_preview.rs
Main's noop_step now takes the step's params (an Arrange that cannot move the layer is a no-op too); it is public for the CLI's run and batch, which pass them.
- Smart-object contents (Replace Contents, Convert to Layers, Stack Mode,
internal Edit Contents) refused any SVG whose canvas area times its
node count passed 24 MP, e.g. a 1920x1080 layout with 12 shapes.
decode_source now uses the bounded image parse with File > Open's
layered import (import_svg_bounded_images). Convert to Layers' shape
cache cap is 1 GiB in the document's format, the import's own budget,
not 24 MP (four full-canvas shapes in a 4K document failed).
- The bounded resolver failed the whole drawing on an embedded image it
can't read (an unknown MIME type, a corrupt PNG); it now leaves it out
as usvg does, so such SVGs place and render again. Byte and decode
overruns stay errors.
- resvg gives each embedded SVG image a scratch pixmap the size of its
target and frees it after, so the scratch budget counts the deepest
chain of nested SVG images, not every sibling: 12 SVG icons on a 2 MP
canvas, or one at a 12 MP render, no longer fail.
- Main's shared nesting budget (#9397) now covers the bounded path too:
511 + 511 levels across a drawing and its embedded SVG overflowed a
1 MB stack.
- An AI working copy is regenerated through VectorCraft only when
VectorCraft is the editor; Illustrator or another program gets the
original bytes. VectorCraft is recognised by bundle id, a known
install, or its name (vectorcraft[.exe], vectorcraft-<version> AppImage,
ai.storyteller.vectorcraft).
- updateSvg refuses an AI/PDF/EPS source (updateVectorSource saves it
back in its own format); the doc no longer calls updateSvg "existing".
- Edit Contents on contents already open in the internal editor switches
to that document, as main does, instead of an error.
- Stack Mode on an SVG refuses up front when one float frame per layer
would pass 1 GiB (the layered import no longer refuses for it).
- A stored display preview still fails on an embedded image it can't
read (strict budget, its own import_svg_preview_with_work; external-
editor saves and conversions leave the image out as placement does); skipped embedded SVGs still use up the XML byte
budget; an image under an unknown MIME type or with no pixels is left
out uncharged.
- Merge: the PSD import reads a preview's native source from the
document's global blocks; main's owned open moves them out of the
file, so every stored preview was dropped.
- Main's strict Clippy rules on the PR's app code (explicit import, a
Unix-only test helper, one long opt-in test).
Co-authored-by: Mocky Digital <282319807+mockydevs@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Conflicts: svg.rs (main's bufread inflate and shared render helper, kept
with the PR's bounded raster path), psd_import.rs (the PR's vector
preview moved into main's smart_content), menus/invoke.rs (the SVG
intercept moved into main's split invoke_unguarded), rendering.rs and
the ui-egui import lists (main's explicit imports), Cargo.lock, docs
status lines (unions).
Port behavior and regression coverage into current modules; preserve current upstream behavior.
Integrate lossless export-only JXL and independent decoder regression matrix, preserving current JPEG/TIFF save extensions and 12-bit/arithmetic JPEG decoder dependency.
Port behavior and regression coverage into current modules; preserve current upstream behavior.
Prebound the listener inside current authorized control_setup before Linux display and GPU initialization, retaining integration preferences, workspace grants, authentication and single-instance routing. Preserved actual port logging, added occupied-port/prebound-dispatch regressions and bounded child cleanup to current process test harness.
Port behavior and regression coverage into current modules; preserve current upstream behavior.
Register starter actions and bounded background-blur command in current modules; preserve persistent migration marker and portable JSON defaults compatibility.
serve_fonts asks for the interface language's served script font once per
language, but the font manifest loads asynchronously: when the first frame
ran before it arrived, no fallback was registered yet, the request found
nothing, and an Arabic interface on an English browser stayed without its
font until the language changed. The manifest load now asks for the
interface language's font along with the browser languages'.
Review finding on #10006.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The web build has no system fonts and embeds no craft fonts, so an Arabic
(or Japanese, Chinese, Korean, Hindi) interface had no font for its labels:
served fonts were registered only in the document text engine. A served
script fallback now also joins the end of every egui font family, and the
font stack theme::install_fonts rebuilds on a language change includes it
again. The web shell asks for the interface language's script font, not
only the browser languages'.
Review finding on #10006.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Main now carries #9031 (Halftone) and #8923; this branch keeps only what it adds on top: the guard for a setting beside a Gallery stack's effect list, its test, and the PSD open warning for patterns that paint nothing.
Reapply Linux Adwaita no-title winit feature and its lockfile,
keep integrated title bar as default, and port compatible tests/docs.
Preserve upstream's preferences UI refactor rather than restoring
its removed prefs_ui.rs file.