Skip to main content

Overview

PNG (Portable Network Graphics) is a lossless raster image format that uses compression to reduce file size while preserving image quality. Understanding the binary structure is essential for this assignment.

PNG file structure

Every PNG file consists of:
1

8-byte signature

Fixed magic bytes that identify the file as PNG
2

Series of chunks

Variable-length data blocks, each with a specific purpose
3

IEND chunk

Special chunk marking the end of the file

PNG signature

The first 8 bytes of every valid PNG file are:
0x89 0x50 0x4E 0x47 0x0D 0x0A 0x1A 0x0A
In a hex dump:
$ hd image.png | head -1
00000000  89 50 4e 47 0d 0a 1a 0a  00 00 00 0d 49 48 44 52  |.PNG........IHDR|

Purpose of each byte

0x89
byte
High bit set - detects systems that don’t handle 8-bit data
0x50 0x4E 0x47
bytes
ASCII “PNG” - identifies the format
0x0D 0x0A
bytes
DOS-style line ending - detects DOS/Unix line ending conversion
0x1A
byte
DOS end-of-file character - stops file display under DOS
0x0A
byte
Unix-style line ending - detects Unix/DOS conversion

Validating the signature

uint8_t signature[8] = {0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A};
uint8_t buffer[8];

if (fread(buffer, 1, 8, fp) != 8) {
    return NULL; // Read error
}

if (memcmp(buffer, signature, 8) != 0) {
    return NULL; // Not a valid PNG
}

// File pointer now positioned at first chunk

Chunk structure

After the signature, a PNG file consists entirely of chunks. Each chunk has this structure:
Length (4 bytes)Number of bytes in the data field
Type (4 bytes)ASCII chunk type identifier
Data (variable)Chunk payload (length specified above)
CRC (4 bytes)Cyclic redundancy check

Length field

  • 4 bytes, big-endian unsigned 32-bit integer
  • Specifies the number of bytes in the data field only
  • Does NOT include the length, type, or CRC fields
  • Can be zero (e.g., IEND chunk has no data)
uint8_t len_bytes[4];
fread(len_bytes, 1, 4, fp);

// Convert from big-endian to native
uint32_t length = (len_bytes[0] << 24) | (len_bytes[1] << 16) |
                  (len_bytes[2] << 8)  | len_bytes[3];

Type field

  • 4 ASCII characters (printable bytes)
  • Case-sensitive
  • Identifies the chunk’s purpose
Common chunk types:
IHDR
critical
Image Header - Must be first chunk, contains image dimensions and format
PLTE
critical
Palette - Color palette for indexed-color images (color type 3)
IDAT
critical
Image Data - Compressed pixel data (can appear multiple times)
IEND
critical
Image End - Must be last chunk, marks end of file
tRNS
ancillary
Transparency - Transparency information
bKGD
ancillary
Background - Default background color

Data field

  • Variable length (specified by length field)
  • Content depends on chunk type
  • Can be empty (length = 0)

CRC field

  • 4 bytes, big-endian unsigned 32-bit integer
  • Computed over the type and data fields (not length or CRC itself)
  • Uses CRC-32 algorithm with polynomial 0xEDB88320
  • Used to detect corruption

Big-endian encoding

PNG uses big-endian (most-significant byte first) for all multi-byte integers. Intel x64 processors are little-endian.

Example: Converting 32-bit integers

// Big-endian bytes: 0x00 0x00 0x01 0x40
// Represents: 320 in decimal

uint8_t bytes[4] = {0x00, 0x00, 0x01, 0x40};

// Manual conversion
uint32_t value = (bytes[0] << 24) | (bytes[1] << 16) |
                 (bytes[2] << 8)  | bytes[3];
// value = 320

// Or use utility function
uint32_t value = read_u32_be(bytes);

Why endianness matters

