Files
photocraft/xtask/heif-corpus.sha256
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]>
2026-10-07 10:20:40 -04:00

10 lines
859 B
Plaintext

f24a3b6ae495605e179bd34ae282047dcc4581819eb53041db34c9fdb66648cd heic-rs/checker-1024.heic
26b302d18d5c0e932a69092dafe4ac25d63814514c63718a08325fd84b38d5d4 heic-rs/checker-1024.ref.png
772a9b8fab3022ea95eb0b1d069053448f9602fad7829b33506877039a6af2c5 heic-rs/checker-64.heic
aa89bda71d2359691d32362923967ff4839e8a4f7d54641b10c647c9fbf9aa2c heic-rs/checker-64.ref.png
6e65bccd51ff2effdfce99ac4275b9f79c85bba51b5875d7fa455e1412821c25 heic-rs/rgb-strips-96.heic
b3a619b185d4cdfb1aafb419fec4f054d86853bb6a65e5d044527e84579407bb heic-rs/rgb-strips-96.ref.png
9ad0c62a8e044f0ce0a342153a6da3f360c688b88bd5287a2c8fbdba3055fbcb heic-rs/with-exif.heic
0499c3a3d8aa1c4383494f90cc127a693f036ab6228f0b91ddcf96f662520129 pillow-heif/heif/RGBA_10__29x100.heif
43960a48f77ffd19343dd38831cf9c4d25290edeeef1d4b35d57b0aa538b0734 pillow-heif/non_heif/RGBA_16__29x100.png