• 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\n",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\n");
    		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 ...\n");
    		return -1;
    	}
    	printf("launcher[%s] ...\n", 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 ...\n",
    			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 ...\n");
    			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 ...\n");
    			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\n");
    		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 ...\n");
    		return -1;
    	}
    	//2: 初始化png_infop结构体 , 
    	//此结构体包含了图像的各种信息如尺寸,像素位深, 颜色类型等等
    	info_ptr = png_create_info_struct(png_ptr);
    	if (!info_ptr) {
    		printf("png_create_info_struct failed ...\n");
    		return -1;
    	}
    	//3: 设置错误返回点
    	if (setjmp(png_jmpbuf(png_ptr))) {
    		printf("error during init_io ...\n");
    		return -1;
    	}
    	//4:绑定文件IO到Png结构体
    	png_init_io(png_ptr, fp);
    	if (setjmp(png_jmpbuf(png_ptr))) {
    		printf("error during init_io ...\n");
    		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 ...\n");
    		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 ...\n");
    	} else {
    		channels = 3;
    		temp = (3 * out->width);
    		printf("not have alpha ...\n");
    	}
    	// 顺时针旋转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 ...\n");
    		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, \nargv[1]:source.png argv[2]:dest.png");	
    	}
    	return 0;
    }
    

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

  • 相关阅读:
    redis命令
    linux命令行任务管理
    tomcat修改内存
    Python调用shell
    取消myeclipse自动进入workspace
    解决Myeclipse编译不生成.class文件问题
    Manacher回文串算法学习记录
    青少年如何使用 Python 开始游戏开发
    对 Linux 专家非常有用的 20 个命令
    对中级 Linux 用户非常有用的 20 个命令
  • 原文地址:https://www.cnblogs.com/nsfoxer/p/16317584.html
Copyright © 2020-2023  润新知