PNG CRC 是如何精确计算的?

Posted

技术标签:

【中文标题】PNG CRC 是如何精确计算的?【英文标题】:How is PNG CRC calculated exactly? 【发布时间】:2014-07-27 18:02:43 【问题描述】:

在过去的 4 个小时里,我一直在研究 CRC 算法。我很确定我已经掌握了窍门。

我正在尝试编写一个 png 编码器,我不希望使用外部库进行 CRC 计算,也不希望使用 png 编码本身。

我的程序已经能够获得与教程中的示例相同的 CRC。喜欢Wikipedia:

使用与示例中相同的多项式和消息,我能够在两种情况下产生相同的结果。对于其他几个示例,我也能做到这一点。

但是,我似乎无法正确计算 png 文件的 CRC。我通过在绘画中创建一个空白的、一个像素大的 .png 文件来测试这一点,并使用它的 CRC 作为比较。我从 png 的 IDAT 块 (which the CRC is calculated from) 中复制了数据(和块名称),并使用 png 规范中提供的多项式计算了它的 CRC。

png specification 中提供的多项式如下:

x32 + x26 + x23 + x22 + x16 + x12 + x11 + x10 + x8 + x7 + x5 + x4 + x2 + x + 1

应该翻译成:

1 00000100 11000001 00011101 10110111

使用该多项式,我尝试获取以下数据的 CRC:

01001001 01000100 01000001 01010100
00011000 01010111 01100011 11101000
11101100 11101100 00000100 00000000
00000011 00111010 00000001 10011100

这是我得到的:

01011111 11000101 01100001 01101000 (MSB First)
10111011 00010011 00101010 11001100 (LSB First)

这是实际的 CRC:

11111010 00010110 10110110 11110111

我不确定如何解决这个问题,但我猜我在做这部分 from the specification 错误:

在 PNG 中,32 位 CRC 被初始化为全 1,然后每个字节的数据从最低有效位 (1) 到最高有效位 (128) 进行处理。处理完所有数据字节后,将反转 CRC(取其补码)。该值首先传输(存储在数据流中)MSB。为了分字节排序,将32位CRC的最低位定义为x31项的系数。

我不完全确定我能理解所有这些。

另外,这是我用来获取 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;
    

那么,我该如何解决这个问题以符合 PNG 规范?

【问题讨论】:

您需要阅读this tutorial。首先,这不是审查代码的地方。这是错误的。其次,您正在接近如何以完全错误的方式计算 CRC。您应该使用异或操作,而不是&amp;&amp; || (! &amp;&amp; !),并且使用一个操作超过多个位。第三,即使您的代码工作正常,您也不会通过反转它来对 CRC 进行预处理和后处理。 我知道这不是审查我的代码的地方,但是,我认为如果我包含代码,它可能会对我的问题有所帮助。我还没有通过一个操作来处​​理多个位,因为我想在开始优化我的代码之前让最基本的工作变得更快。我想了解代码,而不仅仅是从互联网上的某个地方复制粘贴。另外,我想我已经很清楚我的代码正在运行,或者至少正在处理我在指南中找到的示例,您链接的教程就是其中之一。 您的代码不起作用,因为所提供的结果均不符合所提供数据的预期“纯”CRC-32。 “纯”是指没有 CRC 的预处理和后处理。 @MarcusJ “反转 CRC 的每一位”意味着反转 CRC 的每一位。 CRC 是计算的结果。它不是数据,也不是多项式。 这里 MSB 是最高有效位。 CRC 始终与位有关。它们与字节的存在无关。 【参考方案1】:

您可以在public domain code 中找到 CRC 计算(以及一般的 PNG 编码)的完整实现:​​

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;

1https://web.archive.org/web/20150825201508/http://upokecenter.dreamhosters.com/articles/png-image-encoder-in-c/

【讨论】:

可以使用IEND 块进行测试,该块应该始终生成字节0xae 0x42 0x60 0x82,因为它永远不会更改其名称,也没有任何有效负载。查看您现有的文件:它们都应该以这些字节结尾。

以上是关于PNG CRC 是如何精确计算的?的主要内容,如果未能解决你的问题,请参考以下文章

如何计算字符串的 CRC32

如何计算modbus-rtu的crc校验码

如何计算传入 32 位数据的 CRC8

如何从 linux bash 上的字符串计算 crc32 校验和

如何计算 string 的 crc32 值 ?

问一下计算机大神。crc16与crc32有啥区别