mirror of
https://github.com/storytold/photocraft.git
synced 2026-11-03 06:40:20 -06:00
482fd899cfd6c6a55fbd68fbb80cee6c2bc8a9cc
15
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
1a4f867958 |
TIFF: read and write CIE L*a*b* (CIELab, ICCLab); Lab documents stay Lab (#4949)
* TIFF: read and write CIE L*a*b* (CIELab, ICCLab); Lab documents stay Lab Photoshop saves Lab documents as CIELab TIFFs (PhotometricInterpretation 8, a* and b* signed), which PhotoCraft could not open, and PhotoCraft saved Lab documents as sRGB TIFFs. - codecs: a Lab channel layout (Lab, LabA) holding a* and b* as PhotoCraft's Lab documents do ((a + 128) / 255; 16 bits like the PSD importer), with its own Lab <-> sRGB conversion (same formulas as photocraft-color; codecs stays standalone) for formats without Lab, which now warn LabConverted. - TIFF read: CIELab and ICCLab (8 and 16 bits, alpha) are handed to the tiff crate as RGB and labelled Lab; a* and b* are converted from the file's encoding (signed a / 256a, or offset). - TIFF write: Lab and LabA as CIELab, as Photoshop writes it; float Lab is written with 16 bits (DepthReduced). - io: a Lab TIFF opens as a Lab document; a Lab document with one plain layer is written to TIFF in Lab (other formats still use sRGB). Photoshop 2025's Lab TIFFs open with the colours Photoshop shows (its own RGB conversion matches within one level at 8 bits and exactly at 16 bits), and Photoshop reads PhotoCraft's Lab TIFFs back with the same L*a*b* values. Fixes #4936 * Raw: measure the Nikon D90 image area (4288x2848 at (10, 10)) Aligning the decoded D90 sensor plane with the camera's own JPEG puts the image at (10, 10) in the 4352x2868 sensor plane, giving the camera's 4288x2848 frame (10-pixel margins; the right border reads the camera's flat 255 from column 4310). Add the measured ImageArea so a D90 NEF opens on the camera's frame instead of the uncropped plane, and make a measured profile leave a file that carries its own colour calibration (a DNG) untouched. * Codecs: open Cineon (.cin) film frames read-only Cineon is the 10-bit logarithmic film-scan format DPX grew from. Read it from the same Kodak header family: the generic header (big-endian, orientation at 192, channel count at 193, the channel-1 bits/dimensions), one or three channels, three 10-bit samples packed into each 32-bit word on a word boundary. The 10-bit code values open as U16 (round(c x 65535 / 1023)); the file's log black/white points are a display choice and are not applied. A depth other than 10 bits or a channel count other than 1/3 is Unsupported; truncation errors rather than panicking. Read-only (no Cineon writer), so .cin joins the open filters and Cineon read closes part of #1049's G10 gap. * PSD: write tiff blocks without copies, inflate merged ZIP once - ImageSourceData::to_bytes writes the global mask and global blocks straight from the model with 4-byte alignment instead of cloning each block (data included) to set its padding. Same bytes. - The tiff transcoder's nested regions and blocks write into the parent's buffer (taken and handed back, error or not) instead of building their own output and copying it up per nesting level. Bodies that fail or are not transcoded are truncated before being copied as stored or dropped. Same bytes. - PsdFile::from_bytes_and_merged decodes the merged image with the parse when asked: a ZIP stream is inflated once (the decode's stream check is the parse's); the parse-only check runs only when the decode fails, to keep from_bytes' errors. PSD import uses it with merged_needed. - PsdBuilder::composite documents that Gray/CMYK composites stay straight. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Content-Aware Fill: measure a thin hole by its thickness, not its bounds (#1366) Removing long, thin objects (coiled guitar strings, wires) left their outline as darker and lighter blotches and copied pieces of other strings into the fill. The pyramid shrank until the hole's bounding box was four patches across: for a string across the image that is a level where the hole has swallowed its surroundings, so the matches found there copy whatever looks like the blur, and the finer levels (refine-only for wide bounds) keep them. A hole at most a quarter as thick as its bounds are long now counts as thin: the pyramid stops while it is still about two patches thick, and that level starts from a membrane across it (over a few pixels, the surroundings themselves) instead of the onion peel, whose exhaustive search is what made a shallow pyramid unaffordable. Compact holes are unchanged bit for bit. Refs #1366 * perf(compose,gpu): effect caches reserve tile addresses instead of pinning layers Effect map cache entries kept a clone of their layer (a deep clone for groups) so the tile addresses the cache key hashes couldn't be reused. That kept an edited or deleted layer's pixels alive, uncounted, until the entry was evicted. Keep a Weak to each hashed tile instead: it reserves the address without keeping the pixels. The GPU compositor's styled-group pins and shape-source tile snapshots do the same. Part of #4530. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(history): count snapshot-only tiles against the history budget History panel snapshots outlived every budget: a Merged Layers snapshot is a full flattened copy that neither the history budget, the GPU budget's document bytes nor Purge accounted for. Trim now counts the tiles only snapshots hold as fixed cost, the GPU budget includes them, and Purge > Histories no longer reports tiles a kept snapshot still holds as freed. Part of #4530. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Content-Aware Fill: every part of a thin selection keeps its surroundings (#1366) Two more causes of ghosts when removing strings and the small knobs selected with them: * Auto sampling summarises the selection's surroundings by a few clusters. Around a long selection those come from the strings' surroundings, so a small part elsewhere (a tuning knob in a differently coloured corner) lost its own surroundings and was filled from far away in another colour. Around a thin selection the Auto area now always reaches as far as the selection is thick. * The membrane that starts a thin hole now starts a patch radius out, past the anti-aliased fringe or halo that a selection drawn close to the object leaves outside it. Refs #1366 * Content-Aware Fill: peel a thin hole where that stays affordable (#1366) Starting every thin hole from a membrane smoothed Spot Healing on film grain (spot_healing_keeps_film_grain_through_overlapping_strokes: grain 9.0 against 12.1 around). The onion peel copies the grain; it was only given up because its exhaustive search is too slow on the big level a long thin hole stops at. It now starts the thin hole whenever hole cells times sources stay within 2^28 comparisons, and the membrane only beyond. Refs #1366 * Select Subject: keep subject parts coloured like the backdrop, leave enclosed gaps open Users on #2788 reported Select Subject dropping clothes that are close in colour to a plain wall and bridging the gap between an arm and the torso. Both come from the same place: the saliency pass labels low-contrast parts of the subject as background, the background colour model then learns them and explains them away, and the final blind hole fill closes every enclosed gap up to 5 % of the mask. Two changes in crates/algo: - A second GrabCut pass whose background model only knows the backdrop. A colour model fitted to the image frame (minus a margin around the salient blob, in case the subject runs out of the picture) marks every pixel it explains as probable background; the blob and whatever the backdrop cannot account for start as probable foreground. Only components that touch the first pass's result survive. - Hole filling that consults the colour models: specks (<= 0.2 % of the mask) always fill, larger holes up to 5 % fill only when their pixels fit the foreground model better than the background's. Tests: two regressions (a cream-shouldered figure on a near-white wall keeps its shoulders; a ring keeps its backdrop-coloured hole open), plus ignored timing and picture-dump helpers. The 6 MP first run goes from about 100 ms to about 200 ms on one core; the P29 repeat path is untouched. * Keyboard shortcut sets: Set menu, New Set, Save, Delete, .kys Export and Summarize (K2) Edit › Keyboard Shortcuts gets Photoshop's set management. A set is a saved copy of the bindings (Preferences.shortcutSets, the one in use in shortcutSet; "" is PhotoCraft Defaults), and the Set menu shows "(modified)" when the bindings differ from it. New Set…, Save and Delete wait for OK like the rest of the dialog. Export… writes the dialog's bindings as a .kys file (commands then tools, Photoshop's macOS key spelling), each row carrying our id as pc-id so PhotoCraft imports its own sets back exactly, whatever the label; Summarize… writes Photoshop's HTML list of every bound command by menu. edit.keyboardShortcuts takes useSet (before the call's edits), saveSet, deleteSet, sets and activeSet (after them), exportKys and summarize; every reply reports the sets, the set in use and whether it is modified. Bad names, keys or counts are errors that change nothing. * .kys import: tool keys and sets saved on Windows (K3) Photoshop's <tool> rows now import: each matches our tool by name (our tool names are Photoshop's) or by pc-id in sets PhotoCraft exported, and a letter other than the tool's own becomes its override, while its own letter keeps the tool cycling with the others (#5036). A set saved on Windows spells the primary modifier Ctrl where a Mac set writes Cmd and keeps Control for the Control key. A set with no Cmd or Opt anywhere but some Ctrl is read as a Windows set, its Ctrl as our Cmd: Ctrl+L is the Cmd+L default, not a Control-key override that would also mean Control on a Mac. The import reports `windows`. * cargo fmt the extension lists Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Vikram Bhaskaran <vikram.bhaskaran@outlook.com> Co-authored-by: vangel76 <vangel1976@googlemail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Finn Newick <finnnk@gmail.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> Co-authored-by: Diego Chagas <drochachagas@gmail.com> Co-authored-by: Tanish Manku <tmanku.dev@gmail.com> |
||
|
|
1bcb662828 |
Deep EXR: open cropped renders on their full display-window stage (#4907)
A renderer's data window is its crop of the frame; the composition lives on the whole display window (our MY27 renders: data 1782x1267 at (1109,1100) inside a 4000x3000 stage). decode_deep now validates the display window against the limits and reports it as stage + origin on DeepImage; flatten pads the composed region to the stage at its offset (transparent elsewhere) and tags the image with a DisplayWindowStage warning. Files without a usable display window open as before. Includes a cropped-deep generator and a stage/offset oracle test. |
||
|
|
f48a72cbe3 |
Codecs: open and save DPX frames (#1049) (#4104)
Read SMPTE 268M DPX in both byte orders (SDPX, XPDS): the first image element, descriptor 6 (luma), 50 (RGB) or 51 (RGBA), at 8, 10, 12 or 16 bits, with 10-bit packing 0, 1 (method A) or 2 (method B) and 12-bit packing 0 or 1. 10- and 12-bit code values open as U16 round(c * 65535 / (2^b - 1)) and come back exactly when written at their depth. No colour transform: the transfer characteristic and colorimetric specification are reported as import notes; extra image elements are reported; the orientation is applied on import. Write big-endian, one unencoded element, at 8, 10, 12 or 16 bits (EncodeOptions::dpx_bits; `dpxBits` on the save commands, MCP and RPC; `--dpx-bits` in the CLI), following the document depth by default. Gray with alpha is written as luma. Creator, project, copyright, creation time, transfer, colorimetric, frame position, frame rate and time code travel as DPX:* text metadata and are written back. Unsupported descriptors, signed or run-length-encoded data and other bit depths return CodecError::Unsupported; malformed and truncated files return errors. Co-authored-by: Brandon Thomas <echelon@gmail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
6751a46003 |
Codecs: integer fast paths for Image::convert (#3717) (#3729)
U8/U16 depth changes and pure channel-copy layout changes (alpha add/drop, gray to RGB) skip the two intermediate f32 buffers. Output is bit-identical to the f32 route. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
291f30da55 |
EXR: Cryptomatte layers list, decode and hash to the specification (#2180)
* EXR: Cryptomatte layers list, decode and hash to the specification Cryptomatte (specification 1.2) carries per-pixel object and material IDs with coverage in <layer>NN.red/.green channel pairs, announced by cryptomatte/<key>/name|hash|conversion|manifest header attributes - the ID channels Arnold, V-Ray, Redshift, Mantra/Karma and Cycles write next to the beauty pass. cryptomatte_layers lists every layer from the headers alone (name, key, channels, the embedded manifest parsed and capped at 16 MiB, a sidecar pointer); decode_cryptomatte returns the per-pixel (ID, coverage) pairs above zero, sorted by descending coverage; cryptomatte_id hashes names the reference way (MurmurHash3 x86-32, the exponent fixed away from denormals/inf/NaN, read as f32 bits) - verified against the official test vectors - and cryptomatte_key/cryptomatte_preview_color expose the metadata key and preview colour. Files with deep parts are refused, like the other flat part decoders. * chore(ui): format prefs_ui (the workspace fmt gate is red on main until #2417 lands) * codecs: Cryptomatte decode checks the layer size without truncating casts Count each pixel's pair list in the allocation check too. 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> |
||
|
|
f9ea13a857 |
EXR: multi-part files open the colour part; exr_info lists them all (#2116)
* EXR: multi-part files open the colour part; exr_info lists them all A Maya/Arnold render writes one part per AOV. The decoder took part 0 silently, so a depth-only part failed the whole file with "no R/G/B or Y channels". Now the part that looks most like a colour image opens (RGBA > RGB > YA > a single channel, earliest wins), with a DecodeWarning::MoreParts naming how many parts exist. exr_info lists every part (name, view, size, channels, deep/tiled) from the headers alone, and decode_exr_part decodes a chosen part — F16 parts stay F16. Deep parts stay with the deep decoder: a file that has any is refused here with a pointer to decode_deep_exr, by design. * EXR: cap the parts' sizes together, move the chosen part out instead of cloning it 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> |
||
|
|
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 <noreply@anthropic.com> * 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 <noreply@anthropic.com> * Regenerate docs/scorecard.md Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: leon <claude.udder842@passmail.net> Co-authored-by: Claude Mythos 5.1 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
107ffe9127 |
codecs: decode deep OpenEXR (deepscanline/deeptile), composited flat or structured (#642)
* codecs: decode deep OpenEXR (deepscanline/deeptile), composited flat or structured Deep EXR files errored with "not supported: deep data not supported yet" (the `exr` crate rejects deep headers, and its chunk framing for them is an unfinished guess), so the chunk stream is walked directly from the bytes, per the OpenEXR file-layout specification: - offset tables after the headers; deep chunks of [y | tile coords, packed table size, packed data size, unpacked data size, compressed cumulative sample-count table, compressed sample data], - NONE, RLE and ZIPS (zlib, the +128 difference predictor and the half-split interleave - applied to the count table and the sample data alike; a stored payload of uncompressed length is raw). ZIP-16 and mip/rip-map deep tiles decline with `unsupported`, - samples are channel-grouped, per pixel; u32 counts cap the allocation and go through `Limits` before anything is held in memory. `decode` composites to the flat image (samples sorted by Z, front-to-back `over` with premultiplied colour, un-premultiplied to straight alpha) and reports a new DecodeWarning::DeepFlattened; `decode_deep_exr` returns the structured DeepImage (per-channel samples plus cumulative counts) for callers that composite deep data themselves. exr::Error::NotSupported now maps to CodecError::unsupported instead of malformed, like the TIFF codec. Validated against the OpenEXR repository's deep test images (11/42/64/objectid .deep.exr: 256x256, up to 8 samples/pixel, plausible z and alpha), and covered by a synthetic generator (NONE/RLE/ZIPS x scanline/tile) with hand-computed compositing oracles, truncation sweeps and hostile offsets/sizes (13 tests). * Deep EXR: match the OpenEXR layout (RLE tokens and predictor, per-row tile tables and data), keep HDR Checked against deep files written by the OpenEXR 3.5 library (scan line and tiled, NONE/RLE/ZIPS): RLE tokens were inverted and skipped the predictor and byte split, the count table of a tile restarts at every row, a tile's sample data is grouped per row, edge tiles may store a full-size table, and the flattened colour was clamped to 1.0. The synthetic writer in the tests now follows the same layout; new HDR / edge-tile test. 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> |
||
|
|
8a8371395b |
Import warns when it keeps only the first frame or page, or a JPEG ends early (#518, #523) (#563)
Decoders now report what the decoded image doesn't show in a new `Image::warnings` (`photocraft_codecs::DecodeWarning`), and `image_to_document` turns them into import warnings, so they reach `photocraft-cli info` / `convert` stderr, `doc.open` over MCP and serve, and the app's open notice through the existing import-warnings path. * #523: APNG (acTL frames, plus a default image outside the animation), animated GIF (image descriptors counted by walking the blocks), animated WebP and multi-page TIFF (IFDs walked without decoding; reduced-resolution copies and transparency masks are not pages) say "only the first of N frames/pages was imported". Single images stay quiet; the kept frame's pixels are unchanged. * #518: a JPEG whose data runs out before its end-of-image marker (marker segments skipped by length, scan data by byte stuffing) still decodes leniently but warns "JPEG data ends early (the file is truncated or damaged); part of the image is missing". A JPEG that ends before any scan data is an error. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
d3b540234c |
Large images: adapter texture limits, banded compositing/export, proxy thumbnails, history byte budget (#71)
* Compose: render in place and in bands; area-reduced and proxy thumbnails render_tiled wrote every tile into a Vec and then copied them into the output: two full-size float copies (6.3 GB for a 14000² document). Tiles are now written straight into the output band by band. New streaming APIs so large documents never need a full-size float composite: render_bands (sink per band of ~8 MP), render_reduced (banded area average, bit-identical to the old flatten-then-average thumbnail) and flatten_to_surface (Flatten Image, Merge Visible, Copy Merged and the other flattened-surface users). thumbnail() reduces a downsampled proxy (moved from ui-egui to compose::proxy, now also scaling spot channels) for documents over 16 MP when that is faithful (no layer effects or vector masks), so navigator, channel and histogram thumbnails of a 196 MP document cost milliseconds. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * GPU canvas: request the adapter's real limits; banded CPU fallback; navigator thumbnail egui-wgpu asks for an 8192 px texture limit, so the wgpu compositor rejected every document wider or taller than 8192 px and each refresh fell back to a full CPU flatten. The desktop and web apps now request the adapter's own max_texture_dimension_2d and max_buffer_size (gpu_canvas::use_adapter_limits); the benches use the same limits. The CPU fallback (documents beyond the GPU limit, or features it lacks) is GpuCanvas::refresh: it renders and uploads the composite band by band instead of a full float composite plus a full RGBA8 copy. The CPU canvas texture is reduced in bands too, and the Navigator on the GPU canvas uses a cached thumbnail instead of a full-resolution CPU composite per revision. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * IO: banded export and import; no full-size copies in encoders; larger decode limits - Flat export renders the composite in bands and quantises each band; alpha is dropped in place when every pixel is opaque. The single-layer native path copies surface rows a band at a time (no f32 read_region and no per-pixel Vec). Buffers come from try_reserve: Err, not an abort. - PSD export encodes the merged image band by band into its planes (no flatten, no second plane copy); near-opaque documents are re-encoded without the white matte so the output is unchanged. Layer interleaved bytes are freed before their planes are compressed. - Flat import converts in ~32 MB bands (and not at all when already RGBA); the PSD merged-image un-matte works per tile row. - Encoders borrow the image when no conversion is needed (Image::converted) instead of cloning it; PNG only copies 16-bit data for the byte swap. - Decode limits: 2^30 pixels and 8 GiB on 64-bit targets (a 20000² JPEG was refused at 2^28). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Filters: write tile results in memory-bounded groups apply_tiled collected every tile's float result before writing any (3.1 GB for a 14000² layer). Results are now produced and written in groups of about 256 MB (at least one tile per core). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * History: bound pixel memory by Preferences > Performance > Memory Usage History::max_bytes caps the current document's tiles plus the tiles only history holds (layers, masks, alpha channels); History::trim drops the oldest states beyond it after every recorded step and when preferences change, always keeping the last step undoable. The engine sets it from memory_usage_mb, which was unused. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Large-image benchmark (14k / 20k): open, refresh, thumbnails, filter, stroke, saves One operation per run on a cached synthetic photo, with the GPU device created the way the app creates it, so /usr/bin/time -l reports that operation's peak memory. Documented in docs/development.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
69f0e25d5f |
Never crash: psd, codecs, format, io are panic-free (Rule 9) (#34)
- codecs: bump tiff 0.10 -> 0.11. tiff 0.10 indexed an empty SampleFormat list and panicked on a crafted TIFF (found by mutation fuzzing); regression test tiff_empty_sample_format_is_an_error. - codecs: Image::new returns Result instead of panicking on size overflow. - format: par_compress returns an error if a compression worker panics. - io: par_map joins every worker before relaying a worker panic, so the thread scope can't panic on an unjoined thread. - All four crates now deny unwrap/expect/panic/unimplemented/todo/ unreachable outside tests. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
1c09936154 |
Clippy 1.99: chunks_exact with a constant size -> as_chunks; drop a no-op Option filter
Rust 1.99's clippy adds chunks_exact_to_as_chunks and some_filter, which broke the Clippy step on every CI runner (it uses the latest stable). All sites are mechanical: as_chunks drops the remainder exactly like chunks_exact. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
f7eb6ea9e7 |
Format: adopt the craft-family rustfmt.toml and format the workspace
CI's `cargo fmt --all -- --check` has failed on every run because the repo had no rustfmt config and was never formatted. Use the same settings as EffectCraft/FilmCraft (max_width 160, use_small_heuristics Max), which also keeps the churn small. Formatting only; no code changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
42266291d5 |
Fidelity, GPU fallbacks, interactive performance; print spool-file fix
PSD oracle 111 -> 113 (tent effect blurs, bevel height/shading, glow range, spread rounding, text gamma, gradient span, Lab blending, colour burn/dodge edges), channel restrictions (brst), bevel contour/texture, chisel techniques. Vector masks, pass-through clips, artboards, pattern fills and bevel textures render on the GPU (parity <=1/255). PNG export 7x, PSD save 4x, .pcraft save 3.6x faster; CPU effects refresh 11x; deadlock in the effect-map blur fixed. interactive_bench example. Print: unique spool file per call (concurrent prints of same-named documents overwrote each other). Workspace: 2,040 tests pass, clippy clean, layers + wasm OK, parity 600/625. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
16941f7198 | one-shot |