Search code examples
c#pngcrccrc32

How is PNG CRC calculated exactly?


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: enter image description here

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?


Solution

  • 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;
    }