我正在尝试直接使用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中发现了一个错误。现在我没有得到任何大的空块,但文件仍然无效。它只影响输入数据,因此它是正确的。
答案 0 :(得分:0)
首先,只需使用libpng,您就不必担心这一点。
您正在错误地设置块大小。块大小不包括长度,块ID或crc。你也正在计算crc错误,计算结果。而你正在将压缩数据写入IDAT块中的错误位置。我会在那里停下来 - 你需要更仔细地阅读PNG specification。
其他评论:
我不确定crc.h
是什么,但是zlib已经提供了与png兼容的crc32()
函数。您在crc()
中使用的crc.h
例程可能正在计算不同的crc。即使它是相同的crc,crc32()
已经被zlib链接了,所以你也可以使用它。
您不应该使用名称compress()
,因为zlib已经提供了该功能。由于您使用的是zlib,因此使用不同的compress()
函数会使读者感到困惑。
while (stream.avail_out == 0);
毫无意义。 deflate()
的第一次调用以avail_out != 0
完成,或者它陷入无限循环,因为再次调用deflate()不会改变它。
如果要使用Z_FINISH
,则需要使用deflateBound()
来设置输出缓冲区的大小以确保它完成。源代码中的注释“压缩块不应大于未压缩块。”不保证。不可压缩数据将导致压缩数据略大于未压缩输入。
您没有检查deflate
功能的任何返回代码!检查返回代码。