Compression 构建快速PNG编码器存在的问题

Compression 构建快速PNG编码器存在的问题,compression,png,zlib,libpng,encoder,Compression,Png,Zlib,Libpng,Encoder,我试图建立一个快速的8位灰度PNG编码器。不幸的是,我一定是误解了规范的一部分。较小的图像大小似乎可行,但较大的图像大小只能在一些图像查看器中打开。此图像(具有多个放气块)提供了一个 “图像查看器中的“IDAT中的解压缩错误”错误,但在我的浏览器中可以正常打开: 此图像只有一个放气块,但也给出了一个错误: 下面我将概述我在IDAT块中的内容,以防您很容易发现任何错误(注意,图像和步骤已根据答案进行了修改,但仍然存在一个问题): IDAT长度 ascii格式的“IDAT”(字面上是字节0x49

我试图建立一个快速的8位灰度PNG编码器。不幸的是,我一定是误解了规范的一部分。较小的图像大小似乎可行,但较大的图像大小只能在一些图像查看器中打开。此图像(具有多个放气块)提供了一个 “图像查看器中的“IDAT中的解压缩错误”错误,但在我的浏览器中可以正常打开:

此图像只有一个放气块,但也给出了一个错误:

下面我将概述我在IDAT块中的内容,以防您很容易发现任何错误(注意,图像和步骤已根据答案进行了修改,但仍然存在一个问题):

  • IDAT长度

  • ascii格式的“IDAT”(字面上是字节0x49 0x44 0x41 0x54)

  • Zlib头0x78 0x01

  • 步骤4-7适用于每个放气块,因为可能需要分解数据:

  • 字节0x00或0x01,取决于它是中间块还是最后一块

  • 块中存储为小尾数16位整数的字节数(最多2^16-1)

  • 此整数表示形式的1的补码

  • 图像数据(对于PNG中的“无过滤器”选项,每个扫描行以零字节开始,后跟灰度像素数据的宽度字节)

  • 所有图像数据的adler-32校验和

  • 所有IDAT数据的CRC

  • 我在linux上尝试了
    pngcheck
    ,但它没有发现任何错误。如果没有人能看到问题所在,您能为我指出调试工具的正确方向吗

    我最后的办法是使用libpng库来制作我自己的解码器,并从那里进行调试

    有人建议可能是我的adler-32函数计算:

    static uint32_t adler32(uint32_t height, uint32_t width, char** pixel_array)
    {
      uint32_t a=1,b=0,w,h;
      for(h=0;h<height;h++)
        {
          b+=a;
          for(w=0;w<width;w++)
            {
              a+=pixel_array[h][w];
              b+=a;
            }
        }
      return (uint32_t)(((b%65521)*65536)|(a%65521));
    }
    
    静态uint32 adler32(uint32高度、uint32宽度、字符**像素数组)
    {
    uint32_t a=1,b=0,w,h;
    对于(h=0;h我确实在
    pngcheck
    中遇到了一个错误:“zlib:inflate error=-3(数据错误)”。由于您的PNG脚手架结构看起来不错,是时候用十六进制查看器对
    IDAT
    块进行低级查看了。(我将在处理它的同时键入此内容。)

    标题看起来不错;
    IDAT
    长度也不错。您的zlib标志是
    78 01
    (“无/低压缩”,另请参见),其中我自己的一个工具使用了
    78 9C
    (“默认压缩”),但这些标志只是信息性的

    下一步:zlib的内部块(per)

    在压缩标志(
    CMF
    在RFC1950中)之后,它需要FLATE压缩数据,这是zlib支持的唯一压缩方案。这在另一个castle RFC中:

    每个单独的压缩块前面都有一个字节:

    3.2.3.块格式的详细信息

    每个压缩数据块以3个头位开始 包含以下数据:

            first bit       BFINAL
            next 2 bits     BTYPE
    

    BFINAL设置为当且仅当这是数据的最后一个块时 设定

    BTYPE指定数据的压缩方式,如下所示:

            00 - no compression
            01 - compressed with fixed Huffman codes
            10 - compressed with dynamic Huffman codes
            11 - reserved (error)
    
    因此,对于“非最后一个块,未压缩”可将该值设置为
    00
    ,对于“最后一个块,未压缩”可将该值设置为
    01
    ,紧跟其后的是长度(2字节)及其按位倒数,参见3.2.4。非压缩块(BTYPE=00)

    3.2.4.非压缩块(BTYPE=00)

    直到下一个字节边界的任何输入位都将被忽略。 块的其余部分由以下信息组成:

              0   1   2   3   4...
            +---+---+---+---+================================+
            |  LEN  | NLEN  |... LEN bytes of literal data...|
            +---+---+---+---+================================+
    
    LEN是块中的数据字节数。NLEN是 一个人的补语

    它们是您的
    IDAT
    段中的最后4个字节。为什么小图像有效,而大图像无效?因为长度只有2个字节。1您需要将图像分成不大于65535字节的块(在我自己的PNG创建者中,我似乎使用了32768,可能是“为了安全”)。如果是最后一个数据块,则写出
    01
    ,否则写出
    00
    。然后添加两倍的
    LEN
    字节,正确编码,后跟精确的
    LEN
    数据字节。重复操作直到完成

    Adler-32校验和不是此Flate压缩数据的一部分,不应计入
    LEN
    数据块中。(但它仍然是
    IDAT
    数据块的一部分。)


    在重新阅读您的问题以验证我解决了您的所有问题(并确认我拼写的“Adler-32”正确)之后,我意识到您正确描述了所有步骤——除了“最后一个块”指示器是
    01
    ,而不是
    80
    (稍后编辑:嗯,也许您是对的!)--但是它在这个示例PNG中没有显示。看看你是否能让它按照字母顺序完成所有步骤

    这是一个很好的“遵循规范”的练习,如果你能做到这一点,尝试添加适当的压缩可能是值得的。我尽可能避免使用预先制作的库;我为自己的PNG编码器/解码器做的唯一考虑是使用Rich Geldreich,因为实现了适当的Fla编码/解码超出了我的知识范围


    1这还不是全部。浏览器对HTML错误特别宽容;似乎他们对PNG错误也同样宽容。Safari可以很好地显示您的图像,预览也是如此。但他们可能只是在共享OS X的PNG解码器,因为Photoshop拒绝该文件

  • 字节0x00或0x80,取决于它是中间块还是最后一块
  • 0x80
    更改为
    0x01
    ,一切都会好起来

    0x80
    显示为不是最后一个块的存储块。所查看的是低位,即零,表示中间块。所有数据都位于该“中间”块,因此解码器将恢复完整图像。一些自由的PNG解码器可能会在尝试解码下一个块时忽略错误,而下一个块不存在,然后
    static uint32_t adler32(uint32_t height, uint32_t width, unsigned char ** pixel_array)
    {
        uint32_t a = 1, b = 0, w, h, k;
        for (h = 0; h < height; h++)
        {
            b += a;
            w = k = 0;
            while (k < width) {
                k += 5551;
                if (k > width)
                    k = width;
                while (w < k) {
                    a += pixel_array[h][w++];
                    b += a;
                }
                a %= 65521;
                b %= 65521;
            }
        }
        return (b << 16) | a;
    }