How to Calculate the 32-bit CRC for PNG Chunks in C
When writing a custom PNG parser or editor in C, one of the most common hurdles developers face is getting the CRC (Cyclic Redundancy Check) calculation to match the official PNG specification. If your custom CRC function is returning values that don't match the expected chunk CRCs, you are not alone.
In this article, we will break down why your naive CRC-32 implementation isn't matching the PNG standard and provide a clean, working C solution.
The Two Pitfalls in Your Naive CRC Implementation
There are two primary reasons why a manual CRC-32 calculation for PNG chunks fails: C operator precedence and bit reflection.
1. The C Operator Precedence Bug
In C, the equality operator (==) has a higher precedence than the bitwise XOR operator (^). In your original code, the condition:
if (currentBit ^ topCrc == 1)
is evaluated by the compiler as:
if (currentBit ^ (topCrc == 1))
This completely breaks the logic. To fix this, you must wrap the XOR operation in parentheses:
if ((currentBit ^ topCrc) == 1)
2. PNG Uses "Reflected" (LSB-First) CRC-32
The standard CRC-32 algorithm used by PNG (defined in ISO 3309 and ITU-T V.42) is a reflected algorithm. This means:
- Data bytes are processed least-significant bit (LSB) first.
- The CRC register is shifted to the right rather than the left.
- The polynomial is represented in its reversed form:
0xEDB88320(instead of the normal0x04C11DB7).
While it is technically possible to calculate a reflected CRC using a left-shifting (MSB-first) approach, it requires manually reversing the bits of every input byte and then reversing the bits of the final output. It is much simpler and more elegant to write a right-shifting algorithm.
The Elegant, Correct Bit-by-Bit Solution
By switching to a right-shifting (reflected) implementation and using the reversed polynomial 0xEDB88320, your code becomes simpler, faster, and matches the PNG specification perfectly.
Here is the corrected and fully functional C implementation:
#include <stdio.h>
#include <stdint.h>
#include <stddef.h>
typedef unsigned char Byte;
// The reversed polynomial for standard reflected CRC-32
#define CRCPOLY_REVERSED 0xEDB88320
uint32_t calculate_CRC(Byte data[], size_t length) {
uint32_t crc = 0xFFFFFFFF;
// PNG chunk CRC is calculated over the Chunk Type (4 bytes)
// and the Chunk Data. We skip the 4-byte length prefix.
size_t crc_length = length - 8;
for (size_t i = 0; i < crc_length; ++i) {
Byte currentByte = data[i + 4]; // Skip 4-byte length
crc ^= currentByte;
for (int bit = 0; bit < 8; ++bit) {
if (crc & 1) {
crc = (crc >> 1) ^ CRCPOLY_REVERSED;
} else {
crc >>= 1;
}
}
}
return crc ^ 0xFFFFFFFF;
}
int main(void) {
// IEND chunk: 4 bytes length (0), 4 bytes "IEND", 4 bytes CRC
Byte IEND[12] = {
0x00, 0x00, 0x00, 0x00,
0x49, 0x45, 0x4E, 0x44, // "IEND"
0xAE, 0x42, 0x60, 0x82 // Expected CRC
};
uint32_t crc = calculate_CRC(IEND, sizeof(IEND));
// Print CRC in Big-Endian (Network Byte Order) as stored in PNG files
printf("Calculated CRC: %02X %02X %02X %02X\n",
(crc >> 24) & 0xFF,
(crc >> 16) & 0xFF,
(crc >> 8) & 0xFF,
crc & 0xFF);
printf("Expected CRC: AE 42 60 82\n");
return 0;
}
A Note on Endianness
In your original code, you used a union to print the bytes of the CRC. Be careful with this approach: PNG files store integers in Big-Endian (network byte order), whereas most modern consumer hardware (like x86/x64 and ARM) runs on Little-Endian architecture. Relying on a union to inspect bytes will output them in reverse order on Little-Endian machines. Using bit-shifting (as shown in the printf statement above) ensures consistent big-endian output regardless of system architecture.
Next Steps: Optimizing with a Lookup Table
While the bit-by-bit approach is excellent for understanding how the algorithm works, it is computationally expensive for large PNG files because it processes data bit-by-bit.
Once you verify that your naive implementation is working, you should transition to a table-driven CRC-32. This processes data byte-by-byte (8 times faster) by precomputing the CRC values for all 256 possible byte values. The official W3C PNG Specification provides a standard, copy-pasteable implementation of this table-driven approach.