我想建立一个快速的8位灰度PNG编码器。 不幸的是我必须误解了规范的一部分。 较小的图像尺寸似乎工作,但较大的只会打开一些图像观众。 此图片(多DEFLATE块)给出错误“在IDAT解压缩错误”在我的图片浏览器,但打开了我的浏览器罚款:
此图像只有一个DEFLATE块也给出了一个错误:
下面我将概述我把我的IDAT块的情况下,你可以很容易地发现任何错误(基于答案笔记,图片和步骤进行了修改,但仍是一个问题):
长度IDAT
“IDAT”在ASCII(字面上的字节0×44×49×41 0x54)
zlib的头0x78 0×01
步骤4-7是每个放气块,因为数据可能需要被打破:
字节为0x00或0×01,这取决于它是否是一个中间或最后一个块。
字节数在块(高达2 ^ 16-1)存储为小端的16位整数
1的补这个整数表示的。
图像数据(各扫描线是与在PNG无过滤器选项一个零字节开始,并且后面跟着字节宽度灰度像素数据)
所有的图像数据的ADLER-32校验和
所有的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)环。
- 字节为0x00 0x80的还是取决于它是否是一个中间或最后一个块。
改变0x80
以0x01
和一切都会好的。
该0x80
是表现为一个存储块是不是最后一块。 所有这一切被看为低位,这是零,表示中间块。 所有的数据是在“中间”块,所以解码器将恢复完整图像。 然后一些自由派PNG解码器可以忽略它获得的错误时,它会尝试下一个块,这是不存在的解码,然后忽略丢失的校验值(阿德勒-32和CRC-32),等等。这就是为什么它显示了确定在浏览器中,即使它是一个无效的PNG文件。
有两件事情错了你的阿德勒32码。 首先,您是从访问数据char
数组。 char
签订,所以你0xff
字节被添加不是 255,而是作为-127。 你需要做的阵列unsigned char
或从中提取byte值之前,将其转换为这一点。
其次,你正在做的模运算为时已晚。 你必须做好% 65521
的前uint32_t
溢出。 否则,所要求的算法,你没有得到的总和的模。 一个简单的修正将是做% 65521
到a
和b
右宽度循环后,该高度循环内。 这将工作,只要你能保证宽度将小于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;
}
我得到一个错误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的拒绝文件。