我正在尝试构建一个快速的8位灰度PNG编码器。不幸的是,我一定是误解了规范的一部分。较小的图像尺寸似乎有效,但较大的图像尺寸仅在某些图像查看器中打开。这个图像(带有多个DEFLATE Blocks)给出了一个 " IDAT中的解压缩错误"我的图片查看器中的错误,但在我的浏览器中打开:
此图像只有一个DEFLATE块,但也会出错:
下面我将概述我放入IDAT块中的内容,以防您轻易发现任何错误(注意,图片和步骤已根据答案进行了修改,但仍存在问题):
IDAT长度
" IDAT"在ascii中(字面意思是字节0x49 0x44 0x41 0x54)
Zlib标头0x78 0x01
步骤4-7适用于每个deflate块,因为可能需要拆分数据:
字节0x00或0x01,具体取决于它是中间还是最后一个块。
块中的字节数(最多2 ^ 16-1)存储为小端16位整数
此整数表示的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));
}
注意,因为传递给函数的pixel_array在每条扫描线的开头都不包含零字节(PNG需要),所以在每次迭代开始时都会有一个额外的b + = a(和隐式a + = 0)外循环。
答案 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”)后,我意识到您描述了所有步骤 - 除了'最后一个'指示符是(稍后编辑:呃,也许你 对此有关!) - 但在此示例PNG中未显示。看看是否可以按照字母的所有步骤使其工作。01
,而不是80
赞成“手工”这样做。这是'遵循规范'的一个很好的练习,如果你让它工作,可能值得尝试添加适当的压缩。我尽可能地避开预先制作的图书馆;我为自己的PNG编码器/解码器做的唯一允许是使用Rich Geldreich的miniz.c
,因为实现正确的Flate编码/解码超出了我的范围。
1 这不是全部故事。浏览器在HTML错误中特别宽容;似乎他们对PNG错误也是宽容的。 Safari可以很好地显示您的图像,预览也是如此。但他们可能只是共享OS X的PNG解码器,因为Photoshop 拒绝该文件。
答案 1 :(得分:1)
- 字节0x00或0x80,具体取决于它是中间还是最后一个块。
醇>
将0x80
更改为0x01
,一切顺利。
0x80
显示为不最后一个块的存储块。正在查看的所有内容都是低位,即零,表示中间块。所有的数据都在那个&#34;中间&#34;块,所以解码器将恢复完整的图像。然后,一些自由的PNG解码器可以忽略它在尝试解码下一个块时所获得的错误,而不是那里,然后忽略丢失的检查值(Adler-32和CRC-32)等。&#39 ;为什么它在浏览器中显示正常,即使它是一个无效的PNG文件。
您的Adler-32代码有两个问题。首先,您正在访问char
数组中的数据。 char
已签名,因此您的0xff
字节被添加而不是为255,而是为-127。在从中提取字节值之前,您需要创建数组unsigned char
或将其强制转换为该数组。
其次,你做模数操作太晚了。您必须在% 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;
}