构建快速PNG编码器问题

时间:2014-09-09 21:17:13

标签: compression png zlib libpng encoder

我正在尝试构建一个快速的8位灰度PNG编码器。不幸的是,我一定是误解了规范的一部分。较小的图像尺寸似乎有效,但较大的图像尺寸仅在某些图像查看器中打开。这个图像(带有多个DEFLATE Blocks)给出了一个 " IDAT中的解压缩错误"我的图片查看器中的错误,但在我的浏览器中打开: sample grayscale image

此图像只有一个DEFLATE块,但也会出错:

smaller sample grayscale image

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

  1. IDAT长度

  2. " IDAT"在ascii中(字面意思是字节0x49 0x44 0x41 0x54)

  3. Zlib标头0x78 0x01

  4. 步骤4-7适用于每个deflate块,因为可能需要拆分数据:

    1. 字节0x00或0x01,具体取决于它是中间还是最后一个块。

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

    3. 此整数表示的1&。补充。

    4. 图像数据(每个扫描行以PNG中的无过滤器选项的零字节开头,后跟灰度像素数据的宽度字节)

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

    6. 所有IDAT数据的CRC

    7. 我在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));
      }
      

      注意,因为传递给函数的pixel_array在每条扫描线的开头都不包含零字节(PNG需要),所以在每次迭代开始时都会有一个额外的b + = a(和隐式a + = 0)外循环。

2 个答案:

答案 0 :(得分:2)

错误pngcheck:“zlib:inflate error = -3(data error)”。由于您的PNG脚手架结构看起来没问题,现在是时候用一个十六进制查看器对IDAT块进行低级查看了。 (我打算在完成它的时候输入它。)

标题看起来不错; IDAT长度没问题。您的zlib标志为78 01(“无/低压缩”,另请参阅What does a zlib header look like?),其中一个我自己的工具使用78 9C(“默认压缩”),但是再次,这些标志只是提供信息。

下一篇:zlib的内部块(每RFC1950)。

直接在压缩标志(RFC1950中的CMF)之后,它期望FLATE压缩数据,这是zlib支持的唯一压缩方案。这是另一个城堡 RFC:RFC1951

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

  

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)

因此,对于'not last block,uncompressed',此值可以设置为00,对于'last block,uncompressed',此值可以设置为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是     LEN的一个补充。

它们是IDAT细分中的最后4个字节。为什么小图像工作,而不是更大?因为你只有 2个字节的长度。 1 你需要将你的图像分解成不大于65,535字节的块(在我自己的PNG创建者中我似乎使用过32,768,可能是“为了安全”)。如果是最后一个块,请写出01,否则为00。然后添加两次LEN个字节,正确编码,然后加上LEN个数据字节。重复直到完成。

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


重新阅读您的问题以确认我解决了您的所有问题(并确认我正确拼写了“Adler-32”)后,我意识到您描述了所有步骤 - 除了'最后一个'指示符是01,而不是80 (稍后编辑:呃,也许你 对此有关!) - 但在此示例PNG中未显示。看看是否可以按照字母的所有步骤使其工作。

赞成“手工”这样做。这是'遵循规范'的一个很好的练习,如果你让它工作,可能值得尝试添加适当的压缩。我尽可能地避开预先制作的图书馆;我为自己的PNG编码器/解码器做的唯一允许是使用Rich Geldreich的miniz.c,因为实现正确的Flate编码/解码超出了我的范围。


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

答案 1 :(得分:1)

  
      
  1. 字节0x00或0x80,具体取决于它是中间还是最后一个块。
  2.   

0x80更改为0x01,一切顺利。

0x80显示为最后一个块的存储块。正在查看的所有内容都是低位,即零,表示中间块。所有的数据都在那个&#34;中间&#34;块,所以解码器将恢复完整的图像。然后,一些自由的PNG解码器可以忽略它在尝试解码下一个块时所获得的错误,而不是那里,然后忽略丢失的检查值(Adler-32和CRC-32)等。&#39 ;为什么它在浏览器中显示正常,即使它是一个无效的PNG文件。

您的Adler-32代码有两个问题。首先,您正在访问char数组中的数据。 char已签名,因此您的0xff字节被添加而不是为255,而是为-127。在从中提取字节值之前,您需要创建数组unsigned char或将其强制转换为该数组。

其次,你做模数操作太晚了。您必须在% 65521溢出之前执行uint32_t。否则,您无法获得算法所需的和的模数。一个简单的解决方法是在高度循环内的宽度循环之后立即执行% 65521ab。只要您可以保证宽度小于5551字节,这将起作用。 (为什么5551留给读者练习。)如果你不能保证,那么你将需要嵌入另一个循环来消耗行中的字节,直到你得到它们的5551,做模数,然后继续这条线。或者,速度较慢,只需运行一个计数器,并在达到极限时执行模数。

以下是适用于任何宽度的版本示例:

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