PNG is one of the more pleasant binary formats to read by hand. It is built from self-describing chunks, every one is checksummed, and the format encodes information in a place most designs would not think to use β the capitalisation of its own field names. This page walks through the structure completely, with a real file in hex.
The structure
An 8-byte signature, then a sequence of chunks. Each chunk is: 4-byte length, 4-byte type, the data, 4-byte CRC. The first chunk is always IHDR and the last is always IEND. Everything else is between them, in almost any order.
The signature
Every byte is doing a job, and the design is worth appreciating:
89β has the high bit set. Software that strips the eighth bit, as some 7-bit transfer paths did, changes this byte and the corruption is immediately detectable.50 4E 47βPNG, readable in any hex dump or text editor.0D 0Aβ a CRLF pair. A transfer that converts line endings to Unix style deletes the0D.1Aβ DOS end-of-file. RunningTYPE image.pngstops here rather than dumping binary across the terminal.0Aβ a bare LF. A transfer converting to Windows line endings turns this into0D 0A, again detectably.
The two line-ending bytes catch conversion in both directions. In 1995, transferring a binary file over FTP in ASCII mode was a common and silently destructive mistake, and these eight bytes turn it into an immediate, clear failure.
Chunk structure
Two design consequences follow from the length coming first. A decoder can always skip a chunk it does not recognise β read the length, jump that far, continue. And it can validate every chunk independently, so corruption is localised and identifiable rather than cascading.
π‘ Every chunk is checksummed
A CRC-32 on each chunk means a decoder can tell you exactly which part of a file is damaged. JPEG has nothing comparable β a corrupt JPEG simply renders wrongly from the point of damage onwards, with no way to identify where or what.
The capitalisation rule
This is the cleverest thing in the format. A chunk type is four ASCII letters, and the case of each letter is a bit of machine-readable information.
| Letter | Uppercase means | Lowercase means |
|---|---|---|
| 1st | Critical β a decoder that cannot handle it must fail | Ancillary β safe to skip |
| 2nd | Public β registered in the specification | Private β application-specific |
| 3rd | Reserved β always uppercase | (reserved for future use) |
| 4th | Unsafe to copy β depends on pixel data | Safe to copy into an edited file |
The fourth bit is the subtle one and solves a real problem. Suppose an editor opens a PNG containing a chunk it has never heard of, modifies the image, and saves. Should it keep the unknown chunk?
It depends entirely on what that chunk describes. A copyright notice should survive. A chunk describing histogram data for the original pixels is now wrong and must be dropped. The editor cannot know which β but the chunk name tells it, without understanding the chunk at all.
Four bits of metadata carried in a field that had to exist anyway, at zero cost. It is the kind of design detail that separates formats which age well from formats which do not.
The four critical chunks
IHDR β the header
Always first, always exactly 13 bytes:
| Offset | Size | Field |
|---|---|---|
| 0 | 4 | Width in pixels |
| 4 | 4 | Height in pixels |
| 8 | 1 | Bit depth (1, 2, 4, 8 or 16) |
| 9 | 1 | Colour type |
| 10 | 1 | Compression method (always 0) |
| 11 | 1 | Filter method (always 0) |
| 12 | 1 | Interlace (0 = none, 1 = Adam7) |
| Colour type | Meaning | Channels | Valid bit depths |
|---|---|---|---|
| 0 | Greyscale | 1 | 1, 2, 4, 8, 16 |
| 2 | Truecolour (RGB) | 3 | 8, 16 |
| 3 | Indexed (palette) | 1 | 1, 2, 4, 8 |
| 4 | Greyscale + alpha | 2 | 8, 16 |
| 6 | Truecolour + alpha | 4 | 8, 16 |
Types 1 and 5 do not exist β the numbering is a bit field where bit 0 means palette, bit 1 means colour, bit 2 means alpha, and the missing values are combinations that make no sense.
β Choosing colour type 3 is the biggest easy saving
An image with 256 or fewer distinct colours β a logo, a chart, an icon, a flat-design illustration β stored as type 3 uses one byte per pixel instead of four. That is a 75% reduction before compression even begins.
Most export dialogues default to type 6 regardless of content. Running pngquant or checking the "8-bit" option in your export settings is frequently the single largest PNG size win available, and for flat-colour graphics it is visually lossless.
IDAT β the pixel data
All IDAT chunks concatenated form a single zlib stream. Decompress it and you get the raw scanlines β each one prefixed by a single byte naming the filter applied to that row.
| Filter | Name | Each byte is stored as |
|---|---|---|
| 0 | None | The raw value |
| 1 | Sub | Difference from the byte to the left |
| 2 | Up | Difference from the byte above |
| 3 | Average | Difference from the mean of left and above |
| 4 | Paeth | Difference from a predictor using left, above and above-left |
The filters compress nothing themselves. They transform the data into a form that compresses well β a smooth gradient becomes a long run of the same small difference, and DEFLATE handles runs extremely efficiently.
The filter is chosen per row, so a good encoder tries all five on every scanline and keeps whichever produces the most compressible output. This is exactly what oxipng and zopflipng do more thoroughly than typical export code, which is why they shrink files by 10β30% without altering a single pixel.
PLTE and IEND
PLTE holds up to 256 RGB triples, three bytes each. Required for colour type 3, optional as a suggested quantisation for types 2 and 6, forbidden for greyscale types.
IEND is always the same twelve bytes and carries no data at all:
AE 42 60 82 at the end of a file is a reliable PNG marker. It is also useful for a specific recovery case: data appended after IEND is ignored by decoders, so files sometimes carry hidden payloads there.
Ancillary chunks worth knowing
| Chunk | Holds |
|---|---|
tRNS | Transparency for palette or a single colour, without a full alpha channel |
gAMA | Gamma value of the source |
cHRM | Chromaticity and white point |
sRGB | Declares sRGB and a rendering intent |
iCCP | An embedded ICC colour profile |
tEXt | Latin-1 key/value text |
zTXt | Compressed text |
iTXt | UTF-8 text, optionally compressed β where XMP metadata lives |
pHYs | Physical pixel dimensions β the DPI field |
bKGD | Suggested background colour |
tIME | Last modification time |
acTL fcTL fdAT | APNG animation control and frames |
β οΈ PNG carries metadata too
PNG has no EXIF by default, which leads people to assume PNGs are metadata-free. They are not β tEXt and iTXt chunks routinely carry software names, author fields, comments, and full XMP blocks including copyright and sometimes location data.
Strip them explicitly before publishing: oxipng --strip safe keeps the chunks that affect rendering and removes the rest.
How APNG stays compatible
APNG is a good illustration of the naming rules working. Its three chunks are all lowercase-initial, so they are ancillary β a decoder without APNG support skips them entirely and renders the IDAT as a normal still image.
So an animated PNG shown in software that has never heard of APNG displays the first frame correctly rather than failing. That graceful degradation was designed in, and it is why APNG could be deployed by browsers without breaking anything.
Reading a real file
Adam7 interlacing
Interlaced PNG stores the image in seven passes, each a sparse sampling of the whole picture, so a partially downloaded file shows a low-resolution version of the entire image rather than the top portion.
Pass 1 is one pixel in 64. Each subsequent pass fills in more. The technique is named after Adam Costello, who designed it.
It made real sense over a modem. Today it is usually a mistake: interlacing typically makes the file 20β35% larger, because splitting the image into seven sparse passes destroys the local coherence the prediction filters depend on. On a modern connection the progressive display buys nothing that matters and costs bandwidth on every load.
Working with PNG files?
Convert, compress and strip metadata from images entirely in your browser β nothing is uploaded to a server.
Open the Image Compressor βSummary
- The 8-byte signature is a corruption test for the transfer failures of its era.
- Chunks are length, type, data, CRC β self-describing, so unknown chunks are skippable.
- Capitalisation in chunk names carries four bits of meaning, including whether an editor may preserve a chunk it does not understand.
- IHDR, PLTE, IDAT, IEND are the critical chunks.
- Colour type 3 uses a quarter of the bytes for images with 256 or fewer colours.
- Filters run per scanline, which is why optimisers can shrink files without changing pixels.
- PNG does carry metadata in
tEXtandiTXtchunks. Strip it before publishing. - Interlacing costs 20β35% and buys little today.
Frequently Asked Questions
What are the first bytes of a PNG file?
89 50 4E 47 0D 0A 1A 0A. The 50 4E 47 is the letters PNG in ASCII. The surrounding bytes are a deliberate corruption test β the high-bit byte, a CRLF pair, a DOS end-of-file marker and a bare LF all detect specific ways a binary file can be damaged in transfer.
What is a PNG chunk?
The unit PNG is built from. Each chunk has a 4-byte length, a 4-byte type name, the data itself, and a 4-byte CRC-32 checksum. Because the length is stated up front, a decoder can skip any chunk it does not recognise and continue β which is what makes the format extensible.
Why does the capitalisation of PNG chunk names matter?
Each of the four letters carries a bit of information in its case. An uppercase first letter means the chunk is critical and a decoder that cannot handle it must stop. Lowercase means ancillary and safely skippable. The second letter marks public versus private, and the fourth marks whether an editor may copy the chunk into a modified file it does not understand.
What are the four critical PNG chunks?
IHDR holds the dimensions and colour format and must come first. PLTE holds the palette, required only for indexed-colour images. IDAT holds the compressed pixel data and may be split across many chunks. IEND marks the end and contains no data at all.
Why is PNG image data split across multiple IDAT chunks?
Because a chunk's length field limits it, and splitting lets an encoder write data in a streaming fashion without buffering the whole image. Decoders concatenate every IDAT chunk into one compressed stream before decompressing, so the split points are arbitrary and carry no meaning.