Pure Rust HEIC/HEIF image decoder. No C/C++ dependencies, no unsafe code.
#![forbid(unsafe_code)]— zero unsafe blocksno_std + alloccompatible (works on wasm32)- AVX2 SIMD acceleration with automatic scalar fallback
- Decodes 1280x854 in ~57ms on x86-64
Decodes most HEIC files from iPhones and other cameras. 91% pixel-exact vs libheif, 55 dB PSNR across remaining differences.
- HEIF container parsing (ISOBMFF boxes, grid images, overlays)
- Full HEVC I-frame decoding (VPS/SPS/PPS, CABAC, intra prediction, transforms)
- Deblocking filter and SAO (Sample Adaptive Offset)
- YCbCr→RGB with BT.601/BT.709/BT.2020 matrices (full + limited range)
- 10-bit HEVC (transparent downconvert to 8-bit output)
- Alpha plane decoding, HDR gain map extraction
- EXIF/XMP metadata extraction (zero-copy)
- Thumbnail decode, image rotation/mirror transforms
- HEVC scaling lists (custom dequantization matrices)
- AVX2/SSE4.1 SIMD for color conversion, IDCT 8/16/32, IDST 4, residual add, dequantize
- Optional tile-parallel decoding via rayon (
parallelfeature)
- I-slices only (sufficient for HEIC still images, no inter prediction)
| Feature | Default | Description |
|---|---|---|
std |
yes | Standard library support. Disable for no_std + alloc. |
parallel |
no | Parallel tile decoding via rayon. Implies std. |
use heic_decoder::{DecoderConfig, PixelLayout};
let data = std::fs::read("image.heic")?;
let output = DecoderConfig::new().decode(&data, PixelLayout::Rgba8)?;
println!("{}x{} image, {} bytes", output.width, output.height, output.data.len());use heic_decoder::{DecoderConfig, PixelLayout, Limits};
let mut limits = Limits::default();
limits.max_width = Some(8192);
limits.max_height = Some(8192);
limits.max_pixels = Some(64_000_000);
limits.max_memory_bytes = Some(512 * 1024 * 1024);
let output = DecoderConfig::new()
.decode_request(&data)
.with_output_layout(PixelLayout::Rgba8)
.with_limits(&limits)
.decode()?;use heic_decoder::ImageInfo;
let info = ImageInfo::from_bytes(&data)?;
println!("{}x{}, alpha={}, exif={}", info.width, info.height, info.has_alpha, info.has_exif);let info = ImageInfo::from_bytes(&data)?;
let mut buf = vec![0u8; info.output_buffer_size(PixelLayout::Rgba8).unwrap()];
let (w, h) = DecoderConfig::new()
.decode_request(&data)
.with_output_layout(PixelLayout::Rgba8)
.decode_into(&mut buf)?;let decoder = DecoderConfig::new();
let exif: Option<&[u8]> = decoder.extract_exif(&data)?; // raw TIFF bytes
let xmp: Option<&[u8]> = decoder.extract_xmp(&data)?; // raw XML bytes
let thumb = decoder.decode_thumbnail(&data, PixelLayout::Rgb8)?; // smaller previewSIMD-accelerated on x86-64 (AVX2 for color conversion, IDCT 8/16/32; SSE4.1 for IDST 4). Scalar fallback on other architectures.
| Image | Size | Time (release) |
|---|---|---|
| example.heic | 1280x854 | ~57ms |
| iPhone 12 Pro | 3024x4032 | ~470ms |
| Probe (metadata only) | any | ~1µs |
| Thumbnail decode | any | ~4ms |
| EXIF extraction | any | ~4µs |
With the parallel feature, grid-based images decode tiles concurrently via rayon.
decode_into() uses a streaming path for grid-based images (most iPhone photos) that color-converts each tile directly into the output buffer. This avoids the intermediate full-frame YCbCr allocation, reducing peak memory by ~60% compared to decode().
Use DecoderConfig::estimate_memory() to check memory requirements before decoding.
Licensed under either of:
- Apache License, Version 2.0 (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
- MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT)
at your option.
Developed with Claude (Anthropic). Not all code manually reviewed. Review critical paths before production use.