How is PNG CRC calculated exactly?
You can find a complete implementation of the CRC calculation (and PNG encoding in general) in this public domain code:
static uint[] crcTable;
// Stores a running CRC (initialized with the CRC of "IDAT" string). When
// you write this to the PNG, write as a big-endian value
static uint idatCrc = Crc32(new byte[] { (byte)'I', (byte)'D', (byte)'A', (byte)'T' }, 0, 4, 0);
// Call this function with the compressed image bytes,
// passing in idatCrc as the last parameter
private static uint Crc32(byte[] stream, int offset, int length, uint crc)
{
uint c;
if(crcTable==null){
crcTable=new uint[256];
for(uint n=0;n<=255;n++){
c = n;
for(var k=0;k<=7;k++){
if((c & 1) == 1)
c = 0xEDB88320^((c>>1)&0x7FFFFFFF);
else
c = ((c>>1)&0x7FFFFFFF);
}
crcTable[n] = c;
}
}
c = crc^0xffffffff;
var endOffset=offset+length;
for(var i=offset;i<endOffset;i++){
c = crcTable[(c^stream[i]) & 255]^((c>>8)&0xFFFFFF);
}
return c^0xffffffff;
}
1 https://web.archive.org/web/20150825201508/http://upokecenter.dreamhosters.com/articles/png-image-encoder-in-c/
![MythicManiac](https://i.stack.imgur.com/9BkPR.png?s=256&g=1)
MythicManiac
Updated on August 03, 2022Comments
-
MythicManiac almost 2 years
For the past 4 hours I've been studying the CRC algorithm. I'm pretty sure I got the hang of it already.
I'm trying to write a png encoder, and I don't wish to use external libraries for the CRC calculation, nor for the png encoding itself.
My program has been able to get the same CRC's as the examples on tutorials. Like on Wikipedia:
Using the same polynomial and message as in the example, I was able to produce the same result in both of the cases. I was able to do this for several other examples as well.
However, I can't seem to properly calculate the CRC of png files. I tested this by creating a blank, one pixel big .png file in paint, and using it's CRC as a comparision. I copied the data (and chunk name) from the IDAT chunk of the png (which the CRC is calculated from), and calculated it's CRC using the polynomial provided in the png specification.
The polynomial provided in the png specification is the following:
x32 + x26 + x23 + x22 + x16 + x12 + x11 + x10 + x8 + x7 + x5 + x4 + x2 + x + 1
Which should translate to:
1 00000100 11000001 00011101 10110111
Using that polynomial, I tried to get the CRC of the following data:
01001001 01000100 01000001 01010100 00011000 01010111 01100011 11101000 11101100 11101100 00000100 00000000 00000011 00111010 00000001 10011100
This is what I get:
01011111 11000101 01100001 01101000 (MSB First) 10111011 00010011 00101010 11001100 (LSB First)
This is what is the actual CRC:
11111010 00010110 10110110 11110111
I'm not exactly sure how to fix this, but my guess would be I'm doing this part from the specification wrong:
In PNG, the 32-bit CRC is initialized to all 1's, and then the data from each byte is processed from the least significant bit (1) to the most significant bit (128). After all the data bytes are processed, the CRC is inverted (its ones complement is taken). This value is transmitted (stored in the datastream) MSB first. For the purpose of separating into bytes and ordering, the least significant bit of the 32-bit CRC is defined to be the coefficient of the x31 term.
I'm not completely sure I can understand all of that.
Also, here is the code I use to get the CRC:
public BitArray GetCRC(BitArray data) { // Prepare the divident; Append the proper amount of zeros to the end BitArray divident = new BitArray(data.Length + polynom.Length - 1); for (int i = 0; i < divident.Length; i++) { if (i < data.Length) { divident[i] = data[i]; } else { divident[i] = false; } } // Calculate CRC for (int i = 0; i < divident.Length - polynom.Length + 1; i++) { if (divident[i] && polynom[0]) { for (int j = 0; j < polynom.Length; j++) { if ((divident[i + j] && polynom[j]) || (!divident[i + j] && !polynom[j])) { divident[i + j] = false; } else { divident[i + j] = true; } } } } // Strip the CRC off the divident BitArray crc = new BitArray(polynom.Length - 1); for (int i = data.Length, j = 0; i < divident.Length; i++, j++) { crc[j] = divident[i]; } return crc; }
So, how do I fix this to match the PNG specification?
-
AmigoJack about 3 yearsOne can test this with the
IEND
chunk that should always produce the bytes0xae 0x42 0x60 0x82
, since it never changes its name, nor does it ever have any payload. Look at your existing files: they should all end with those bytes.