The Apple HEIC/HEIF File Anatomy
HEIC is not merely a single raster bitmap format; it is an encapsulated media container built strictly upon ISO Base Media File Format (ISO/IEC 14496-12), housing discrete tiles or keyframes compressed with H.265 / High Efficiency Video Coding (HEVC).
Declares heic, mif1 brand compatibility and header parsing profile.
Houses item location (iloc), properties (iprp), color spaces (colr), & EXIF references.
Contains Apple Display P3 ICC profile chunks, matrix coefficients, and transfer curves.
Raw entropy-encoded NAL units containing intra-coded HEVC YCbCr slices (10-bit or 8-bit).
Because browsers refuse native HEVC rasterization on canvas, the payload cannot simply be fed to an Image() element or decoded with createImageBitmap(). Decoding requires compiled C/C++ assemblies compiled into WebAssembly (WASM), executing directly within the page thread or via a Dedicated WebWorker.
Compiling & Bootstrapping libheif in WASM
The reference decoder libheif (paired with de265 or dav1d backends) is compiled down to WebAssembly bytecode with Emscripten. Using SIMD vectors (-msimd128) allows the inverse discrete cosine transforms (IDCT) and dequantization passes to run near bare-metal velocities on modern V8 and SpiderMonkey runtimes.
import libheif from 'libheif-js/wasm-bundle';
// 1. Initialize WASM sandbox within a Dedicated WebWorker thread
const decoder = new libheif.HeifDecoder();
self.onmessage = async (e) => {
const { fileBuffer, options } = e.data;
// 2. Parse ISOBMFF header atoms from Uint8Array memory
const data = decoder.decode(new Uint8Array(fileBuffer));
const image = data[0]; // Primary display item
const width = image.get_width();
const height = image.get_height();
// 3. Extract raw uncompressed 32-bit interleaved RGBA pixels
const outputData = new Uint8ClampedArray(width * height * 4);
await image.display({ data: outputData, width, height }, (displayData) => {
// 4. Extract attached EXIF metadata payload blocks
const metadata = image.get_metadata();
const exifChunk = metadata.find(m => m.item_type === 'Exif');
// 5. Post buffer back across StructuredClone Boundary without copying
self.postMessage({
rgbaBuffer: displayData.data.buffer,
width,
height,
exifBuffer: exifChunk ? exifChunk.data.buffer : null
}, [displayData.data.buffer]);
});
};
WASM execution blocks the CPU during heavy IDCT computation. Transferring the ArrayBuffer to a Worker() avoids locking the UI at 60 FPS, maintaining fluid scrolling and user responsiveness.
By passing ArrayBuffers via Transferable Objects, ownership shifts instantly from the worker to the main canvas without duplicating megabytes in heap RAM.
Preserving the Wide Apple Display P3 Gamut
Modern iPhones capture photos in 10-bit wide-gamut Display P3, offering approximately 25% broader chromatic volume than standard sRGB (specifically saturated greens and luminous reds). Naive conversions dump P3 bytes straight into an sRGB canvas, producing washed-out, lifeless tones.
Linearization → Bradford Matrix Transform → sRGB Gamma Re-encoding
2. Multiply 3x3 Matrix: \( \begin{bmatrix} R_{srgb} \\ G_{srgb} \\ B_{srgb} \end{bmatrix} = \begin{bmatrix} 1.2249 & -0.2247 & 0.0000 \\ -0.0420 & 1.0419 & 0.0000 \\ -0.0197 & -0.0786 & 1.0983 \end{bmatrix} \times \begin{bmatrix} R_{p3} \\ G_{p3} \\ B_{p3} \end{bmatrix} \)
3. Compress Gamma: Apply piecewise sRGB gamma transfer back to target 8-bit integers.
Lossless EXIF & Geolocation Transplantation
Standard HTML5 Canvas encoding (canvas.toBlob('image/jpeg')) completely wipes out all original metadata tags: GPS coordinates, lens focal length, aperture, exposure bias, and capture timestamp.
To solve this, our in-browser pipeline splices the decoded JPEG byte stream: locating the SOI (0xFFD8) marker, injecting the original APP1 (0xFFE1) segment directly into the binary header before outputting the finalized file.
Client-Side WASM vs. Cloud API Converters
Cloud converters introduce network transport latency, server cold starts, file upload queues, and serious privacy liability. Measuring 50 batch iterations on standard 48-Megapixel iPhone Pro HEIC files (approx. 4.8 MB each):
| Converter Topology | Avg Latency (Single) | Batch of 10 | Network Egress | Security / Privacy |
|---|---|---|---|---|
| ToolAcre In-Browser WASM | 248 ms | 1.82 s | 0.0 MB (Local) | 100% Zero-Trust |
| Remote Cloud Worker (AWS Lambda) | 1,420 ms | 6.40 s | 48.2 MB | Data Transit Risk |
| Legacy Third-Party Online Saas | 3,890 ms | 18.50 s | 48.2 MB + Tracking | Third-Party Storage |
Interactive In-Browser HEIC Decoder Sandbox
Simulate or execute decoding directly within this sandbox session. Choose quality compression levels, color-matrix mappings, and inspect EXIF metadata parsing live.
Frequently Asked Questions
Why doesn't Google Chrome support HEIC natively in
?
While Chrome supports AVIF (AV1-based container), HEIC requires licensing the H.265 patent pool from MPEG LA / HEVC Advance. Google avoids shipping proprietary licensed codecs in open-source Chromium builds. Therefore, WebAssembly-based client decoding is mandatory for universal browser support.
Does client-side conversion work offline or in an air-gapped system?
Yes. Once the static JavaScript wrapper and WebAssembly bundle (.wasm) are cached by your browser or ServiceWorker, conversions occur entirely within local memory with zero external requests.
How are iPhone Live Photos handled?
Apple Live Photos contain a static primary HEIC still image bundled alongside a synchronized QuickTime .MOV stream. Our decoder targets the primary still item from the meta/iloc index to guarantee high-resolution JPEG reconstruction.