zoukankan      html  css  js  c++  java
  • libpng.md

    libpng是一款C语言编写的比较底层的读写PNG文件的跨平台的库。借助它,你可以轻松读写PNG文件的每一行像素。
    因为PNG文件是经过压缩而且格式复杂的图形文件(有的PNG文件甚至像GIF文件一样带动画效果)
    而且PNG可以是带透明通道的真彩色图像、不带透明通道的真彩色图像、索引颜色、灰度颜色等各种格式,如果大家都自己写程序分析PNG文件就会显得很麻烦、很累。因此,通过使用libpng你就能直接使用现成的函数、程序来读写PNG文件了。

    一: 安装PNG库文件

    因为PNG文件需要用到zlib进行编解码, 所以用到libpng的同时也需要zlib加密解密

    本文使用的是libpng版本和zlib版本如下:

    下载地址:

    https://sourceforge.net/projects/libpng/files/

    libpng:
    #解压
    tar -xzvf libpng-1.6.26.tar.gz && cd libpng-1.6.26
    #安装
    ./configure
    make 
    sudo make install
    sudo ldconfig
     
    zlib:
    #解压:
    tar -xf zlib-1.2.11.tar.xz && cd zlib-1.2.11
    #安装
    ./configure 
    make
    sudo make install
    

    怎样借助libpng读写PNG文件????
    首先来讲如何写入PNG文件。
    第一步:初始化libpng库。
    当你需要读一个PNG文件或者写一个PNG文件的时候,你需要先定义两个结构体指针:

    png_structp png_ptr=NULL;//libpng的结构体
    png_infop   info_ptr=NULL;//libpng的信息
    

    你可以把上面的结构体指针定义为全局变量使用。
    每这两个结构体对应一个PNG文件。因此当你要同时操作多个PNG文件的时候,你就需要定义多个png_structp和png_infop来处理这些PNG文件了。
    因为是要写文件,所以要这样初始化:

    int iRetVal;
    png_ptr=png_create_write_struct(PNG_LIBPNG_VER_STRING,NULL,NULL,NULL);
    if(!png_ptr)
        goto 错误处理;
    info_ptr=png_create_info_struct(png_ptr);
    if(!info_ptr)
    {
        png_destroy_write_struct(&png_ptr,NULL);
        goto 错误处理;
    }
    iRetVal=setjmp(png_jmpbuf(png_ptr));//安装错误处理跳转点
    //当libpng内部出现错误的时候,libpng会调用longjmp直接跳转到这里运行。
    if(iRetVal)//setjmp的返回值就是libpng跳转后提供的错误代码(貌似总是1,但是还是请大家看libpng的官方文档)
    {
        fprintf(stderr,"错误码:%d
    ",iRetVal);
        goto 错误处理;
    }
    

    只要最后png_ptr和info_ptr都不是NULL就行了。否则就算是出错了。
    这里可以看到libpng使用了setjmp来做错误处理。有关setjmp的信息请点这里进去看。
    这两个结构体有对应的释放函数:png_destroy_write_struct
    结束对一个PNG的访问之后,你只需像这样调用这个函数:

    png_destroy_write_struct(&png_ptr,&info_ptr);

    就可以了。
    接下来打开文件准备写文件。还是大家熟悉的C语言文件流。

    FILE*fp=fopen("C:\TEST.PNG","wb");
    if(!fp)
        goto 错误处理;
    

    打开了文件以后,你要让libpng和这个文件流绑定起来,因此你需要调用png_init_io来完成绑定。

    png_init_io(png_ptr,fp);

    接下来就是关键的部分了:设置PNG文件的属性、写入PNG文件头、写入PNG文件。

    //设置PNG文件头
    png_set_IHDR(png_ptr,info_ptr,
        图像宽度,图像高度,//尺寸
        8,//颜色深度,也就是每个颜色成分占用位数(8表示8位红8位绿8位蓝,如果有透明通道则还会有8位不透明度)
        PNG_COLOR_TYPE_RGB,//颜色类型,PNG_COLOR_TYPE_RGB表示24位真彩深色,PNG_COLOR_TYPE_RGBA表示32位带透明通道真彩色
        PNG_INTERLACE_NONE,//不交错。PNG_INTERLACE_ADAM7表示这个PNG文件是交错格式。交错格式的PNG文件在网络传输的时候能以最快速度显示出图像的大致样子。
        PNG_COMPRESSION_TYPE_BASE,//压缩方式
        PNG_FILTER_TYPE_BASE);//这个不知道,总之填写PNG_FILTER_TYPE_BASE即可。
    png_set_packing(png_ptr);//设置打包信息
    png_write_info(png_ptr,info_ptr);//写入文件头
    

    执行完这些语句以后,你会发现libpng已经通过文件流指针fp写入了PNG的文件头。
    接下来要做的就是写入PNG的图像信息。其实就是把颜色保存到PNG。
    不像恶心的BMP居然有“底到上型”和“顶到下型”之分,PNG只有“顶到下型”,因此你不需要考虑行序。
    写图的方法之一是调用png_write_image(png_ptr,行指针数组的指针);这个你不需要考虑交错文件的写入的遍数。
    而如果你需要手动写入每一行的数据,你需要调用png_write_row(png_ptr,一行像素的指针);来进行逐行的像素值写入。
    如果你设置了交错格式的PNG,你需要多写入几遍图形数据,你需要调用png_set_interlace_handling(png_ptr);来得知你需要写入的遍数。如果你没有设置交错格式的PNG,你只需要写入一遍。

    写入好像素以后,调用png_write_end(png_ptr,info_ptr);把文件的结尾写入。

    调用png_destroy_write_struct(&png_ptr,&info_ptr);结束对这个PNG文件的访问。

    读取PNG文件也是类似的步骤,首先你需要初始化libpng库。

    png_destroy_read_struct(&png_ptr,&info_ptr,NULL);
     
    FILE*fp=fopen("C:\TEST.PNG","rb");
    if(!fp)
        goto 错误处理;
     
    png_init_io(png_ptr,fp);
    

    绑定之后,你还需要获取PNG的文件头信息。因此你需要调用png_read_info(png_ptr, info_ptr);

    png_read_info(png_ptr, info_ptr);

    读取了文件头,你就能获取文件头的信息。比如文件尺寸、位深度等。代码如下:

    png_get_IHDR(png_ptr,info_ptr,&width,&height,&bit_depth,&color_type,NULL,NULL,NULL);

    有些PNG文件是有背景色的,因此你需要处理这些背景色信息。我们可以用png_get_valid来判断这个PNG是否有背景色信息。png_get_valid(png_ptr,info_ptr,PNG_INFO_bKGD)返回0表示没有背景色信息,返回非零表示有背景色信息。然后我们调用png_get_bKGD来读取背景色。

    png_color_16p pBackground;
    png_get_bKGD(png_ptr,info_ptr,&pBackground);
    

    大家可以看看png_color_16p的原型:

    typedef struct png_color_16_struct
    {
        png_byte index;
        png_uint_16 red;
        png_uint_16 green;
        png_uint_16 blue;
        png_uint_16 gray;
    } png_color_16;
    

    如果这个PNG是调色板颜色的位图,那么index表示背景色的调色板颜色序号。
    red、green、blue表示背景色的颜色值。如果png_get_IHDR返回的位深度(bit_depth)是16,那么red、green、blue就是16位的颜色值,范围0~65535。(瞬间觉得PNG高大上啊!16+16+16=48,这个比真彩色还要真彩色!屌!)
    而如果png_get_IHDR返回的位深度(bit_depth)是8,那么red、green、blue其实都是8位的颜色值,范围0~255,也就是24位真彩色。
    接下来就是关键的步骤了,读取颜色数据。
    因为有些PNG是灰度色,有些PNG是索引颜色,有些PNG是48位色,总之各种奇葩。为了便于读取,我们应该先规范一下格式。

    if(colortype==PNG_COLOR_TYPE_PALETTE)
        png_set_palette_to_rgb(png_ptr);//要求转换索引颜色到RGB
    if(colortype==PNG_COLOR_TYPE_GRAY && bit_depth<8)
        png_set_expand_gray_1_2_4_to_8(png_ptr);//要求位深度强制8bit
    if(bit_depth==16)
        png_set_strip_16(png_ptr);//要求位深度强制8bit
    if(png_get_valid(png_ptr,info_ptr,PNG_INFO_tRNS))
        png_set_tRNS_to_alpha(png_ptr);
    if(colortype == PNG_COLOR_TYPE_GRAY || colortype == PNG_COLOR_TYPE_GRAY_ALPHA)
        png_set_gray_to_rgb(png_ptr);//灰度必须转换成RGB
    

    经过这些设定以后,我们读取的PNG就一律是R8:G8:B8:A8的4字节格式了(红绿蓝透明均为8位,每像素4字节)
    然后准备读取PNG。首先分配一个足够大的内存来存储颜色数据,然后分配一个内存来存储颜色数据每行的指针。
    因为颜色已经被规范为32位了所以我们可以直接把每个像素当做一个COLORREF变量。

    ppLinePtrs=(COLORREF**)malloc(g_dwHeight*sizeof(COLORREF*));//列指针
    if(!ppLinePtrs)
        goto Error;
    i=g_dwHeight;
    y=0;
    while(i--)//逆行序读取,因为位图是底到上型
        ppLinePtrs[y++]=(COLORREF*)&g_pBits[i*g_dwWidth];
    

    这个时候就是万事俱备的时候,只需要调用png_read_image(png_ptr,(png_bytepp)ppLinePtrs);就能完成读取。
    读取完以后,调用png_read_end(png_ptr,info_ptr);结束读取,调用png_destroy_read_struct(&png_ptr,&info_ptr,NULL);销毁结构体,然后fclose(fp);就算一切都搞定了。

    下面给一个实例:

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <png.h>
    

    #define PNG_BYTES_TO_CHECK	8
    #define HAVE_ALPHA			1
    #define NOT_HAVE_ALPHA		0
     
    typedef struct _pic_data pic_data;
    struct _pic_data {
    	int width, height; 	//长宽
    	int bit_depth; 	   	//位深度
    	int alpha_flag;		//是否有透明通道
    	unsigned char *rgba;//实际rgb数据
    };
     
    int check_is_png(FILE **fp, const char *filename) //检查是否png文件
    {
    	char checkheader[PNG_BYTES_TO_CHECK]; //查询是否png头
    	*fp = fopen(filename, "rb");
    	if (*fp == NULL) {
    		printf("open failed ...1
    ");
    		return -1;
    	}
    	if (fread(checkheader, 1, PNG_BYTES_TO_CHECK, *fp) != PNG_BYTES_TO_CHECK) //读取png文件长度错误直接退出
    		return 0;
    	return png_sig_cmp(checkheader, 0, PNG_BYTES_TO_CHECK); //0正确, 非0错误
    }
     
    int decode_png(const char *filename, pic_data *out) //取出png文件中的rgb数据
    {
    	png_structp png_ptr; //png文件句柄
    	png_infop	info_ptr;//png图像信息句柄
    	int ret;
    	FILE *fp;
    	if (check_is_png(&fp, filename) != 0) {
    		printf("file is not png ...
    ");
    		return -1;
    	}
    	printf("launcher[%s] ...
    ", PNG_LIBPNG_VER_STRING); //打印当前libpng版本号
     
    	//1: 初始化libpng的数据结构 :png_ptr, info_ptr
    	png_ptr  = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); 
    	info_ptr = png_create_info_struct(png_ptr);
     
    	//2: 设置错误的返回点
    	setjmp(png_jmpbuf(png_ptr));
    	rewind(fp); //等价fseek(fp, 0, SEEK_SET);
     
    	//3: 把png结构体和文件流io进行绑定 
    	png_init_io(png_ptr, fp);
    	//4:读取png文件信息以及强转转换成RGBA:8888数据格式
    	png_read_png(png_ptr, info_ptr, PNG_TRANSFORM_EXPAND, 0); //读取文件信息
    	int channels, color_type; 
    	channels 	= png_get_channels(png_ptr, info_ptr); //通道数量
    	color_type 	= png_get_color_type(png_ptr, info_ptr);//颜色类型
    	out->bit_depth = png_get_bit_depth(png_ptr, info_ptr);//位深度	
    	out->width 	 = png_get_image_width(png_ptr, info_ptr);//宽
    	out->height  = png_get_image_height(png_ptr, info_ptr);//高
    	
    	//if(color_type == PNG_COLOR_TYPE_PALETTE)
    	//	png_set_palette_to_rgb(png_ptr);//要求转换索引颜色到RGB
    	//if(color_type == PNG_COLOR_TYPE_GRAY && out->bit_depth < 8)
    	//	png_set_expand_gray_1_2_4_to_8(png_ptr);//要求位深度强制8bit
    	//if(out->bit_depth == 16)
    	//	png_set_strip_16(png_ptr);//要求位深度强制8bit
    	//if(png_get_valid(png_ptr,info_ptr,PNG_INFO_tRNS))
    	//	png_set_tRNS_to_alpha(png_ptr);
    	//if(color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
    	//	png_set_gray_to_rgb(png_ptr);//灰度必须转换成RG
    	printf("channels = %d color_type = %d bit_depth = %d width = %d height = %d ...
    ",
    			channels, color_type, out->bit_depth, out->width, out->height);
     
    	int i, j, k;
    	int size, pos = 0;
    	int temp;
    	
    	//5: 读取实际的rgb数据
    	png_bytepp row_pointers; //实际存储rgb数据的buf
    	row_pointers = png_get_rows(png_ptr, info_ptr); //也可以分别每一行获取png_get_rowbytes();
    	size = out->width * out->height; //申请内存先计算空间
    	if (channels == 4 || color_type == PNG_COLOR_TYPE_RGB_ALPHA) { //判断是24位还是32位
    		out->alpha_flag = HAVE_ALPHA; //记录是否有透明通道
    		size *= (sizeof(unsigned char) * 4); //size = out->width * out->height * channel
    		out->rgba = (png_bytep)malloc(size);
    		if (NULL == out->rgba) {
    			printf("malloc rgba faile ...
    ");
    			png_destroy_read_struct(&png_ptr, &info_ptr, 0);
    			fclose(fp);
    			return -1;
    		}
    		//从row_pointers里读出实际的rgb数据出来
    		temp = channels - 1;
    		for (i = 0; i < out->height; i++) 
    			for (j = 0; j < out->width * 4; j += 4) 
    				for (k = temp; k >= 0; k--)
    					out->rgba[pos++] = row_pointers[i][j + k];
    	} else if (channels == 3 || color_type == PNG_COLOR_TYPE_RGB) { //判断颜色深度是24位还是32位
    		out->alpha_flag = NOT_HAVE_ALPHA;
    		size *= (sizeof(unsigned char) * 3);
    		out->rgba = (png_bytep)malloc(size);
    		if (NULL == out->rgba) {
    			printf("malloc rgba faile ...
    ");
    			png_destroy_read_struct(&png_ptr, &info_ptr, 0);
    			fclose(fp);
    			return -1;
    		}
    		//从row_pointers里读出实际的rgb数据
    		temp = (3 * out->width);
    		for (i = 0; i < out->height; i ++) {
    			for (j = 0; j < temp; 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 return -1; 
    	//6:销毁内存
    	png_destroy_read_struct(&png_ptr, &info_ptr, 0);
    	fclose(fp);
    	//此时, 我们的out->rgba里面已经存储有实际的rgb数据了
    	//处理完成以后free(out->rgba)
    	return 0;
    }
     
    int RotationRight90(unsigned char * src, int srcW, int srcH, int channel) //顺时针旋转90度
    {
    	unsigned char * tempSrc = NULL; //临时的buf用来记录原始的图像(未旋转之前的图像)
    	int mSize = srcW * srcH * sizeof(char) * channel;
    	int i = 0;
    	int j = 0;
    	int k = 0;
    	int l = 3;
    	int desW = 0;
    	int desH = 0;
     
    	desW = srcH;
    	desH = srcW;
     
    	tempSrc = (unsigned char *)malloc(sizeof(char) * srcW * srcH * channel);
    	memcpy(tempSrc, src, mSize); //拷贝原始图像至tempbuf
    	for(i = 0; i < desH; i ++)
    	{
    		for(j = 0; j < desW; j ++)
    		{
    			for(k = 0; k < channel; k ++)
    			{
    				src[(i * desW + j) * channel + k] = tempSrc[((srcH - 1 - j) * srcW + i) * channel + k]; //替换像素
    			}
    		}
    	}
    	free(tempSrc);
    	return 0;
    }
     
    int write_png_file(const char *filename , pic_data *out) //生成一个新的png图像
    {
    	png_structp png_ptr;
    	png_infop 	info_ptr;
    	png_byte color_type;
    	png_bytep * row_pointers;
    	FILE *fp = fopen(filename, "wb");
    	if (NULL == fp) {
    		printf("open failed ...2
    ");
    		return -1;
    	}
    	//1: 初始化libpng结构体  
    	png_ptr	= png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0);
    	if (!png_ptr) {
    		printf("png_create_write_struct failed ...
    ");
    		return -1;
    	}
    	//2: 初始化png_infop结构体 , 
    	//此结构体包含了图像的各种信息如尺寸,像素位深, 颜色类型等等
    	info_ptr = png_create_info_struct(png_ptr);
    	if (!info_ptr) {
    		printf("png_create_info_struct failed ...
    ");
    		return -1;
    	}
    	//3: 设置错误返回点
    	if (setjmp(png_jmpbuf(png_ptr))) {
    		printf("error during init_io ...
    ");
    		return -1;
    	}
    	//4:绑定文件IO到Png结构体
    	png_init_io(png_ptr, fp);
    	if (setjmp(png_jmpbuf(png_ptr))) {
    		printf("error during init_io ...
    ");
    		return -1;
    	}
    	if (out->alpha_flag == HAVE_ALPHA) color_type = PNG_COLOR_TYPE_RGB_ALPHA;
    	else color_type = PNG_COLOR_TYPE_RGB;
    	//5:设置以及写入头部信息到Png文件
    	png_set_IHDR(png_ptr, info_ptr, out->width, out->height, out->bit_depth,
    	color_type, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
    	png_write_info(png_ptr, info_ptr);
    	if (setjmp(png_jmpbuf(png_ptr))) {
    		printf("error during init_io ...
    ");
    		return -1;
    	}
    	int channels, temp;
    	int i, j, pos = 0;
    	if (out->alpha_flag == HAVE_ALPHA) {
    		channels = 4;
    		temp = (4 * out->width);
    		printf("have alpha ...
    ");
    	} else {
    		channels = 3;
    		temp = (3 * out->width);
    		printf("not have alpha ...
    ");
    	}
    	// 顺时针旋转90度 , 旋转完了一定要把width 和height调换 不然得到的图像是花的  旋转三次就是逆时针旋转一次
    	//RotationRight90(out->rgba, out->width, out->height, channels);
    	//RotationRight90(out->rgba, out->height, out->width, channels);
    	//RotationRight90(out->rgba, out->width, out->height, channels);
    	row_pointers = (png_bytep*)malloc(out->height * sizeof(png_bytep));
    	for (i = 0; i < out->height; i++) {
    		row_pointers[i] = (png_bytep)malloc(temp* sizeof(unsigned char));
    		for (j = 0; j < temp; j += channels) {
    			if (channels == 4) {
    				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] = out->rgba[pos++];
    			} else {
    				row_pointers[i][j+2] = out->rgba[pos++];
    				row_pointers[i][j+1] = out->rgba[pos++];
    				row_pointers[i][j+0] = out->rgba[pos++];
    			}
    		}
    	}
    	//6: 写入rgb数据到Png文件
    	png_write_image(png_ptr, (png_bytepp)row_pointers);
    	if (setjmp(png_jmpbuf(png_ptr))) {
    		printf("error during init_io ...
    ");
    		return -1;
    	}
    	//7: 写入尾部信息
    	png_write_end(png_ptr, NULL);
    	//8:释放内存 ,销毁png结构体
    	for (i = 0; i < out->height; i ++)
    		free(row_pointers[i]);
    	free(row_pointers);
    	png_destroy_write_struct(&png_ptr, &info_ptr);
    	fclose(fp);
    	return 0;
    }
     
    int main(int argc, char **argv)
    {
    	pic_data out;
    	if (argc == 3) {
    		decode_png(argv[1], &out);
    		write_png_file(argv[2], &out);
    		free(out.rgba);
    	} else {
    		puts("please input two file, 
    argv[1]:source.png argv[2]:dest.png");	
    	}
    	return 0;
    }
    

    下一篇文章讲解如何生成二维码图片(Linux使用libqrcode生成二维码图片)其实也是对libpng的一个实战。

  • 相关阅读:
    PAT (Advanced Level) 1114. Family Property (25)
    PAT (Advanced Level) 1113. Integer Set Partition (25)
    PAT (Advanced Level) 1112. Stucked Keyboard (20)
    PAT (Advanced Level) 1111. Online Map (30)
    PAT (Advanced Level) 1110. Complete Binary Tree (25)
    PAT (Advanced Level) 1109. Group Photo (25)
    PAT (Advanced Level) 1108. Finding Average (20)
    PAT (Advanced Level) 1107. Social Clusters (30)
    PAT (Advanced Level) 1106. Lowest Price in Supply Chain (25)
    PAT (Advanced Level) 1105. Spiral Matrix (25)
  • 原文地址:https://www.cnblogs.com/nsfoxer/p/14353852.html
Copyright © 2011-2022 走看看