Build1 publisher3 min readPublished
Chromium and Firefox reject the 35-tile HEIC that WebKit stitches and rotates
One converted iPhone photo decoded in Playwright's WebKit and failed in Chromium and Firefox. A three-line createImageBitmap call tells an upload component which case it has before it draws the box.
The Engineer · Build desk

What happened
- A dev.to writeup traces the common blank upload preview on iPhone photos to components that treat a browser's HEIC decode failure as nothing to show.
- Pointed at the same portrait HEIC through Playwright's bundled engines on macOS 26.5, WebKit returned 2448x3264 while Chromium and Firefox failed to decode the file at all.
- Wrapping the identical bytes in a File named x.bin with an empty MIME type produced exactly the same results in all three engines, because browsers sniff the bytes.
- The test file is a CC0 iPhone 6 JPEG from Wikimedia Commons converted with the sips tool in macOS 26.5; HDR, Live Photos and depth data were out of scope.
Compiled by The EngineerSomething wrong?How this is made
Why it matters
- capability A three-line decode attempt gives the component a real branch for a message, so the failing case can say the file is still selected instead of showing an empty box.
- constraint Any allow-list keyed on the extension or the reported MIME type tells you nothing about whether the preview will render, so that check cannot be the gate.
- decision Teams that want a real thumbnail now choose between shipping a WASM libheif build to the client on demand and sending the original to a server. Only the first keeps the file on the user's machine.
- exposure A server conversion path validated on a small single-tile sample can hand users a fraction of their photo with no error to signal it.
The probe is short enough to read in one pass: call `createImageBitmap` on the blob, close the bitmap, return true, and return false from the catch [13]. Against the HEIC it returns false in Chromium and Firefox, true in WebKit; against a JPEG control, true in all three engines at 2448x3264 with the EXIF rotation already applied [14]. The post prefers this to listening for an `<img>` error event because the failure rejects into a catch you control [15]. Do not branch on the error name. The two failing engines use different names across APIs [16].
A false says only that the browser could not decode it. Bytes 4 to 12 of the sample read `ftypheic`, so a header check separates an HEIC this browser cannot decode from a corrupt upload, and those two need different messages [17]. For the first case the post suggests one sentence in place of the empty box: "This browser can't preview HEIC. The file is still selected." That alone stops the re-select loop [18].
Decoding this format asks a lot of an engine. In the portrait sample the primary item is a grid of 35 `hvc1` tiles, each a 512x512 HEVC image laid out 7 by 5; the `ispe` property declares 3264x2448 landscape, and the portrait orientation sits in an `irot` of 270 degrees [5]. Seven tiles of 512 pixels make 3584 and five make 2560, so the stitched canvas is 320 pixels wider and 112 taller than the declared frame [1]. The same photo upscaled to 4032x3024 becomes 48 tiles [6]. To display it, an engine needs an HEVC decoder, has to stitch the grid, and has to apply the rotation [7].
That tiling is what catches a naive server pipeline. ffmpeg 7.1, given `ffmpeg -i in.heic out.png`, wrote 512x512: the first tile [22]. That is 262,144 pixels out of 7,990,272, about 3 percent of the photo, and it looks like a successful conversion until someone opens it [2].
The author leans local-first, working under compliance rules where "did the original leave the user's machine" has been a question to answer for months, and decoding locally means the original never leaves the machine [20]. The reference converter named in the post, imging, tells the user the browser cannot decode HEIC natively and offers a "Load decoder" button; after loading, the file reads locally, and the author saw no upload request for the original [21]. Converting the portrait sample to JPG gave 2448x3264 with EXIF orientation 1, so nothing rotates twice [23]. imging's HEIC output is marked server-side [24].
I would keep the WebKit column out of a bug ticket. WebKit is borrowing the macOS system decoder, and the engine under test is Playwright's WebKit build; real Safari, Windows and phone browsers were not tested [10]. For that 2448x3264 to transfer to your users, they would need a machine with a system HEVC decoder and a browser that still delegates to it. The post's author wrote that a photo working on his own Mac tells you nothing about the Chrome user who filed the bug [11].
What to watch
- A run on real Safari, on Windows and on a phone browser would show whether WebKit's success is the engine or the macOS system decoder it borrows.
- Native HEIC decode landing in Chromium would take the WASM download off the false branch entirely.
- Files straight off a phone, with HDR, Live Photos or depth data, may behave differently from this sips-converted sample.