借助它,你可以轻松读写PNG文件的每一行像素。因为PNG文件是经过压缩而且格式复杂的图形文件(有的PNG文件甚至像GIF文件一样带动画效果,但是不像jpg那样是有损压缩,png是无损压缩的),而且PNG可以是带透明通道的真彩色图像、不带透明通道的真彩色图像、索引颜色、灰度颜色等各种格式,如果大家都自己写程序分析PNG文件就会显得很麻烦、很累。因此,通过使用libpng你就能直接使用现成的函数、程序来读写PNG文件了。
PNG图像格式使用Big-Endian顺序存储数据。
PNG的文件结构
对于一个
PNG文件来说,其文件头总是由位固定的字节来描述的:
十进制数 |
137 80 78 71 13 10 26 10 |
十六进制数 |
89 50 4E 47 0D 0A 1A 0A |
其中第一个字节
0x89超出了ASCII字符的范围,这是为了避免某些软件将PNG文件当做文本文件来处理。文件中剩余的部分由3个以上的PNG的数据块(Chunk)按照特定的顺序组成,因此,一个标准的PNG文件结构应该如下:
PNG文件标志 |
PNG数据块 |
…… |
PNG数据块 |
所以一个最小的PNG文件由一个PNG文件标志和三个PNG数据块组成。
是比较内存区域buf1和buf2的前count个字节,相等返回0,不等返回非0.
紧跟在PNG文件标志后面的数据是数据块(chunks),数据块(chunks)分为两类:关键数据块(critical chunks)和辅助数据块(ancillary chunks)。
关键数据块(critical chunk)在PNG文件中是必须有的,而辅助数据块(ancillary chunks)是可选的。
关键数据块(critical chunks)由4部分组成:文件头数据块(IHDR)、调色板数据块(PLTE)、图像数据块(IDAT)和图像结束数据(IEND),其中调色板数据块(PLTE)根据图像的色深可选。
数据块名称 |
允许多 个数据块 |
位 置 |
文件头数据块(IHDR) |
不允许 |
第一个数据块 |
调色板数据块(PLTE) |
不允许 |
第二个数据块,可选 |
图像数据块(IDAT) |
允许 |
如果有调色板数据块(PLTE),则是第三个数据块,如果没有调色板数据块(PLTE),则时第二个数据块。如果有多个图像数据块,则必须按图像数据连续存储 |
图像结束数据(IEND) |
不允许 |
最后一个数据块 |
辅助数据块(ancillary chunks)一共有14个,这些辅助数据块包含了很多信息,辅助数据块不是必须包含的。
数据块名称 |
允许多个 数据块 |
位 置 |
样本有效位数据块( |
不允许 |
在PLTE和IDAT之前 |
标准RPG颜色( |
不允许 |
在PLTE之后IDAT之前如
果有 |
背景颜色数据块( |
不允许 |
在PLTE之后IDAT之前 |
图像直方图数据块( |
不允许 |
在PLTE之后IDAT之前 |
图像透明数据块( |
不允许 |
在PLTE之后IDAT之前 |
物理像素尺寸数据块( |
不允许 |
在IDAT之前 |
建议调色板( |
允许 |
在IDAT之前 |
图像最后修改时间数据块( |
不允许 |
无限制 |
国际文本数据( |
允许 |
无限制 |
文本信息数据块( |
允许 |
无限制 |
压缩文本数据块( |
允许 |
无限制 |
PNG中每个数据块的格式由4个部分组成:
|
|
|
Length (长度) |
4字节 |
指定数据块中数据域的长度,其长度不超过(2 |
Chunk Type Code (数据块类型码) |
4字节 |
数据块类型码由ASCII字母(A-Z和a-z)组成 |
Chunk Data (数据块数据) |
可变长度 |
存储按照Chunk Type Code指定的数据 |
CRC (循环冗余检测) |
4字节 |
存储用来检测是否有错误的循环冗余码 |
CRC(cyclic redundancy check)域中的值是对Chunk Type Code域和Chunk Data域中的数据进行计算得到的。CRC具体算法定义在ISO 3309和ITU-T V.42中,其值按下面的CRC码生成多项式进行计算:
x
32
+x
26
+x
23
+x
22
+x
16
+x
12
+x
11
+x
10
+x
8
+x
7
+x
5
+x
4
+x
2
+x+1
下面,我们依次来了解一下各个关键数据块的结构吧。
IHDR
文件头数据块
IHDR(header chunk):它包含有PNG文件中存储的图像数据的基本信息,并要作为第一个数据块出现在PNG数据流中,而且一个PNG数据流中只能有一个文件头数据块。
文件头数据块由
13 + 12(长度信息、数据类型码、循环检测)字节组成,它的格式如下表所示。
|
|
|
Width |
4 bytes |
图像宽度,以像素为单位 |
Height |
4 bytes |
图像高度,以像素为单位 |
Bit depth |
1 byte |
图像深度: |
ColorType |
1 byte |
颜色类型: |
Compression method |
1 byte |
压缩方法(LZ77派生算法) |
Filter method |
1 byte |
滤波器方法 |
Interlace method |
1 byte |
隔行扫描方法: |
PLTE
调色板数据块
PLTE(palette chunk)包含有与索引彩色图像(indexed-color image)相关的彩色变换数据,它仅与索引彩色图像有关,而且要放在图像数据块(image data chunk)之前。
PLTE数据块是定义图像的调色板信息,PLTE可以包含1~256个调色板信息,每一个调色板信息由3个字节组成:
字段名 |
大小(单 位:字节) |
描 述 |
btRed |
1 |
红色颜色值 |
btGreen |
1 |
绿色颜色值 |
btBlue |
1 |
蓝色颜色值 |
因此,调色板的长度应该是
3的倍数,否则,这将是一个非法的调色板。
对于索引图像,调色板信息是必须的,调色板的颜色索引从
0开始编号,然后是1、2……,调色板的颜色数不能超过色深中规定的颜色数(如图像色深为4的时候,调色板中的颜色数不可以超过2^4=16),否则,这将导致PNG图像不合法。
真彩色图像和带
α通道数据的真彩色图像也可以有调色板数据块,目的是便于非真彩色显示程序用它来量化图像数据,从而显示该图像。
IDAT
图像数据块
IDAT(image data chunk):它存储实际的数据,在数据流中可包含多个连续顺序的图像数据块。
IDAT存放着图像真正的数据信息,因此,如果能够了解IDAT的结构,我们就可以很方便的生成PNG图像。
图像数据块中的图像数据可能是经过变种的
LZ77
压缩编码
DEFLATE
压缩的,关于
DEFLATE
详细介绍可以参考《
DEFLATE Compressed Data Format Specification version 1.3
》,网址:
http://www.ietf.org/rfc/rfc1951.txt
。
IEND
图像结束数据
IEND(image trailer chunk):它用来标记PNG文件或者数据流已经结束,并且必须要放在文件的尾部。
如果我们仔细观察
PNG文件,我们会发现,文件的结尾12个字符看起来总应该是这样的:
00 00 00 00 49 45 4E 44 AE 42 60 82
不难明白,由于数据块结构的定义,
IEND数据块的长度总是0(00 00 00 00,除非人为加入信息),数据标识总是IEND(49 45 4E 44),因此,CRC码也总是AE 42 60 82。
PNG的辅助数据块(ancillary chunks)一共有14个,可以分为5类,上面已有,由于时间关系不能将全部辅助数据块(ancillary chunks)的详细结构进行说明,如果读者有兴趣请参考
http://www.w3.org/TR/REC-png.html
。
使用libpng的编程方法:
1.判断文件是否为png文件。
2.初始化libpng数据结
png_info png_structp png_ptr;
png_infop info_ptr;
3.设置错误的返回点。
4.设置数据源
5.读取png数据
6.对数据进行处理。
7.释放libpng内存。
下面为一段测试代码:png_to_bmp bmp_to_png
#include <stdio.h>
#include <png.h>
#include <stdlib.h>
#include <malloc.h>
#include <string.h>
typedef unsigned char BYTE;
typedef unsigned short WORD;
typedef unsigned long DWORD;
#pragma pack(1)
typedef struct tagBITMAPFILEHEADER{
WORD bfType; // the flag of bmp, value is “BM”
DWORD bfSize; // size BMP file ,unit is bytes
DWORD bfReserved; // 0
DWORD bfOffBits; // must be 54
}BITMAPFILEHEADER;
typedef struct tagBITMAPINFOHEADER{
DWORD biSize; // must be 0x28
DWORD biWidth; //
DWORD biHeight; //
WORD biPlanes; // must be 1
WORD biBitCount; //
DWORD biCompression; //
DWORD biSizeImage; //
DWORD biXPelsPerMeter; //
DWORD biYPelsPerMeter; //
DWORD biClrUsed; //
DWORD biClrImportant; //
}BITMAPINFOHEADER;
#pragma pack(4)
/******************************图片数据*********************************/
typedef struct _pic_data pic_data;
struct _pic_data
{
int width, height; /* 尺寸 */
int bit_depth; /* 位深 */
int flag; /* 一个标志,表示是否有alpha通道 */
unsigned char *rgba; /* 图片数组 */
};
/**********************************************************************/
/*
写入bmp数据为头
fp:文件指针
width:图像的宽度
height:图像的高度
*/
int write_bmp_header(FILE *fp,int width, int height)
{
BITMAPFILEHEADER bf;
BITMAPINFOHEADER bi;
//Set BITMAPINFOHEADER
bi.biSize = 40;
bi.biWidth = width;
bi.biHeight = height;
bi.biPlanes = 1;
bi.biBitCount = 24;
bi.biCompression = 0;
bi.biSizeImage = height*width*3;
bi.biXPelsPerMeter = 0;
bi.biYPelsPerMeter = 0;
bi.biClrUsed = 0;
bi.biClrImportant = 0;
//Set BITMAPFILEHEADER
bf.bfType = 0x4d42;
bf.bfSize = 54 + bi.biSizeImage;
bf.bfReserved = 0;
bf.bfOffBits = 54;
fwrite(&bf, 14, 1, fp); //先写54字节的头部数据
fwrite(&bi, 40, 1, fp);
return 0;
}
/*
将rgb数组数据写入bmp文件中
filename:bmp文件名
out:存在rgb数据的数组 格式为bgr bgr …
*/
int write_bmp(const char *filename, pic_data *out)
{
FILE *fp;
int width, height;
int i, j, count=0, linesize=0;
unsigned char * lineData = NULL;
width = out->width;
height = out->height;
linesize = width*3;
count = height /2;
if((fp = fopen(filename, “wb+”)) == NULL){
perror(“fopen bmp error”);
return -1;
}
//write_bmp_header(fp, width, height);
lineData = (unsigned char*)malloc(linesize);
if(lineData == NULL){
perror(“malloc lineData error”);
return -1;
}
for(i=0; i<count; i++){
memcpy(lineData, out->rgba + (i*linesize), linesize);
memcpy(out->rgba + (i*linesize), out->rgba+ (height – i -1)*linesize, linesize);
memcpy(out->rgba+ (height – i -1)*linesize, lineData, linesize);
}
fwrite(out->rgba, width*height*3, 1, fp); //图片旋转
free(lineData);
fclose(fp);
return 0;
}
#define PNG_BYTES_TO_CHECK 4
#define HAVE_ALPHA 1
#define NO_ALPHA 0
int check_if_png(char *file_name, FILE **fp)
{
unsigned char buf[PNG_BYTES_TO_CHECK];
/* Open the prospective PNG file. */
if ((*fp = fopen(file_name, “rb”)) == NULL)
return 0;
/* Read in some of the signature bytes */
if (fread(buf, 1, PNG_BYTES_TO_CHECK, *fp) != PNG_BYTES_TO_CHECK)
return 0;
/* Compare the first PNG_BYTES_TO_CHECK bytes of the signature.
Return nonzero (true) if they match */
printf(“buf0 =%x buf1=%x buf2=%x buf3=%x\n”,buf[0], buf[1], buf[2], buf[3]);
return(!png_sig_cmp(buf, (png_size_t)0, PNG_BYTES_TO_CHECK)); //0错误 非0正确
}
/*
获取png的数据
filepath:文件名
out:存放数据的rgb数组,格式bgr bgr …
*/
int detect_png(char *filepath, pic_data *out)
/* 用于解码png图片 */
{
FILE *pic_fp;
int ret = -1;
/* 初始化各种结构 */
png_structp png_ptr;
png_infop info_ptr;
/*检测是否为png文件*/
if((ret = check_if_png(filepath,&pic_fp)) ==0)
{
printf(“not png file”);
return -1;
}
png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0);
info_ptr = png_create_info_struct(png_ptr);
setjmp(png_jmpbuf(png_ptr)); // 这句很重要
rewind(pic_fp);
/*开始读文件*/
png_init_io(png_ptr, pic_fp); //文件指针赋值
//png_ptr->io_ptr = (png_voidp)fp;
// png_voidp io_ptr; /* ptr to application struct for I/O functions */
// 读文件了
png_read_png(png_ptr, info_ptr, PNG_TRANSFORM_EXPAND, 0);
//#define PNG_TRANSFORM_EXPAND 0x0010 /* read only */
int color_type,channels; //typedef unsigned char png_byte;
/*获取宽度,高度,位深,颜色类型*/
channels = png_get_channels(png_ptr, info_ptr); /*获取通道数*/
out->bit_depth = png_get_bit_depth(png_ptr, info_ptr); /* 获取位深 */
color_type = png_get_color_type(png_ptr, info_ptr); /*颜色类型*/
int i,j;
int size, pos = 0;
int temp;
/* row_pointers里边就是rgba数据 */
png_bytep* row_pointers; //二级指针
row_pointers = png_get_rows(png_ptr, info_ptr); //获取二维数组的数据
out->width = png_get_image_width(png_ptr, info_ptr);
out->height = png_get_image_height(png_ptr, info_ptr);
printf(“channels=%d depth=%d color_type=%d width=%d height=%d\n”,channels, out->bit_depth,color_type,out->width,out->height);
size = out->width * out->height; /* 计算图片的总像素点数量 */
if(channels == 4 || color_type == PNG_COLOR_TYPE_RGB_ALPHA) //6
{/*如果是RGB+alpha通道,或者RGB+其它字节*/
size *= (3*sizeof(unsigned char)); /* 每个像素点占4个字节内存 */
out->flag = HAVE_ALPHA; /* 标记 */
out->rgba = (unsigned char*) malloc(size);
if(out->rgba == NULL)
{/* 如果分配内存失败 */
fclose(pic_fp);
puts(“错误(png):无法分配足够的内存供存储数据!”);
return 1;
}
temp = (4 * out->width);/* 每行有4 * out->width个字节 */
for(i = 0; i < out->height; i++)
{
for(j = 0; j < temp; j += 4)
{/* 一个字节一个字节的赋值 */
// out->rgba[0][pos] = row_pointers[i][j]; // red
// out->rgba[1][pos] = row_pointers[i][j+1]; // green
// out->rgba[2][pos] = row_pointers[i][j+2]; // blue
// out->rgba[3][pos] = row_pointers[i][j+3]; // alpha
// ++pos;
//out->rgba[pos++] = row_pointers[i][j+3]; 忽略
//pos++;
char ch = row_pointers[i][j+3];
out->rgba[pos++] = row_pointers[i][j+2];
out->rgba[pos++] = row_pointers[i][j+1];
out->rgba[pos++] = row_pointers[i][j+0];
}
}
}
else if(channels == 3 || color_type == PNG_COLOR_TYPE_RGB)//2
{/* 如果是RGB通道 */
size *= (3*sizeof(unsigned char)); /* 每个像素点占3个字节内存 */
out->flag = NO_ALPHA; /* 标记 */
out->rgba = (unsigned char*) malloc(size);
printf(“malloc\n”);
if(out->rgba == NULL)
{/* 如果分配内存失败 */
fclose(pic_fp);
puts(“错误(png):无法分配足够的内存供存储数据!”);
return 1;
}
temp = (3 * out->width);/* 每行有3 * out->width个字节 */
for(i = 0; i < out->height; i++)
{
for(j = 0; j < temp; j += 3)
{/* 一个字节一个字节的赋值 */
// out->rgba[0][pos] = row_pointers[i][j]; // red
//out->rgba[1][pos] = row_pointers[i][j+1]; // green
// out->rgba[2][pos] = row_pointers[i][j+2]; // blue
// ++pos;
out->rgba[pos++] = row_pointers[i][j+2];
out->rgba[pos++] = row_pointers[i][j+1];
out->rgba[pos++] = row_pointers[i][j+0];
}
}
}
else return 1;
/* 撤销数据占用的内存 */
png_destroy_read_struct(&png_ptr, &info_ptr, 0);
//free(out->rgba);
return 0;
}
/*
写入数据到png文件
file_name:写入数据的文件名
graph:数据的rgb数组 格式为bgr bgr排放
*/
int write_png_file(char *file_name , pic_data *graph)
/* 功能:将LCUI_Graph结构中的数据写入至png文件 */
{
int j, i, temp, pos;
png_byte color_type;
png_structp png_ptr;
png_infop info_ptr;
png_bytep * row_pointers;
/* create file */
FILE *fp = fopen(file_name, “wb”);
if (!fp)
{
printf(“[write_png_file] File %s could not be opened for writing”, file_name);
return -1;
}
/* initialize stuff */
png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
if (!png_ptr)
{
printf(“[write_png_file] png_create_write_struct failed”);
return -1;
}
info_ptr = png_create_info_struct(png_ptr);
if (!info_ptr)
{
printf(“[write_png_file] png_create_info_struct failed”);
return -1;
}
if (setjmp(png_jmpbuf(png_ptr)))
{
printf(“[write_png_file] Error during init_io”);
return -1;
}
png_init_io(png_ptr, fp);
/* write header */
if (setjmp(png_jmpbuf(png_ptr)))
{
printf(“[write_png_file] Error during writing header”);
return -1;
}
/* 判断要写入至文件的图片数据是否有透明度,来选择色彩类型 */
if(graph->flag == HAVE_ALPHA) color_type = PNG_COLOR_TYPE_RGB_ALPHA;
else color_type = PNG_COLOR_TYPE_RGB;
png_set_IHDR(png_ptr, info_ptr, graph->width, graph->height,
graph->bit_depth, color_type, PNG_INTERLACE_NONE,
PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
png_write_info(png_ptr, info_ptr);
/* write bytes */
if (setjmp(png_jmpbuf(png_ptr)))
{
printf(“[write_png_file] Error during writing bytes”);
return -1;
}
if(graph->flag == HAVE_ALPHA) temp = (4 * graph->width);
else temp = (3 * graph->width);
pos = 0;
row_pointers = (png_bytep*)malloc(graph->height*sizeof(png_bytep));
for(i = 0; i < graph->height; i++)
{
row_pointers[i] = (png_bytep)malloc(sizeof(unsigned char)*temp);
for(j = 0; j < temp; j += 3)
{
// row_pointers[i][j] = graph->rgba[0][pos]; // red
// row_pointers[i][j+1] = graph->rgba[1][pos]; // green
// row_pointers[i][j+2] = graph->rgba[2][pos]; // blue
row_pointers[i][j+2] = graph->rgba[pos++];
row_pointers[i][j+1] = graph->rgba[pos++];
row_pointers[i][j+0] = graph->rgba[pos++];
//if(graph->flag == HAVE_ALPHA)
// row_pointers[i][j+3] = graph->rgba[3][pos]; // alpha
//++pos;
}
}
png_write_image(png_ptr, row_pointers);
/* end write */
if (setjmp(png_jmpbuf(png_ptr)))
{
printf(“[write_png_file] Error during end of write”);
return -1;
}
png_write_end(png_ptr, NULL);
/* cleanup heap allocation */
for (j=0; j<graph->height; j++)
free(row_pointers[j]);
free(row_pointers);
fclose(fp);
return 0;
}
int main(int argc, char *argv[]) //规则图片效果较好
{
if(argc == 3){ //将png图片转化成bmp图片,argv[1]为png文件名 argv[2]为bmp文件名。
pic_data out;
detect_png(argv[1], &out);
write_bmp(argv[2], &out);
free(out.rgba);
}
return 0;
}
参考资料:
PNG文件格式白皮书:
http://www.w3.org/TR/REC-png.html
为数不多的中文
PNG格式说明:
http://dev.gameres.com/Program/Visual/Other/PNGFormat.htm
RFC-1950(ZLIB Compressed Data Format Specification):
ftp://ds.internic.net/rfc/rfc1950.txt
RFC-1950(DEFLATE Compressed Data Format Specification):
ftp://ds.internic.net/rfc/rfc1951.txt
LZ77算法的JAVA实现:
http://jazzlib.sourceforge.net/
LZ77算法的JAVA实现,包括J2ME版本:
http://www.jcraft.com/jzlib/index.html
使用libpng,libjpeg从文件读取和从内存读取2中方式的实现
:
1.
http://blog.csdn.net/bigapple88/article/details/5644418
2.http://blog.csdn.net/dj0379/article/details/4340300