【问题标题】:Writing a PNG using zlib使用 zlib 编写 PNG
【发布时间】:2012-07-25 12:29:34
【问题描述】:

我正在尝试直接使用 zlib 编写 PNG。但是,输出文件不正确。在 frhed 中查看它,IDAT 块显示周期性模式和一大块零。这表明调用 zlib 时出现问题。代码如下:

#include "FrameGrabber.h"
#include "Image.h"
#include "crc.h"
#include <zlib.h>
#include <stdint.h>

#include <cstdio>
#include <cassert>



template<class T>
void assign(void* addr,T x)
{
T* p_out= (T*)addr ;
*p_out=x;
}

inline int32_t swap_bytes(int32_t x)
{
asm("bswap %1":"=r"(x):"r"(x):);
return x;
}

//Input is BGRx (convert to BGR (should be swapped to RGB later) and add a byte in the beginning)
void filterApply(const unsigned char* const* scanlines,char* buffer_png,int width,int height)
{
for(int k=0;k<height;++k)
    {
    *buffer_png=0;
    ++buffer_png;
    for(int l=0;l<width;++l)
        {
        assign(buffer_png,( (int32_t*)(scanlines[k])  )[l] );
        buffer_png+=3;
        }
    }
}

size_t compress(const char* buffer_uncompressed,char* buffer_compressed,size_t length_in)
{
z_stream stream;
memset(&stream,0,sizeof(stream));
//  15 bits=32K?
deflateInit2(&stream,6,Z_DEFLATED,15,9,Z_FILTERED);

stream.avail_in=length_in;
stream.next_in=(unsigned char*)buffer_uncompressed;
stream.avail_out=length_in;
stream.next_out =(unsigned char*)buffer_compressed;
do
    {       
    deflate(&stream, Z_FINISH);
    }
while (stream.avail_out == 0);
deflateEnd(&stream);
return stream.total_out;
}



const size_t CHUNK_BASE_SIZE=12;

void Chunk_sizeSet(char* chunk,uint32_t size)
{assign(chunk,swap_bytes(size));}

void Chunk_IDSet(char* chunk,uint32_t id)
{assign(chunk+4,swap_bytes(id));}

void Chunk_CRCSet(char* chunk,const PngCRC& crc,uint32_t size_chunk_data)
{assign(chunk+8+size_chunk_data, swap_bytes(crc(chunk+4,size_chunk_data+4)) );}



const size_t IHDR_SIZE=13;
const int IHDR_COLORTYPE_RGB=2;

void IHDR_widthSet(char* chunk,int32_t width)
{assign(chunk+8,swap_bytes(width));}

void IHDR_heightSet(char* chunk,int32_t height)
{assign(chunk+12,swap_bytes(height));}

void IHDR_bitDepthSet(char* chunk,char bd)
{chunk[16]=bd;}

void IHDR_colorTypeSet(char* chunk,char ct)
{chunk[17]=ct;}

void IHDR_compressionMethodSet(char* chunk,char cmprm)
{chunk[18]=cmprm;}

void IHDR_filterMethodSet(char* chunk,char filter)
{chunk[19]=filter;}

void IHDR_interlaceMethodSet(char* chunk,char interlace)
{chunk[20]=interlace;}



int main()
{
PngCRC crc;                              //The CRC code works
char signature[8]={137,80,78,71,13,10,26,10};

char IEND[CHUNK_BASE_SIZE];
Chunk_sizeSet(IEND,0);
Chunk_IDSet(IEND,0x49454e44);
Chunk_CRCSet(IEND,crc,0);

FrameGrabber grabber(GetDesktopWindow()); //Grab the desktop (works)
Image img(grabber);
grabber.grab();

char IHDR[CHUNK_BASE_SIZE+IHDR_SIZE];
Chunk_sizeSet(IHDR,CHUNK_BASE_SIZE+IHDR_SIZE);
Chunk_IDSet(IHDR,0x49484452);
IHDR_widthSet(IHDR,grabber.widthGet());
IHDR_heightSet(IHDR,grabber.heightGet());
IHDR_bitDepthSet(IHDR,8);
IHDR_colorTypeSet(IHDR,IHDR_COLORTYPE_RGB);
IHDR_compressionMethodSet(IHDR,0);
IHDR_filterMethodSet(IHDR,0);
IHDR_interlaceMethodSet(IHDR,0);
Chunk_CRCSet(IHDR,crc,IHDR_SIZE);   


size_t size_uncompressed=(1+3*grabber.widthGet())*grabber.heightGet();
char* img_png_uncompressed=(char*)malloc(size_uncompressed);
filterApply(img.rowsGet(),img_png_uncompressed,grabber.widthGet(),grabber.heightGet());

    //The compressed chunk should not be larger than the uncompressed one.
char* IDAT=(char*)malloc(size_uncompressed+CHUNK_BASE_SIZE);

int32_t size_idat=compress(img_png_uncompressed,IDAT+CHUNK_BASE_SIZE,size_uncompressed);
Chunk_sizeSet(IDAT,size_idat);
Chunk_IDSet(IDAT,0x49444154);
Chunk_CRCSet(IDAT,crc,size_idat);


FILE* file_out=fopen("test.png","wb");

fwrite(signature,1,sizeof(signature),file_out);
fwrite(IHDR,1,sizeof(IHDR),file_out);
fwrite(IDAT,1,size_idat,file_out);
fwrite(IEND,1,sizeof(IEND),file_out);
free(IDAT);
fclose(file_out);

return 0;
}

