构建快速PNG编码器问题 [英] Building a fast PNG encoder issues

查看:338
本文介绍了构建快速PNG编码器问题的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想建立一个快速的8位灰度PNG编码器。不幸的是,我必须误解部分规范。较小的图像大小似乎工作,但较大的图像只会在一些图像查看器中打开。这个图像(多个DEFLATE块)给我一个
IDAT解压缩错误错误在我的图像查看器,但在我的浏览器打开:



此图片只有一个DEFLATE块,但也会出现错误:





下面我将概述我在IDAT块中放置什么,以便您可以轻松地发现任何错误(注意,图像和步骤已修改基于答案,但仍然有一个问题):



  • IDAT长度


  • IDATin ascii(字面上为0x49 0x44 0x41 0x54)


  • Zlib标头0x78 0x01


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



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


    2. 此整数表示形式的1的补码。


    3. 图像数据(每个扫描线在PNG中的无过滤器选项的零字节开头,后面是灰度像素数据的宽度字节)


    4. 所有图片数据的adler-32校验和


    5. 所有IDAT数据的CRC

      $ b

    我在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)。

    解决方案



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


    0x80 更改为<$ c $



    0x80 显示为存储的块不是最后一个块。所有正在被看的是低位,其为零,指示中间块。所有数据都在中间块中,因此解码器将恢复完整的图像。一些自由PNG解码器然后可以忽略它获得的错误,当它试图解码下一个块,这不是在那里,然后忽略丢失的检查值(Adler-32和CRC-32)等等。这就是为什么它显示即使它是一个无效的PNG文件。



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



    你正在做模运算太晚了。您必须在 uint32_t 溢出之前执行%65521 。否则你不会得到算法要求的总和的模。一个简单的解决办法是将%65521 更改为 a b 紧跟在宽度循环之后,在高度循环内。这将工作,只要你能保证宽度小于5551字节。 (为什么5551留给读者做练习。)如果你不能保证,那么你将需要嵌入另一个循环来消耗字节,直到你得到5551个,做模,然后继续线。



    下面是一个适用于任何宽度的版本示例:

    / p>

      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 a + = pixel_array [h] [w ++];
    b + = a;
    }
    a%= 65521;
    b%= 65521;
    }
    }
    return(b << 16)|一个;
    }


    I am trying to build a fast 8-bit greyscale PNG encoder. Unfortunately I must be misunderstanding part of the spec. Smaller image sizes seem to work, but the larger ones will only open in some image viewers. This image (with multiple DEFLATE Blocks) gives a "Decompression error in IDAT" error in my image viewer but opens fine in my browser:

    This image has just one DEFLATE block but also gives an error:

    Below I will outline what I put in my IDAT chunk in case you can easily spot any mistakes (note, images and steps have been modified based on answers, but there is still a problem):

    1. IDAT length

    2. "IDAT" in ascii (literally the bytes 0x49 0x44 0x41 0x54)

    3. Zlib header 0x78 0x01

    Steps 4-7 are for every deflate block, as the data may need to be broken up:

    1. The byte 0x00 or 0x01, depending on if it is a middle or the last block.

    2. Number of bytes in block (up to 2^16-1) stored as a little endian 16-bit integer

    3. The 1's complement of this integer representation.

    4. Image data (each scan-line is starts with a zero-byte for the no filter option in PNG, and is followed by width bytes of greyscale pixel data)

    5. An adler-32 checksum of all the image data

    6. A CRC of all the IDAT data

    I've tried pngcheck on linux, an it does not spot any errors. If nobody can see what is wrong, can you point me in the right direction for a debugging tool?

    My last resort is to use the libpng library to make my own decoder, and debug from there.

    Some people have suggested it may be my adler-32 function calculation:

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

    Note that because the pixel_array passed to the function does not contain the zero-byte at the beginning of each scanline (needed for PNG) there is an extra b+=a (and implicit a+=0) at the beginning of each iteration of the outer loop.

    解决方案

    1. The byte 0x00 or 0x80, depending on if it is a middle or the last block.

    Change the 0x80 to 0x01 and all will be well.

    The 0x80 is appearing as a stored block that is not the last block. All that's being looked at is the low bit, which is zero, indicating a middle block. All of the data is in that "middle" block, so a decoder will recover the full image. Some liberal PNG decoders may then ignore the errors it gets when it tries to decode the next block, which isn't there, and then ignore the missing check values (Adler-32 and CRC-32), etc. That's why it shows up ok in browsers, even though it is an invalid PNG file.

    There are two things wrong with your Adler-32 code. First, you are accessing the data from a char array. char is signed, so your 0xff bytes are being added not as 255, but rather as -127. You need to make the array unsigned char or cast it to that before extracting byte values from it.

    Second, you are doing the modulo operation too late. You must do the % 65521 before the uint32_t overflows. Otherwise you don't get the modulo of the sum as required by the algorithm. A simple fix would be to do the % 65521 to a and b right after the width loop, inside the height loop. This will work so long as you can guarantee that the width will be less than 5551 bytes. (Why 5551 is left as an exercise for the reader.) If you cannot guarantee that, then you will need to embed a another loop to consume bytes from the line until you get to 5551 of them, do the modulo, and then continue with the line. Or, a smidge slower, just run a counter and do the modulo when it gets to the limit.

    Here is an example of a version that works for any width:

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

    这篇关于构建快速PNG编码器问题的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

    查看全文
    登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