cd /news/developer-tools/supporting-heic-uploads-the-decoder-… · home › topics › developer-tools › article
[ARTICLE · art-140855] src=dev.to ↗ pub= topic=developer-tools verified=true sentiment=· neutral

Supporting HEIC uploads: the decoder is 1.46 MB, but most visitors never download it

A developer measured the real cost of adding HEIC upload support to a browser-based image tool, finding that Chromium 149 and a Firefox 151 build fail every native decode path (<img>, img.decode(), createImageBitmap, and WebCodecs ImageDecoder) while only Playwright's WebKit 26.5 build on macOS succeeds by handing files to the system decoder. The lazy-loaded libheif WASM bundle is 1,461,926 bytes uncompressed and about 510 KB over gzip, but repeat visits cost only 1,095 bytes via 304 responses, so the decoder is paid only by visitors who upload a HEIC and fail a native decode. The developer also found JPEG at quality 80 (662 KB in Chromium) far cheaper than PNG (13.52 MB) for a 12MP sample, and noted all figures come from a single M4 machine with no real-phone decode timing measured.

by read3 min views1 publishedSep 28, 2026

My tool sites only accept JPG, PNG and WebP. Every time an iPhone user drops a HEIC file on the upload box, it gets rejected, and I have been putting off fixing that for months. The reason is boring: Chrome cannot read HEIC, so reading it in the browser means shipping a decoder. I pay for bandwidth by the gigabyte, so before writing any code I wanted the actual byte count. For the measurements I used the HEIC reader in ImgIng (https://imging.ai/) on an Apple M4 with 16 GB, driving the browsers bundled with Playwright 1.61.1 in headless mode, with HEIC files I generated from synthetic images using macOS sips (not photos from a phone).

I tried four native paths: <img>, img.decode(), createImageBitmap and WebCodecs ImageDecoder. Chromium 149 and the Firefox 151 build failed all of them. <img> fires error, createImageBitmap throws InvalidStateError, and changing the Blob type to image/heic changes nothing. ImageDecoder.isTypeSupported('image/heic') returns false in both, while the same call for AVIF returns true. Only the Playwright WebKit 26.5 build on macOS decoded the files, because it hands them to the system decoder. So for Chrome users you either bring your own decoder or you do the work on the server.

ImgIng tries the native path first and only offers a "load decoder" button when that fails. Clicking it adds exactly two GET requests. The small one is a 2,107-byte script. The big one is libheif-bundle.mjs, which is 1,461,926 bytes uncompressed and carries the WASM inline as base64: 1,034,305 bytes of it, with libheif 1.19.8 and libde265 strings inside. There is no separate .wasm request. Over the wire, with gzip, both files together cost 522,557 bytes, roughly 510 KB.

The repeat visit is where it gets cheap. Both files are served with max-age=0, must-revalidate, so a refresh sends two conditional requests and gets two 304 responses: 1,095 bytes in total. The page also remembers in localStorage which decoders were used and reloads them silently, so the button does not come back. No Service Worker, no Cache Storage. On the WebKit build nothing is downloaded at all, since the native decode succeeds.

That changed my math. The 1.46 MB number looked scary next to my homepage, but it is only paid by visitors who upload a HEIC and whose browser cannot decode it, and only once per cache lifetime. My plan is now simple: keep it off the homepage, never preload it on the upload page, and fetch it only after a .heic file arrives and a native decode attempt fails.

The bigger bandwidth question turned out to be the output format. A 12MP portrait sample of 1.25 MB became 13.52 MB as PNG. As JPEG at quality 80 it was 662 KB in Chromium, and 2.66 times that in the WebKit build at the same quality setting. I will convert to JPEG. Whether that is smaller than the HEIC depends on the image: an earlier batch made from openly licensed photos grew by 37 to 61 percent.

What I have not measured is download plus decode time on a real phone. All my numbers come from one M4.

If you are weighing HEIC support, check the native decode first, then lazy-load the decoder only for the files that fail. Run your own users' typical photos through both output formats before picking one.

── more in #developer-tools 4 stories · sorted by recency
── more on @chromium 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
→ Live at https://your-agent.zahid.host ✓
Get free account → Pricing
from €0/mo · no card required
LIVE [news/supporting-heic-uplo…] indexed:0 read:3min 2026-09-28 · —