编辑:在 filterApply 中发现了一个错误。现在我没有得到任何大的空块,但文件仍然无效。它只是影响输入数据,所以它是正确的。

【问题讨论】:

  • 你应该只使用 libpng (libpng.org/pub/png/libpng.html)。它被编写用于有效地编码和解码 png 文件并支持所有 png 选项。它当然使用 zlib。
  • 支持所有选项会降低性能。
  • 这并没有让它变慢。 libpng 已经优化了多年,所以它可能比你一天写的要快。它只是意味着您不会使用更多链接的代码。它为您的可执行文件添加了几百 K 字节。你没有足够的内存来支持它?
  • 问题是我在写下一张图片时不能重用设置和图片缓冲区。我将编写数千张具有相同宽度、高度和位深度的图像,每次都说 png_create_write_struct 并不好玩(每秒 25 次)。自定义所需的虚拟调用(我将每一帧写入内存缓冲区而不是允许更好并行化的文件)也是一个瓶颈。

标签: png zlib


【解决方案1】:

首先,只需使用libpng,您就不必担心这些。

您设置的块大小不正确。块大小不包括长度、块 id 或 crc。关于计算的内容,您还错误地计算了 crc。而且您正在将压缩数据写入 IDAT 块中的错误位置。我会停在那里 -- 你需要更仔细地阅读PNG specification

其他cmets:

我不确定crc.h 是什么,但是zlib 已经提供了与png 兼容的crc32() 函数。您从crc.h 使用的crc() 例程可能正在计算不同的crc。即使是同一个crc,crc32()已经被zlib链接进去了,所以你也可以使用它。

您不应使用名称compress(),因为该功能已由 zlib 提供。由于您使用的是 zlib,因此使用不同的 compress() 函数会使读者感到困惑。

while (stream.avail_out == 0); 毫无意义。 deflate() 的第一次调用以avail_out != 0 完成,或者它陷入无限循环,因为再次调用 deflate() 不会改变这一点。

如果你想使用Z_FINISH,那么你需要使用deflateBound()来设置输出缓冲区的大小以保证它完成。源代码中的注释“压缩的块不应大于未压缩的块”。不保证。不可压缩的数据会导致压缩后的数据略大于未压缩的输入。

您没有检查来自deflate 函数的任何返回码!检查返回码。

【讨论】:

  • *libpng 太多了 *crc.h 包含了 png 规范建议的 crc 算法 *你读过 C++ 中的静态多态性吗? *剩下的,我需要一个简单易懂的有效压缩循环。 32K 规格呢?
  • 我不知道你在问“32K 规格怎么样?”。
  • "对于 PNG 压缩方法 0,zlib 压缩方法/标志代码必须指定方法代码 8("deflate" 压缩)和不超过 32K 的 LZ77 窗口大小。"
  • 32K 的滑动窗口大小是 deflate 格式的最大大小,是 zlib 中的默认值。 deflateInit2() 调用中的 15 指定 32K 窗口。
  • 我在分配时遇到了一些无聊的对齐问题。还有一些偏移量是错误的。
猜你喜欢
  • 2022-01-25
  • 2018-01-19
  • 1970-01-01
  • 2014-04-30
  • 2014-03-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-12-03
相关资源
最近更新 更多