构建快速PNG编码器问题(Building a fast PNG encoder issues)

2019-10-20 22:10发布

我想建立一个快速的8位灰度PNG编码器。 不幸的是我必须误解了规范的一部分。 较小的图像尺寸似乎工作,但较大的只会打开一些图像观众。 此图片(多DEFLATE块)给出错误“在IDAT解压缩错误”在我的图片浏览器,但打开了我的浏览器罚款:

此图像只有一个DEFLATE块也给出了一个错误:

下面我将概述我把我的IDAT块的情况下,你可以很容易地发现任何错误(基于答案笔记,图片和步骤进行了修改,但仍是一个问题):

  1. 长度IDAT

  2. “IDAT”在ASCII(字面上的字节0×44×49×41 0x54)

  3. zlib的头0x78 0×01

步骤4-7是每个放气块,因为数据可能需要被打破:

  1. 字节为0x00或0×01,这取决于它是否是一个中间或最后一个块。

  2. 字节数在块(高达2 ^ 16-1)存储为小端的16位整数

  3. 1的补这个整数表示的。

  4. 图像数据(各扫描线是与在PNG无过滤器选项一个零字节开始,并且后面跟着字节宽度灰度像素数据)

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

  6. 所有的IDAT数据的CRC

我试过pngcheck在Linux上,一个不发现任何错误。 如果没有人能看到什么是错的,你可以点我在调试工具的正确方向?

我最后的手段是使用的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 + =一个在外部的每次迭代的开始(以及隐含一个+ = 0)环。

Answer 1:

  1. 字节为0x00 0x80的还是取决于它是否是一个中间或最后一个块。

改变0x800x01和一切都会好的。

0x80是表现为一个存储块是不是最后一块。 所有这一切被看为低位,这是零,表示中间块。 所有的数据是在“中间”块,所以解码器将恢复完整图像。 然后一些自由派PNG解码器可以忽略它获得的错误时,它会尝试下一个块,这是不存在的解码,然后忽略丢失的校验值(阿德勒-32和CRC-32),等等。这就是为什么它显示了确定在浏览器中,即使它是一个无效的PNG文件。

有两件事情错了你的阿德勒32码。 首先,您是从访问数据char数组。 char签订,所以你0xff字节被添加不是 255,而是作为-127。 你需要做的阵列unsigned char或从中提取byte值之前,将其转换为这一点。

其次,你正在做的模运算为时已晚。 你必须做好% 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;
}


Answer 2:

得到一个错误pngcheck :“ZLIB:膨胀误差= -3(数据错误)”。 当你的PNG脚手架结构看起来还行,现在是时候采取了低级别的面貌迈向IDAT用十六进制查看器块。 (我会通过它工作时键入这件事。)

标题看起来正常的; IDAT长度是好的。 你zlib的标志是78 01 (“无/低压缩”,还看到什么是一个zlib的头是什么样子? ),在我自己的工具之一,使用78 9C (“默认压缩”),但话又说回来,这些标志是唯一翔实。

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

后直接压缩标志( CMF在RFC1950),预计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) 

因此这个值可以被设置为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是LEN的补数。

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

阿德勒-32校验和此Flate压缩数据的一部分,并且不应该在的块进行计数LEN数据。 (它仍然是部分IDAT块,虽然。)


重读你的问题来验证我解决了所有的问题,你(和我确认拼写“阿德勒-32”正确)后,我意识到你描述的正确步骤都- 除了“最后一块”指标是01 ,不80 (后来编辑:嗯,也许你对这种事的!) -但它不会在此示例PNG 显示 。 看看你能得到它下面的所有字母的步骤工作。

荣誉对“手”这样做。 这是一个很好的锻炼“以下规格”中,如果你得到这个工作,它可能是值得尝试和加适量压缩。 我顺预制的库尽可能; 我为我自己的PNG编码器/解码器做出的唯一津贴是利用丰富的Geldreich的miniz.c ,因为实施正确Flate编码/解码我是无法理解。


1这不是故事的全部。 浏览器在HTML错误特别的宽容; 看来他们是PNG错误的宽容为好。 Safari浏览器会显示你的形象蛮好的,所以不预览。 但他们可能只是全部是共享OS X的PNG解码器,因为Photoshop的拒绝文件。



文章来源: Building a fast PNG encoder issues