If you forget to convert:
// WRONG: Reading as little-endian on x64
uint32_t *ptr = (uint32_t*)bytes;
uint32_t value = *ptr;
// value = 0x40010000 = 1073807360 (completely wrong!)

// CORRECT: Convert from big-endian
uint32_t value = (bytes[0] << 24) | (bytes[1] << 16) |
                 (bytes[2] << 8)  | bytes[3];
// value = 0x00000140 = 320 (correct)

Critical chunks

For this assignment, focus on these four chunk types:

IHDR (Image Header)

Must be the first chunk. Contains 13 bytes of data:
Width
uint32_t
required
Image width in pixels (big-endian)
Height
uint32_t
required
Image height in pixels (big-endian)
Bit depth
uint8_t
required
Bits per sample: 1, 2, 4, 8, or 16
Color type
uint8_t
required
  • 0: Grayscale
  • 2: RGB (Truecolor)
  • 3: Indexed (Palette)
  • 4: Grayscale + Alpha
  • 6: RGB + Alpha
Compression
uint8_t
required
Must be 0 (deflate/inflate)
Filter
uint8_t
required
Must be 0 (adaptive filtering with five basic filter types)
Interlace
uint8_t
required
0 = no interlace, 1 = Adam7 interlace

PLTE (Palette)

  • Contains 1-256 RGB color entries
  • Each entry is 3 bytes: Red, Green, Blue (0-255 each)
  • Length must be divisible by 3
  • Required for color type 3, optional for types 2 and 6
Example with 2 colors:
Length: 6 (2 colors × 3 bytes)
Data: [R1, G1, B1, R2, G2, B2]

IDAT (Image Data)

  • Contains compressed pixel data
  • Uses zlib DEFLATE compression
  • Multiple IDAT chunks can exist (concatenate before decompression)
  • After decompression, data is organized into scanlines
  • Each scanline starts with a filter type byte

IEND (Image End)

  • Must be the last chunk
  • Always has length 0 (no data)
  • Marks the end of the PNG file

Examining PNG files

Using hex dump (hd)

$ hd tests/data/Large_batman_3.png | less
Output:
00000000  89 50 4e 47 0d 0a 1a 0a  00 00 00 0d 49 48 44 52  |.PNG........IHDR|
00000010  00 00 01 40 00 00 01 40  08 03 00 00 00 fa 4e 55  |...@[email protected]|
00000020  e6 00 00 01 f8 50 4c 54  45 47 70 4c 00 00 00 02  |.....PLTEGpL....|
Breaking down the first chunk:
1

Length

Bytes 8-11: 00 00 00 0d = 13 bytes
2

Type

Bytes 12-15: 49 48 44 52 = “IHDR” in ASCII
3

Data

Bytes 16-28: 13 bytes of IHDR data
  • Width: 00 00 01 40 = 320
  • Height: 00 00 01 40 = 320
  • Bit depth: 08 = 8
  • Color type: 03 = Palette
  • Compression: 00
  • Filter: 00
  • Interlace: 00
4

CRC

Bytes 29-32: fa 4e 55 e6 (checksum)

Chunk boundaries

Notice how chunks follow each other:
Signature:  89 50 4e 47 0d 0a 1a 0a
IHDR chunk: 00 00 00 0d 49 48 44 52 [13 bytes data] fa 4e 55 e6
PLTE chunk: 00 00 01 f8 50 4c 54 45 [504 bytes data] [4 byte CRC]
...
Each chunk starts immediately after the previous chunk’s CRC.

PNG specification reference

For complete details, see:

Key takeaways

Essential concepts:
  1. PNG files start with an 8-byte signature
  2. All data after the signature is organized into chunks
  3. Each chunk has length, type, data, and CRC fields
  4. All multi-byte integers are big-endian
  5. IHDR must be first, IEND must be last
  6. CRC is computed over type + data fields

Next steps

Chunk reading

Learn how to read PNG chunks

CRC validation

Implement CRC-32 checksums

Build docs developers (and LLMs) love