mirror of
https://github.com/storytold/photocraft.git
synced 2026-10-14 11:16:53 -04:00
main
10
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
5bef14dbdc |
Swatches: user swatch model with .aco/.ase import and export (FILE-215-14, part of #215) (#886)
- photocraft-psd: clean-room .aco (v1/v2, UTF-16 names, RGB/HSB/CMYK/Lab/Gray at 16 bits, other spaces kept verbatim) and .ase (groups, global/spot/process, RGB/CMYK/LAB/Gray floats, unknown models kept) readers and writers; byte-stable round trips, hostile input -> Err. - engine: presets::swatches (colours in their own model, display/foreground conversion through photocraft-cms working spaces, persisted with the prefs, default 40 colours in 4 groups) and swatch_cmds (swatches.list/add/delete/rename/move/newGroup/renameGroup/deleteGroup/use/reset/ import/export) with graceful-failure tests. - ui: swatches_ui.rs replaces the const grid (groups, tooltips, click = foreground and selected type, Alt/Ctrl-click = background, rename/delete/move, panel menu Import/Replace/Export/Reset), Color Picker "Add to Swatches", .aco/.ase via File > Open and drag-and-drop; strings translated in every complete catalog. - Scorecard FILE-215-14 -> done. Co-authored-by: leon <[email protected]> Co-authored-by: Claude Opus 5.5 <[email protected]> Co-authored-by: Brandon Thomas <[email protected]> |
||
|
|
b0680fd421 |
Advanced Blending: knockout, blend as group, transparency shapes, masks hide effects (FILE-215-16, part of #215) (#903)
* Advanced Blending: knockout, blend as group, transparency shapes, masks hide effects (part of #215) Blending Options > Advanced Blending now works end to end: - doc: AdvancedBlending on every layer (knockout none/shallow/deep, blend interior effects as group, blend clipped layers as group, transparency shapes layer, layer/vector mask hides effects), saved in .pcraft. - PSD and layered TIFF: knko, infx, clbl, tsly, lmgm, vmgm read into the model and written back (in place, or added when not at the defaults); the TIFF byte-order transcoder handles lmgm/vmgm as byte flags. - CPU compositor (compose/src/advanced.rs): shallow and deep knockouts (isolated groups and clipping groups stop a deep knockout), clipped layers blended individually, effects over the whole layer when Transparency Shapes is off, effects built from an unmasked copy when a mask hides them, interior effects under fill opacity. - GPU: documents using Advanced Blending fall back to the CPU compositor. - engine: layer.layerStyle.blendingOptions takes channels, knockout and the five switches; inspect reports them; Copy/Paste Layer Style carries them. - UI: the Blending Options page laid out like Photoshop's (General, Advanced Blending, Blend If with split sliders), translated. psd-tools corpus oracle 229 -> 236 (seven knockout files now pass; floor raised); other corpora and all round trips unchanged. Scorecard FILE-215-16 -> partial. Co-Authored-By: Claude Opus 5.5 <[email protected]> * Polish catalog rows for Advanced Blending; regenerate docs/scorecard.md pl.tsv landed on main and the coverage test needs every catalog to translate new UI strings. Co-Authored-By: Claude Opus 5.5 <[email protected]> * Undo damage: compare Layer.advanced in layer_pixels_equal; regenerate scorecard #810's exhaustive Layer destructure must classify the new advanced-blending field; it changes the composite, so it is compared. Co-Authored-By: Claude Opus 5.5 <[email protected]> * Regenerate docs/scorecard.md Co-Authored-By: Claude Opus 5.5 <[email protected]> --------- Co-authored-by: leon <[email protected]> Co-authored-by: Claude Opus 5.5 <[email protected]> Co-authored-by: Brandon Thomas <[email protected]> |
||
|
|
4cb7cf3931 |
Layered TIFF: the review changes of #672 (keep unknown blocks with their byte order, io behind the engine, fixture attribution) (#835)
* Review: keep unknown blocks with their byte order, io behind the engine, fixtures documented Addresses the review on #672: * Unknown layer blocks read from a little-endian TIFF are no longer dropped. Their bytes are kept verbatim with the signature `MIB8` (`46B8` for `8B64`) marking the little-endian layout (`TaggedBlock::is_foreign_order`), so a save back to TIFF restores them exactly. A malformed known block is kept the same way. Only a save to the other byte order has to drop such a block, with a warning: a PSD export strips them (`strip_foreign_order_blocks`), and a big-endian block of unknown layout still cannot go into a little-endian TIFF. * The UI no longer depends on `photocraft-io`: the layer predicate is exposed by the engine (`file_cmds::tiff_would_write_layers`); io is a dev-dependency again. * The layered TIFF path ends in the same `encode_image` as every flat export, so export-wide metadata policies (such as #733's XMP choice) apply to its composite too. * Scorecard FILE-215-7 is `partial`: extra alpha channels and a Quick Mask are not written. * The committed fixtures get an ATTRIBUTION.md row and a README describing their contents and how they were generated (psdtags + tifffile); the Python generator script is removed. Co-Authored-By: Claude Mythos 5.1 <[email protected]> * tiff_corpus: ask for layers explicitly (headless default is flat since #825) Co-Authored-By: Claude Mythos 5.1 <[email protected]> * i18n: drop the duplicate Korean "Ambient" entry that fails bundled_catalogs_are_consistent Co-Authored-By: Claude Mythos 5.1 <[email protected]> * i18n: drop the duplicate Korean "Warmth" entry as well Co-Authored-By: Claude Mythos 5.1 <[email protected]> * agent_tasks: tolerate Windows holding the test directory open during teardown The three tests that remove their directory right after cancelling the client failed on Windows (CI and locally) with os error 32: the spawned server task is still closing its files. The cleanup now retries briefly and otherwise leaves the directory for the next run's tmp() to clear, as it already did at the start. Co-Authored-By: Claude Mythos 5.1 <[email protected]> * preset store: a missing directory is an empty store on Windows too DirBackend::read passed the OS message through, and Windows words a missing directory as "cannot find the path specified (os error 3)", which missing_file() did not recognise, so attaching a store on a path that does not exist yet (first launch, and two engine unit tests) reported actions.json as skipped. The backend now reports every NotFound as "<name>: not found", the wording the predicate and the memory backend already use. Regression test: opening a store on a nested missing directory warns about nothing and writes nothing. Co-Authored-By: Claude Mythos 5.1 <[email protected]> * Review: keep scripts/layered_tiff_fixtures.py, drop the fixtures README and attribution rows The four .tif fixtures those described were never committed (binary test data stays out of the repo; the tiff_layers tests read them from the gitignored corpus/layered-tiff/, written by the script that tiff_layers.rs:181 still points to). Co-Authored-By: Claude Mythos 5.1 <[email protected]> * automation: the Direct Selection path commands edit vector paths, not files #865 added path.moveAnchors, path.moveHandle, path.bendSegment and path.convertPoint, whose registry docs mention the vector path they return, so registry_filesystem_path_params_are_classified fails on main (Linux and Windows): they join the list of commands whose "path" is a document path. Co-Authored-By: Claude Mythos 5.1 <[email protected]> * i18n(cs): the lower-case "{n} layer|{n} layers" plural (main red since #829) xtask's source_key_set_is_covered_by_complete_catalogs fails on main: the key reached every catalog but the Czech one. Same forms as the capitalised twin already in cs.tsv. Co-Authored-By: Claude Mythos 5.1 <[email protected]> * Drop the duplicate path.* entries from the workspace test (main already lists them) main already has path.moveAnchors/moveHandle/bendSegment/convertPoint in this match; the duplicates trip unreachable_patterns. Co-Authored-By: Claude Opus 5.5 <[email protected]> --------- Co-authored-by: leon <[email protected]> Co-authored-by: Claude Mythos 5.1 <[email protected]> Co-authored-by: Brandon Thomas <[email protected]> |
||
|
|
f41eb4f70d |
SVG opens as shape layers and places as a vector smart object (FILE-215-9, part of #215) (#1305)
* SVG opens as shape layers and places as a vector smart object (FILE-215-9, part of #215) crates/io/src/svg.rs: usvg parses (already in the tree through egui_extras); paths become ShapeLayer knots with the transform chain baked in, quadratics raised to cubics and every subpath after the first joining the component so the fill rule spans the path as SVG does; groups keep opacity and blend mode (transform-only and id-only wrapper groups vanish); text is shaped into outline shapes with the craft fonts plus the system's; gradients become the layer's gradient fill (stops and angle, with a warning). Clip paths, masks, filters, pattern paints and images are rasterised by resvg into pixel layers over their bounding box rather than dropped. Limits: 16384 px canvas, 2000 elements, 24 M px per raster, bounded recursion; malformed input is IoError::Svg. smart_cmds::render rasterises an SVG source at the placement's axis scale (1/16 steps, cached per scale) and divides the placement by it, affine and projective alike; place_bytes renders that cache up front. svg/svgz join the openable lists; book page; scorecard row done. Co-Authored-By: Claude Mythos 5.1 <[email protected]> * SVG import: never read local files from <image href> usvg's default image resolver reads any absolute or relative path in an <image href>, so opening an untrusted SVG could embed local files (/etc/..., ~/.ssh/...) into the document. Parse with a resolver that only accepts data: URLs (resources_dir None, string hrefs ignored). Embedded sub-SVGs already get no <image> resolution from usvg. The font database (and its system-font scan) is now only loaded when the drawing may contain text. Regression test: a PNG on disk referenced by absolute path, file:// URL and relative path is not loaded; a data: URL still is. Co-Authored-By: Claude Opus 5.5 <[email protected]> --------- Co-authored-by: leon <[email protected]> Co-authored-by: Claude Mythos 5.1 <[email protected]> Co-authored-by: Brandon Thomas <[email protected]> |
||
|
|
ec7302f4b3 |
TIFF: BigTIFF, every IFD and SubIFD, banded parallel decode (FILE-215-8, part of #215) (#859)
* TIFF: BigTIFF, every IFD and SubIFD, banded parallel decode (FILE-215-8) Large and multi-page TIFFs now open the way Photoshop opens them, quickly and without a second full-size buffer, and any directory of the file can be opened on request. * photocraft-codecs `tiff_ifd`: a bounds-checked directory walker for classic TIFF and BigTIFF (LONG8 / IFD8, both byte orders). It follows the whole IFD chain and the SubIFD trees (tag 330) with a seen-set, a depth limit and a 10 000-directory cap, and checks every value range against the file. `tiff_info` lists the pages (size, kind: page / reduced-resolution / mask, parent, storage); `default_page` is the first full-resolution page of the main chain, so a leading thumbnail is skipped. `decode_tiff_page`, `tiff_orientation`, `tiff_page_orientation`. * Banded decode: the `tiff` crate reads the chosen directory through a patched byte view (header -> that IFD, its next pointer -> 0, WhiteIsZero / palette -> BlackIsZero), so SubIFDs and cyclic chains decode. The image is allocated once (fallibly, zeroed in parallel) and each strip or row of tiles, across every plane, is decoded straight into it, in parallel on native targets, with one capped chunk-sized scratch buffer per worker. New: planar configuration 2 (was an error), palette images (as RGB), 64-bit integers, associated alpha made straight, non-alpha extra samples dropped. Image data cut off by the end of the file decodes with a `Truncated` warning. Before the crate sees a file: structural tag values must lie inside it, the chunk tables must match the geometry (checked in 64 bits; the crate overflowed and panicked on `(h / rows + 1) * planes`), and a decompression-bomb guard refuses files far too small for their declared size. * WhiteIsZero and BigTIFF Orientation keep upstream #746's behaviour (#691, #693) through the new decode path; regression tests cover both. * Encode: `EncodeOptions::tiff_bigtiff` writes BigTIFF; without it, BigTIFF is chosen when the file could pass 4 GiB (and a classic write that overflows 32-bit offsets is redone as BigTIFF). * photocraft-io: `import_tiff_page(name, bytes, page)`; the TIFF branch of import goes through it. * Tests: codecs `tiff_pages.rs` on hand-built files (`tests/common/tiffgen.rs`): BigTIFF LE/BE at 8/16/32f, padded tiles and planar strips/tiles, pages and SubIFDs, cycles, the IFD cap, truncated and past-EOF strips, huge dimensions / counts / tiles, WhiteIsZero, palette, wide formats, BigTIFF write, and a cut / bit-flip / random-mutation zoo (an ignored long run did 200 000 mutations without a panic); io `tiff_pages.rs`. The `decode_tiff` fuzz target walks every page. `tiff_bench.rs` (ignored) times a 30 MP decode against the previous path. * 30 MP RGB (release, 16 threads, same run): Deflate 344 -> 32 ms, LZW 428 -> 43 ms, 16-bit Deflate 497 -> 46 ms, uncompressed 15 -> 10 ms, 256 px tiles 36 -> 10 ms, planar error -> 12 ms. * Scorecard FILE-215-8 done; README and roadmap updated. Co-Authored-By: Claude Opus 5.5 <[email protected]> * rustfmt the TIFF reader and tests Co-Authored-By: Claude Opus 5.5 <[email protected]> --------- Co-authored-by: leon <[email protected]> Co-authored-by: Claude Opus 5.5 <[email protected]> Co-authored-by: Brandon Thomas <[email protected]> |
||
|
|
735bb09a98 |
Lossy WebP: a pure-Rust VP8 encoder with quality and lossless controls (#648) (#880)
* Lossy WebP: a pure-Rust VP8 encoder with quality and lossless controls (#648) crates/codecs gains codecs/vp8: a clean-room VP8 key-frame encoder from RFC 6386 (boolean coder, 16x16 and 4x4 intra prediction with per-macroblock mode decision, forward DCT/WHT paired with the bit-exact inverse transforms, dc/ac quantiser tables, token coding with per-frame probability updates, skipped macroblocks). webp.rs wraps the frame in the RIFF container (bare VP8, or VP8X with ALPH alpha and ICCP/EXIF/XMP). EncodeOptions: webp_lossless now picks the encoder instead of erroring, webp_quality (0-100) sets the quantiser; a lossy WebP reports a LossyCompression fidelity warning. Export As: the WebP entry gets Quality and a Lossless switch (new tl! string in all catalogs). CLI --quality, MCP/RPC doc_save quality and the engine's JPEG-style quality turn a WebP lossy; without a quality WebP stays lossless as before. Tests: tests/webp_lossy.rs decodes every file with image-webp (PSNR by quality, sizes fall with quality and are >= 4x below lossless on noisy photos, flat frames under 1.2 KB, alpha exact, metadata round trip, odd/tiny sizes, 16-bit input, the 16383 px limit as an error, text edges), plus unit tests for the coder, transforms, prediction and header. Co-Authored-By: Claude Mythos 5.1 <[email protected]> * i18n(it): Lossless (Export As WebP switch) in the Italian catalog that landed on main Co-Authored-By: Claude Mythos 5.1 <[email protected]> --------- Co-authored-by: leon <[email protected]> Co-authored-by: Claude Mythos 5.1 <[email protected]> Co-authored-by: Brandon Thomas <[email protected]> |
||
|
|
bd632e7eb3 |
Layered TIFF: open and save Photoshop layer data in TIFF files, in either byte order (part of #215) (#672)
* Layered TIFF: open and save Photoshop layer data in TIFF files, in either byte order (part of #215) Photoshop keeps a layered TIFF's layers in two private tags: 37724 (ImageSourceData, the same layer section a PSD has) and 34377 (the image resources). PhotoCraft now reads and writes both, so a TIFF opens with its groups, masks, layer styles, type, shapes, smart objects and adjustment layers exactly as the PSD path would, and saves them back. * photocraft-psd: a `tiff` module with `ImageSourceData` (parse/write, PSD- and PSB-sized), the 34377 resource helpers, and a field-by-field byte-order transcoder. In Intel-order TIFFs Photoshop byte-swaps the whole block structure (reversed keys, little-endian integers and doubles) while pixel samples stay big-endian; the transcoder knows the layer-info structure, masks, blending ranges and every block photocraft-io maps (descriptors, TySh, lrFX, the binary adjustments, paths, patterns, filter effects, metadata settings, linked layers, annotations...), recognizes descriptor-shaped unknown blocks, and drops the rest with a warning instead of passing byte-swapped data to other readers. The reader gained an optional little-endian mode. 23 unit tests; a fuzz target in the CI matrix. * photocraft-codecs: the two tags travel as opaque bytes on `Metadata`, read by a bounds-checked walk of IFD0 (classic and BigTIFF, both byte orders) and written back as BYTE / UNDEFINED tags. `tiff_writes_little_endian()` tells which order the encoder produces. * photocraft-io: `tiff_layers` rebuilds a PSD model from the TIFF's pixels plus the tags on import, and on export runs the PSD exporter (new `merged_matte` / `merged_compression` options), interleaves its merged planes for the TIFF image and stores the layer section in the encoder's byte order. `ExportOptions.tiff_layers` (default on). Lab and Multichannel documents save flat; extra alpha channels are not written (one transparency sample). * UI: the TIFF Options prompt ("Including layers will increase file size." / "Discard Layers and Save a Copy" / "Don't show again") behind Preferences > File Handling > Ask Before Saving Layered TIFF Files; `ExportSettings.tiff_layers`; catalogs updated. * Tests: io round trips at 8/16/32 bits in Gray/RGB/CMYK, equivalence with the PSD path, every TIFF compression, the flat fall-backs, malformed input, and files written by psdtags in both byte orders (fixtures from scripts/layered_tiff_fixtures.py); a corpus test (feature `corpus`) writes every corpus PSD as a layered TIFF and compares the result with the PSD round trip (700 files, 0 differences, 0 crashes). Codecs tests cover the tag walk on hand-made files. * Scorecard FILE-215-7 done, BUG-204-3 partial; README and book updated. Co-Authored-By: Claude Mythos 5.1 <[email protected]> * Layered TIFF: de and pt-br strings; neutral wording in the psd tiff docs The four psdtags fixtures stay out of the repository (no binary test data here): the generator now writes them to the gitignored corpus/layered-tiff/, and the tests that read them skip with a message when absent. Co-Authored-By: Claude Opus 5.5 <[email protected]> * Regenerate docs/scorecard.md Co-Authored-By: Claude Opus 5.5 <[email protected]> --------- Co-authored-by: leon <[email protected]> Co-authored-by: Claude Mythos 5.1 <[email protected]> Co-authored-by: Brandon Thomas <[email protected]> |
||
|
|
47f9306fd0 |
Open HEIC/HEIF photos (#355) (#372)
* Open HEIC/HEIF photos (#355) iPhones save HEIC by default and Photoshop opens it; PhotoCraft had no HEIF code, so .heic was missing from File > Open and failed to decode. Add Format::Heif to photocraft-codecs, decoded by heic-rs (pure Rust, forbid(unsafe), MIT OR Apache-2.0, wasm-ready): single pictures and grid-tiled photos, 8-bit to U8 and 10/12-bit to U16, auxiliary alpha, ICC, EXIF and XMP. The container's irot/imir/clap are applied and EXIF Orientation is rewritten to 1, so photos are never turned twice. Limits are checked against the declared size before decoding. Read-only (no pure-Rust HEVC encoder), listed in ASYMMETRIC_EXCEPTIONS. File > Open (desktop, web) and the batch list accept heic/heif/hif. Tests compare against Apple's own decode and a 16-bit source, cover orientation, metadata, limits and every truncation; fixtures from heic-rs and pillow-heif are attributed in ATTRIBUTION.md. Co-Authored-By: Claude Opus 5.5 <[email protected]> * HEIF: pin heic-rs exactly and add a decode_heif fuzz target Review follow-up: heic-rs is young and parses untrusted files, so pin it (=0.1.1) and fuzz the HEIF path. The target covers both orientation paths and is meant to run with -timeout so a hang is reported, not only a panic. It is added to the CI fuzz matrix. Co-Authored-By: Claude Opus 5.5 <[email protected]> * HEIF: a heic-rs panic is a malformed-file error, not a crash Fuzzing decode_heif found two out-of-range slices in heic-rs 0.1.1 on malformed files: - in its box parser (boxes.rs:130), reachable from a 72-byte file, even through the metadata-only probe; - during HEVC reconstruction (hevc/decode/recon.rs:70). The background job path catches panics, but the synchronous ones (jobs off, the CLI) did not. The HEIF codec now runs the decoder under catch_unwind and reports a panic as CodecError::Malformed, as gpu/health.rs already does around wgpu. The fuzz target also calls heic-rs directly, so its panics still reach the fuzzer. The 72-byte input is a regression test, which fails without the guard. Co-Authored-By: Claude Opus 5.5 <[email protected]> * HEIF: move the decoder into an optional photocraft-heif crate The heic-rs decoding from #372 now lives in `crates/heif` (photocraft-heif, standalone, depends only on heic-rs =0.1.1): probe, decode with a pixel limit, container transforms, alpha, 8/10-bit, ICC/EXIF/XMP and the catch_unwind guard, behind a small API of its own (Info, Options, Decoded, Error). photocraft-codecs uses it behind a new non-default `heif` feature; the apps and photocraft-io forward it. HEIF detection, extensions and mime stay compiled in, so without the feature a HEIC file is still recognised and opening it is a clear "HEIC/HEIF support isn't included in this build of PhotoCraft" error. `cargo xtask layers` allows exactly codecs -> heif between standalone crates. The fixtures are no longer committed: `cargo xtask corpus --heif` fetches them from heic-rs (tests/fixtures, v0.1.1) and pillow-heif (tests/images) at pinned commits, verified against xtask/heif-corpus.sha256 (same bytes as the PR's copies). The fixture tests run with `--features corpus,heif` (`cargo xtask test-corpus` enables both). Co-Authored-By: Claude Opus 5.5 <[email protected]> * Build CI and official binaries with HEIC support (--features heif) CI test and clippy, the FreeBSD job, and the macOS, Linux (and Flatpak), FreeBSD, Windows and web packaging all build with the `heif` feature; CI also tests codecs/io without it on Linux so the "not in this build" path stays covered. `cargo xtask wasm` also checks photocraft-codecs with `--features heif`. Co-Authored-By: Claude Opus 5.5 <[email protected]> * Docs: HEIC open is an optional build feature, on in official builds Co-Authored-By: Claude Opus 5.5 <[email protected]> --------- Co-authored-by: Claude Opus 5.5 <[email protected]> Co-authored-by: Brandon Thomas <[email protected]> |
||
|
|
a8afec4455 |
Open documents in bounded reads, with a clear out-of-memory error (#375) (#401)
Every open path read the whole file with std::fs::read, which asks Windows for up to 4 GiB in a single ReadFile. Some file systems (exFAT, network redirectors) reject a read that large with ERROR_INVALID_PARAMETER, "The parameter is incorrect" (os error 87), as reported for a 4.75 GB PSB in #375. Add photocraft_format::read_file and read_all beside atomic_write: 64 MiB reads into a buffer reserved up front, and an OutOfMemory error that says how much the file needed. Route every document read through them: - the background open job (engine jobs.rs read_path); - the engine's read_file; - Open Recent and OS open events; - desktop drag and drop, which now goes through open_path (and so through the background job when jobs are on); - the File > Open dialog; - .pcraft loading; - the automation workspace read and automation::files::open; - smart object Replace Contents and linked files. The dialog no longer fails silently: PickOpenFn returns the picked name with the read result, and a failure is reported through open_failed and the notices like any other open failure. Speed is unchanged (1.11 s vs 1.13 s for a warm 4.75 GB file). Co-authored-by: Claude Opus 5.5 <[email protected]> |
||
|
|
d2576b27eb |
Profile mismatch: compare profiles by colour, not bytes (#394)
Opening any file Photoshop tagged with sRGB IEC61966-2.1 asked "Embedded Profile Mismatch: sRGB IEC61966-2.1 vs sRGB IEC61966-2.1": open_policy compared a hash of the ICC bytes, and Photoshop's v2 profile (1024-entry tables) and the built-in v4 sRGB (parametric curves) are the same colours in different bytes. 32-bit documents in Photoshop's linear sRGB missed the stay-linear exception for the same reason. Add Profile::same_colors: same colour space and every sample of a device grid within dE76 0.5 through both (re-encoding costs 0.025; moving the red primary 2% costs 1.0). Colours are compared rather than round-tripped because CMYK -> PCS -> CMYK is not an identity even for one profile. Of the 75 profile-tagged PSDs in the test corpora, all 75 prompted on open; now 6 do (Adobe RGB, Gray Gamma 2.2, SWOP), each a real mismatch. Co-authored-by: Claude Opus 5.5 <[email protected]> |