libpng库的开发
来源:互联网 发布:云计算与人工智能 编辑:程序博客网 时间:2024/05/30 04:20
借助它,你可以轻松读写PNG文件的每一行像素。因为PNG文件是经过压缩而且格式复杂的图形文件(有的PNG文件甚至像GIF文件一样带动画效果,但是不像jpg那样是有损压缩,png是无损压缩的),而且PNG可以是带透明通道的真彩色图像、不带透明通道的真彩色图像、索引颜色、灰度颜色等各种格式,如果大家都自己写程序分析PNG文件就会显得很麻烦、很累。因此,通过使用libpng你就能直接使用现成的函数、程序来读写PNG文件了。
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文件标志后面的数据是数据块(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个,这些辅助数据块包含了很多信息,辅助数据块不是必须包含的。
数据块名称
允许多个
数据块
位 置
基色和白色点数据块(cHRM)
不允许
在PLTE和IDAT之前
图像γ数据块(gAMA)
不允许
在PLTE和IDAT之前
ICCP(iCCP)
允许
在PLTE之后IDAT之前如果有iCCP,则无sRGB
数据块名称
允许多个
数据块
位 置
样本有效位数据块(sBIT)
不允许
在PLTE和IDAT之前
标准RPG颜色(sRGB)
不允许
在PLTE之后IDAT之前如
果有sRGB,则无iCCP
背景颜色数据块(bKGD)
不允许
在PLTE之后IDAT之前
图像直方图数据块(hIST)
不允许
在PLTE之后IDAT之前
图像透明数据块(tRNS)
不允许
在PLTE之后IDAT之前
物理像素尺寸数据块(pHYs)
不允许
在IDAT之前
建议调色板(sPLT)
允许
在IDAT之前
图像最后修改时间数据块(tIME)
不允许
无限制
国际文本数据(iTXt)
允许
无限制
文本信息数据块(tEXt)
允许
无限制
压缩文本数据块(zTXt)
允许
无限制
名称
字节数
说明
Length (长度)
4字节
指定数据块中数据域的长度,其长度不超过(231-1)字节
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码生成多项式进行计算:
x32+x26+x23+x22+x16+x12+x11+x10+x8+x7+x5+x4+x2+x+1
下面,我们依次来了解一下各个关键数据块的结构吧。
IHDR
文件头数据块IHDR(header chunk):它包含有PNG文件中存储的图像数据的基本信息,并要作为第一个数据块出现在PNG数据流中,而且一个PNG数据流中只能有一个文件头数据块。
文件头数据块由13 + 12(长度信息、数据类型码、循环检测)字节组成,它的格式如下表所示。
域的名称
字节数
说明
Width
4 bytes
图像宽度,以像素为单位
Height
4 bytes
图像高度,以像素为单位
Bit depth
1 byte
图像深度:
索引彩色图像:1,2,4或8
灰度图像:1,2,4,8或16
真彩色图像:8或16
ColorType
1 byte
颜色类型:
0:灰度图像, 1,2,4,8或16
2:真彩色图像,8或16
3:索引彩色图像,1,2,4或8
4:带α通道数据的灰度图像,8或16
6:带α通道数据的真彩色图像,8或16
Compression method
1 byte
压缩方法(LZ77派生算法)
Filter method
1 byte
滤波器方法
Interlace method
1 byte
隔行扫描方法:
0:非隔行扫描
1: Adam7(由Adam M. Costello开发的7遍隔行扫描方法)
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
- libpng库的开发
- libpng库的使用讲解
- libpng库
- zlib 库和libpng 的安装:
- libpng的简单使用
- libpng的参考资料
- libpng的使用
- 树莓派的libpng安装
- 【C】libpng的使用
- VS2012使用libpng库
- VS2013编译libpng库
- 图像处理库 libpng
- 常用库之三:libpng的交叉编译
- Windows下zlib和libPng库的编译及使用
- Linux中应用libpng库出现的错误
- libpng库的安装与使用(交叉编译)
- Libpng的编译和安装
- libpng与zlib的坑
- Redis缓存服务器安装部署
- 分析String ,stringbuilder, stringbuffer
- 20170827,一周总结
- Android系统自带主题初探
- 自然语言处理入门(6)——基于LDA的文章主题生成
- libpng库的开发
- deep learning的一些资料和教材
- Weex环境配置以及踩坑记录
- 【redis】redis导图宏观总结
- map和set容器自定义比较函数
- [js高手之路]构造函数的基本特性与优缺点
- php使用ZipArchive函数实现文件的压缩与解压缩
- 并查集模板
- 基于ADF4106的锁相环频率器研究与设计