A HEIC photo is nine pictures and an 8-byte note on how to staple them together
HEIC gets two complaints, and they are usually made in the same breath: the files are impressively small, and almost nothing will open them. Both are true, both have concrete causes, and the causes are unrelated to each other.
The small part is a codec decision. The opening part is a container decision. This article measures the first and takes apart a real file to show the second.
How much smaller, actually
"Half the size of JPEG" is the usual claim. It is worth testing, because the quality numbers in the two formats are not comparable — a HEIC at quality 50 and a JPEG at quality 50 have nothing in common except the digits.
So: one source image, saved losslessly as PNG, fed to both encoders, and the results scored by peak signal-to-noise ratio against that same source. Higher PSNR is closer to the original.
HEIC bytes PSNR JPEG bytes PSNR
q30 15,212 38.29 q30 42,006 36.47
q50 27,996 40.03 q50 50,805 38.38
q70 61,528 41.05 q70 63,788 40.14
q85 83,450 41.37 q85 106,758 42.87
q95 218,831 42.11 q95 182,723 48.30
Read it by finding equal quality rather than equal settings.
At about 38.3 dB, HEIC needs 15,212 bytes. JPEG reaches the same quality at just under its quality 50, which costs about 50,000. That is roughly a third of the size for the same picture.
At about 40 dB, HEIC needs 27,996 bytes and JPEG needs 63,788. Less than half.
So the claim holds, and at the lower end it is understated. But look at the last row.
Where HEIC stops
Between quality 70 and 95, HEIC's file size more than triples — from 61,528 to 218,831 bytes — and the quality improves by one decibel. JPEG at quality 95 is smaller and six decibels better.
HEIC appears to hit a ceiling at around 42 dB that more bytes will not break through. Splitting the error by channel says where the ceiling is:
bytes Y (brightness) Cb Cr
HEIC q50 27,996 46.40 42.58 43.32
HEIC q95 218,831 51.17 43.80 44.89
JPEG q50 50,805 42.87 42.02 42.44
JPEG q95 182,723 54.79 50.87 51.28
HEIC's brightness channel behaves exactly as expected: 46.4 dB at quality 50, 51.2 at quality 95, and it beats JPEG's luma at quality 50 by three and a half decibels while using half the bytes. The codec is doing its job.
The colour channels are the ones that stall. They move from about 43 dB to about 44 dB while the file triples. JPEG's colour channels, meanwhile, climb from 42 to 51 dB, because above quality 95 that encoder stops throwing colour resolution away.
The combined score is dragged down by whichever channel is worst, so HEIC's overall figure is pinned by colour no matter how many bytes go into brightness. That is an encoder policy, not a limit of the codec, and it means the headline is narrower than it is usually stated: HEIC is dramatically smaller at the qualities phones and messaging apps actually use, and the wrong tool for an archival master.
What the codec does that JPEG cannot
The byte counts say HEVC wins; they do not say how. One of the differences is visible with the same grid test used on JPEG: measure the average brightness step across every eighth column and divide it by the average step elsewhere. A value of 1 means there is no grid.
The source here had already been a JPEG once, so it arrives carrying a grid of its own — 1.482 before either encoder touches it. That makes it a useful test, because now we can watch what each encoder does to a grid that already exists.
bytes every 8px
source (lossless copy) - 1.482
JPEG q50 50,805 2.088
HEIC q50 27,996 1.247
JPEG takes the existing grid and deepens it, as the previous article measured in detail. HEIC, using 55% of the bytes, leaves the picture with less of a grid than it started with.
That is not a rounding artefact. HEVC runs a deblocking filter inside the coding loop: after each block is reconstructed, the edges between blocks are smoothed, and the smoothed result is what the next block is predicted from. JPEG has nothing equivalent — its decoder hands back exactly what the inverse transform produces, boundaries and all. So an encoder that re-encodes an already-blocky picture through HEVC partly cleans it up on the way through, while JPEG can only add.
The other large difference does not show up in this test. JPEG transforms every block independently and never predicts: each 8x8 square is described from scratch. HEVC predicts each block from its already-decoded neighbours first and only codes the difference, over block sizes it chooses per region rather than a fixed 8x8. A flat sky costs almost nothing because the prediction is already right. That is where most of the 2-to-3x comes from.
Now the file itself
A JPEG begins with two bytes, FF D8, and from there a decoder is essentially already reading the picture. HEIC does not work like that at all. It is built on the same container as MP4 — a tree of boxes, each with a length and a four-character name.
Here is the entire structure of the 88,010-byte file produced above:
ftyp @0 36 B major=heic compatible=mif1 MiPr miaf MiHB heic
meta @36 915 B
hdlr @48 33 B
dinf @81 36 B
dref @89 28 B
pitm @117 14 B
iinf @131 245 B
iref @376 56 B
iprp @432 311 B
ipco @440 216 B
colr @448 19 B
clli @467 12 B
ispe @479 20 B
ispe @499 20 B
irot @519 9 B
pixi @528 16 B
hvcC @544 112 B
ipma @656 87 B
idat @743 16 B
iloc @759 192 B
mdat @951 87,059 B <- the picture data
915 bytes of bookkeeping wrapped around 87 kilobytes of picture. That ratio is fine. The problem is what the bookkeeping says.
The primary item is not a picture
The pitm box names the primary item — the thing you get when you open the file. In this file it says item 10.
The iinf box lists what the items are:
1=hvc1 2=hvc1 3=hvc1 4=hvc1 5=hvc1
6=hvc1 7=hvc1 8=hvc1 9=hvc1 10=grid 11=Exif
Items 1 through 9 are HEVC-coded pictures. Item 11 is the Exif metadata. Item 10 — the primary one, the one that is the photograph — has the type grid.
A grid is not an image. It is an instruction. Reading its bytes requires the iloc box, which gives each item's location, and for this item the location is not in mdat at all but in the small idat box. All eight bytes of it:
00 00 02 02 04 b0 04 b0
version 0, flags 0
rows = 2 + 1 = 3
columns = 2 + 1 = 3
width = 0x04b0 = 1200
height = 0x04b0 = 1200
Three rows by three columns, composing to 1200 by 1200. Which nine items? That is in yet another box, iref, under the sub-type dimg:
iref/dimg: item 10 is built from 9 items -> [1, 2, 3, 4, 5, 6, 7, 8, 9]
So this single photograph is stored as nine entirely separate HEVC bitstreams. The two ispe properties give their size and the output size: the tiles are 512 by 512, and three of them across is 1536, so the composed image is cropped back to the declared 1200.
The arithmetic closes, which is the check that the parse is right: the nine tile payloads come to 86,965 bytes, the Exif item is 78, and together that is 87,043 — exactly the size of the mdat payload. Nothing unaccounted for.
What opening one actually takes
Putting that in order, here is what a program must do to show a HEIC photograph, before it decodes a single pixel:
read pitm -> the primary item is 10
read iinf -> item 10 is a "grid", not a picture
read iloc -> its bytes are in idat, not mdat
parse those 8 bytes -> 3x3 tiles composing to 1200x1200
read iref/dimg -> the tiles are items 1..9
read iprp/ipco/hvcC -> the HEVC decoder configuration
decode 9 separate HEVC bitstreams and compose them
Seven steps across six box types, and only then does an HEVC decoder get to start — nine times.
Compare that with the format it replaced. To display a JPEG: find the start-of-frame marker for the dimensions, read the Huffman and quantisation tables, decode. One image, one pass, no indirection.
This is the real answer to "why doesn't my software open HEIC". It is not that the format is secret or that nobody has got round to it. It is that a conforming reader has to implement a general-purpose item-and-property database, tile composition, and a full HEVC intra decoder — and HEVC's patent licensing has historically made bundling that decoder a commercial decision rather than a technical one. A library that handles JPEG in a few hundred lines needs something far larger here.
Why the tiles exist at all
The tiling is not arbitrary. HEVC was designed for video, and hardware decoders in phones are built around a maximum frame size and benefit from work that can be split across cores. Cutting a large still into 512-pixel tiles lets the same silicon that decodes video decode a photograph, in parallel, without a special path.
That is a sound engineering decision on a phone. It is also exactly the decision that makes the file awkward everywhere else, and it is worth noticing that the awkwardness is not a side effect — it is the same choice, seen from the other end.
When these numbers do not apply
This is one encoder. The file was produced by Apple's, which is the one that matters for photos from an iPhone but is not the only one. Other HEIF encoders make different choices, including about tiling and about the colour handling that caused the ceiling above.
The source had been a JPEG before. Both encoders were fed the same lossless copy of it, so the comparison between them is fair, but neither was working from a pristine camera original. The absolute PSNR figures would be higher with a true original; the relative picture is what this measures.
PSNR is not perception. It is a reproducible proxy, which is why it is used here, but it does not fully agree with how the result looks. A six-decibel gap is real; a half-decibel one is not worth arguing about.
Not every HEIC is a grid. Small images are often stored as a single hvc1 item with no tiling at all, in which case the walk above is shorter. The machinery still has to be there, because the reader cannot know which kind it has until it has read the item table.
Nothing here is about AVIF. AVIF uses the same container and the same item structure, so the opening problem is identical, but the codec underneath is AV1 rather than HEVC, with a different licensing position and different behaviour at high quality.
Checking it yourself
Every claim above is a few bytes into the file. Boxes are a 4-byte big-endian length followed by a 4-byte name, so the first 8 bytes of any HEIC read as a length and then ftyp. Skip that many bytes and the next name appears.
One trap worth knowing: a length of 1 does not mean a 1-byte box. It means the real 64-bit length follows the name, which is how a box larger than 4 GB is expressed. A parser that misses this reads the file as 1-byte boxes and gives up — the first version of the parser used for this article did exactly that, and reported the picture as 8 bytes long.
If you want the short version: search a HEIC for the four ASCII characters grid. If they are there, the photograph you are looking at is several pictures in a trench coat.
Sources
- ISO/IEC 23008-12 — High Efficiency Image File Format (HEIF)
- ISO/IEC 14496-12 — ISO base media file format
- ISO/IEC 23008-2 / ITU-T H.265 — High Efficiency Video Coding
Tools mentioned
← All articles · corrections: editorial policy