找回密码
 立即注册→加入我们

QQ登录

只需一步,快速开始

搜索
热搜: 下载 VB C 实现 编写
查看: 40629|回复: 56

【C】libpng的使用

[复制链接]
发表于 2014-3-30 10:57:27 | 显示全部楼层 |阅读模式

欢迎访问技术宅的结界,请注册或者登录吧。

您需要 登录 才可以下载或查看,没有账号?立即注册→加入我们

×
参考资料:
Writing PNG Images (PNG: The Definitive Guide)
Reading PNG Images (PNG: The Definitive Guide)

我要讲的三个大部分分别是:
1、libpng是什么,能做什么?
2、怎样让自己的程序可以使用libpng库?
3、怎样借助libpng读写PNG文件


1、libpng是什么?
libpng是一款C语言编写的比较底层的读写PNG文件的跨平台的库。借助它,你可以轻松读写PNG文件的每一行像素。
因为PNG文件是经过压缩而且格式复杂的图形文件(有的PNG文件甚至像GIF文件一样带动画效果)
而且PNG可以是带透明通道的真彩色图像、不带透明通道的真彩色图像、索引颜色、灰度颜色等各种格式,如果大家都自己写程序分析PNG文件就会显得很麻烦、很累。因此,通过使用libpng你就能直接使用现成的函数、程序来读写PNG文件了。
注:libpng的官网说,“PNG”这个词的发音是“拼”(ping),而不是“批恩鸡”(pee en gee,也就是直接读字母),也不是其它的单词发音(什么pinj、pig之类的发音)。
但是如果你不是以英语为母语的话(咱都是中国人呢~~)你就不必蛋疼地在意这个发音的问题,直接见人就说“批恩鸡”就行啦。(当我没说)

2、怎样让自己的程序可以使用libpng库?
有很多种方法。
方法1:上网下载libpng的DLL、LIB文件以及头文件,然后在自己的程序里,包含png.h,链接libpng.lib,就可以了。但是这样的话你的程序需要libpng.dll才能运行,而libpng使用了zlib所以可能你还需要zlib.dll才能运行。因此你还需要下载zlib的头文件、lib、DLL。
方法2:直接下载libpng的源码和zlib的源码,然后把.c文件和.h文件都加入到自己的工程里面。这招最好使因为这样便于调试。只是你的程序会很大因为你的程序直接集成了libpng和zlib。
方法3:下载libpng的源码和zlib的源码,自己将其编译为DLL或LIB,然后包含png.h,链接LIB文件,就能使用。不过你如果没编译好也可能会出问题。

3、怎样借助libpng读写PNG文件
首先来讲如何写入PNG文件。
第一步:初始化libpng库。
当你需要读一个PNG文件或者写一个PNG文件的时候,你需要先定义两个结构体指针:
  1. png_structp png_ptr=NULL;//libpng的结构体
  2. png_infop   info_ptr=NULL;//libpng的信息
复制代码
你可以把上面的结构体指针定义为全局变量使用。
每这两个结构体对应一个PNG文件。因此当你要同时操作多个PNG文件的时候,你就需要定义多个png_structppng_infop来处理这些PNG文件了。
因为是要写文件,所以要这样初始化:
  1. int iRetVal;
  2. png_ptr=png_create_write_struct(PNG_LIBPNG_VER_STRING,NULL,NULL,NULL);
  3. if(!png_ptr)
  4.     goto 错误处理;
  5. info_ptr=png_create_info_struct(png_ptr);
  6. if(!info_ptr)
  7. {
  8.     png_destroy_write_struct(&png_ptr,NULL);
  9.     goto 错误处理;
  10. }
  11. iRetVal=setjmp(png_jmpbuf(png_ptr));//安装错误处理跳转点
  12. //当libpng内部出现错误的时候,libpng会调用longjmp直接跳转到这里运行。
  13. if(iRetVal)//setjmp的返回值就是libpng跳转后提供的错误代码(貌似总是1,但是还是请大家看libpng的官方文档)
  14. {
  15.     fprintf(stderr,"错误码:%d\n",iRetVal);
  16.     goto 错误处理;
  17. }
复制代码
只要最后png_ptr和info_ptr都不是NULL就行了。否则就算是出错了。
这里可以看到libpng使用了setjmp来做错误处理。有关setjmp的信息请点这里进去看。
这两个结构体有对应的释放函数:png_destroy_write_struct
结束对一个PNG的访问之后,你只需像这样调用这个函数:
  1. png_destroy_write_struct(&png_ptr,&info_ptr);
复制代码
就可以了。
接下来打开文件准备写文件。还是大家熟悉的C语言文件流。
  1. FILE*fp=fopen("C:\\TEST.PNG","wb");
  2. if(!fp)
  3.     goto 错误处理;
复制代码
打开了文件以后,你要让libpng和这个文件流绑定起来,因此你需要调用png_init_io来完成绑定。
  1. png_init_io(png_ptr,fp);
复制代码
接下来就是关键的部分了:设置PNG文件的属性、写入PNG文件头、写入PNG文件。
  1. //设置PNG文件头
  2. png_set_IHDR(png_ptr,info_ptr,
  3.     图像宽度,图像高度,//尺寸
  4.     8,//颜色深度,也就是每个颜色成分占用位数(8表示8位红8位绿8位蓝,如果有透明通道则还会有8位不透明度)
  5.     PNG_COLOR_TYPE_RGB,//颜色类型,PNG_COLOR_TYPE_RGB表示24位真彩深色,PNG_COLOR_TYPE_RGBA表示32位带透明通道真彩色
  6.     PNG_INTERLACE_NONE,//不交错。PNG_INTERLACE_ADAM7表示这个PNG文件是交错格式。交错格式的PNG文件在网络传输的时候能以最快速度显示出图像的大致样子。
  7.     PNG_COMPRESSION_TYPE_BASE,//压缩方式
  8.     PNG_FILTER_TYPE_BASE);//这个不知道,总之填写PNG_FILTER_TYPE_BASE即可。
  9. png_set_packing(png_ptr);//设置打包信息
  10. 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,你只需要写入一遍。

以下文本写给小白。高手请略过。
这里需要详细说明图像是怎么写入的。首先说明一下什么是图像。一个图像,是由一个一个的正方形的小像素点组成的。
每个像素点都有自己的颜色值,用三个字节来表示红色、绿色、蓝色的分量。所有的颜色都是用红绿蓝三基色混合搭配调出来的颜色。
然后对于libpng,图像是一行一行写入到文件的。这里所说的“行”和“列”指的是排列起来的像素点。比如一个图像的宽度是1024像素,高度是768像素,那么这个图像就有768行,每行有1024个像素点。
假设你设置的颜色深度是8,颜色类型是PNG_COLOR_TYPE_RGB,那么你的每个像素点都是由三个字节组成的,这三个字节分别是红色、绿色和蓝色的分量。
而如果颜色类型是PNG_COLOR_TYPE_RGBA,那么你的每个像素点都是由四个字节组成的,这四个字节分别是红色、绿色和蓝色的分量和不透明度。这样的图像才支持透明颜色的显示。
调用png_write_row的方法很简单,就是把一行的像素点的颜色设置好,然后调用它:png_write_row(png_ptr,这行像素的第一个像素在内存中的位置);就可以写入一行。
而调用png_write_image你需要把每一行的像素颜色都设置好,然后建立一个指针数组,这个指针数组的每一个指针都指向每一行的像素。明白吧?

写入好像素以后,调用png_write_end(png_ptr,info_ptr);把文件的结尾写入。
调用png_destroy_write_struct(&png_ptr,&info_ptr);结束对这个PNG文件的访问。
最后fclose(fp);关闭文件。这个时候你会发现,你已经成功地产生了一个PNG文件!而且可以用PS打开了。

读取PNG文件也是类似的步骤,首先你需要初始化libpng库。
你需要先定义两个结构体指针:
  1. png_structp png_ptr=NULL;//libpng的结构体
  2. png_infop   info_ptr=NULL;//libpng的信息
复制代码
你可以把上面的结构体指针定义为全局变量使用。
每这两个结构体对应一个PNG文件。因此当你要同时操作多个PNG文件的时候,你就需要定义多个png_structppng_infop来处理这些PNG文件了。
因为是要读文件,所以要这样初始化:
  1. int iRetVal;
  2. png_ptr=png_create_read_struct(PNG_LIBPNG_VER_STRING,NULL,NULL,NULL);
  3. if(!png_ptr)
  4.     goto 错误处理;
  5. info_ptr=png_create_info_struct(png_ptr);
  6. if(!info_ptr)
  7. {
  8.     png_destroy_read_struct(&png_ptr,NULL,NULL);
  9.     goto 错误处理;
  10. }
  11. iRetVal=setjmp(png_jmpbuf(png_ptr));//安装错误处理跳转点
  12. //当libpng内部出现错误的时候,libpng会调用longjmp直接跳转到这里运行。
  13. if(iRetVal)//setjmp的返回值就是libpng跳转后提供的错误代码(貌似总是1,但是还是请大家看libpng的官方文档)
  14. {
  15.     fprintf(stderr,"错误码:%d\n",iRetVal);
  16.     goto 错误处理;
  17. }
复制代码
只要最后png_ptr和info_ptr都不是NULL就行了。否则就算是出错了。
这两个结构体有对应的释放函数:png_destroy_read_struct
结束对一个PNG的访问之后,你只需像这样调用这个函数:
  1. png_destroy_read_struct(&png_ptr,&info_ptr,NULL);
复制代码
就可以了。
接下来打开文件准备读文件。还是大家熟悉的C语言文件流。
  1. FILE*fp=fopen("C:\\TEST.PNG","rb");
  2. if(!fp)
  3.     goto 错误处理;
复制代码
打开了文件以后,你要让libpng和这个文件流绑定起来,因此你需要调用png_init_io来完成绑定。绑定之后,你还需要获取PNG的文件头信息。因此你需要调用png_read_info(png_ptr, info_ptr);
  1. png_init_io(png_ptr,fp);
  2. png_read_info(png_ptr, info_ptr);
复制代码
读取了文件头,你就能获取文件头的信息。比如文件尺寸、位深度等。代码如下:
  1. 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来读取背景色。
  1. png_color_16p pBackground;
  2. png_get_bKGD(png_ptr,info_ptr,&pBackground);
复制代码
大家可以看看png_color_16p的原型:
  1. typedef struct png_color_16_struct
  2. {
  3.     png_byte index;
  4.     png_uint_16 red;
  5.     png_uint_16 green;
  6.     png_uint_16 blue;
  7.     png_uint_16 gray;
  8. } 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位色,总之各种奇葩。为了便于读取,我们应该先规范一下格式。
  1. if(colortype==PNG_COLOR_TYPE_PALETTE)
  2.     png_set_palette_to_rgb(png_ptr);//要求转换索引颜色到RGB
  3. if(colortype==PNG_COLOR_TYPE_GRAY && bit_depth<8)
  4.     png_set_expand_gray_1_2_4_to_8(png_ptr);//要求位深度强制8bit
  5. if(bit_depth==16)
  6.     png_set_strip_16(png_ptr);//要求位深度强制8bit
  7. if(png_get_valid(png_ptr,info_ptr,PNG_INFO_tRNS))
  8.     png_set_tRNS_to_alpha(png_ptr);
  9. if(colortype == PNG_COLOR_TYPE_GRAY || colortype == PNG_COLOR_TYPE_GRAY_ALPHA)
  10.     png_set_gray_to_rgb(png_ptr);//灰度必须转换成RGB
复制代码
经过这些设定以后,我们读取的PNG就一律是R8:G8:B8:A8的4字节格式了(红绿蓝透明均为8位,每像素4字节)
然后准备读取PNG。首先分配一个足够大的内存来存储颜色数据,然后分配一个内存来存储颜色数据每行的指针。
因为颜色已经被规范为32位了所以我们可以直接把每个像素当做一个COLORREF变量。
  1. ppLinePtrs=(COLORREF**)malloc(g_dwHeight*sizeof(COLORREF*));//列指针
  2. if(!ppLinePtrs)
  3.     goto Error;
  4. i=g_dwHeight;
  5. y=0;
  6. while(i--)//逆行序读取,因为位图是底到上型
  7.     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);就算一切都搞定了。

接下来我会放上两份源码,一份把BMP转换成PNG(支持把两个BMP合体转换成带透明通道的PNG)
而另一份是结合了分层窗体的技术,把PNG当做分层窗体的界面来显示的源码。
效果不错。发张图晒晒。
雾切响子.PNG
BMP转PNG:
EXE下载:
BMP2PNG.exe (132 KB, 下载次数: 39)
源码下载:
游客,如果您要查看本帖隐藏内容请回复


分层窗体:
EXE下载:
LayeredPNG_bin.7z (106.18 KB, 下载次数: 22)
源码下载:
游客,如果您要查看本帖隐藏内容请回复

本帖被以下淘专辑推荐:

回复

使用道具 举报

发表于 2014-3-30 14:09:32 | 显示全部楼层
表示被底部的图片吓到了。。。
回复 赞! 靠!

使用道具 举报

 楼主| 发表于 2014-3-30 14:13:01 | 显示全部楼层
如月桃 发表于 2014-3-30 06:09
表示被底部的图片吓到了。。。

233 不要在意细节
回复 赞! 靠!

使用道具 举报

发表于 2014-4-18 17:57:34 | 显示全部楼层
哈哈,好东西,
论坛的搜索功能果真牛,
谢谢楼主啦
回复 赞! 靠!

使用道具 举报

沧海浮萍 该用户已被删除
发表于 2014-5-9 17:54:02 | 显示全部楼层
提示: 作者被禁止或删除 内容自动屏蔽
回复

使用道具 举报

发表于 2014-5-10 08:35:24 | 显示全部楼层
楼主喜欢研究细节上的一些问题。
回复 赞! 靠!

使用道具 举报

发表于 2014-5-28 13:20:38 | 显示全部楼层
看看,正在找这个
回复 赞! 靠!

使用道具 举报

KxIX 该用户已被删除
发表于 2014-5-30 16:09:29 | 显示全部楼层
提示: 作者被禁止或删除 内容自动屏蔽
回复 赞! 靠!

使用道具 举报

发表于 2014-6-3 14:47:01 | 显示全部楼层
本帖最后由 undefined 于 2014-6-3 15:25 编辑

不知道为什么我读取的png颜色会变得很奇怪,研究一下楼主的源码

// ====================
哎哎? 透明混合竟然是自己算的? 还以为UpdateLayeredWindow会做好混合...

// ====================
被AlphaBlend弄晕了...

是不是这样:

不考虑全局不透明度【SourceConstantAlpha == 255】的情况下,

混合图层 = 背景层(不透明) + 前景层(有Alpha通道)
计算公式:
混合 = 背景*(1 - Alpha) + 前景*Alpha

而winAPI中的AlphaBlend是:
混合 = 背景*(1 - Alpha) + 前景

求解答

// ====================
跪谢楼主,终于能正确显示图片了
回复 赞! 靠!

使用道具 举报

发表于 2014-6-4 23:16:58 | 显示全部楼层
libpng v1.6.10提供了Simplified API,感觉用起来挺方便的.
在这里记录一下.
以下基本来自libpng-manual.txt:

Simplified API隐藏了png_ptr和info_ptr,只需要一个image结构体.

一般image这样初始化:
png_image image;
memset(&image,0,(sizeof image));
image.version = PNG_IMAGE_VERSION; // 必须这么做

我觉得image结构体常用的成员就version,width,height,format,其他成员见manual.txt.

有了image之后,我就可以读取头部信息了:
if (png_image_begin_read_from_file(&image,filename)) {
    // do something...
} else {
    // err
}

成功读取的话,image.width和image.height就可以用了.

接下来读取图像,不过得先指定格式:
image.format = PNG_FORMAT_RGBA;

我觉得常用的PNG_FORMAT_*有:
PNG_FORMAT_RGB
PNG_FORMAT_RGBA
PNG_FORMAT_ARGB
PNG_FORMAT_BGR
PNG_FORMAT_BGRA
PNG_FORMAT_ABGR
都是顾名思义的,不多解释.
不过说一点,bitmap是BGRA格式的.

设置了格式之后,就可以读取了:
if (png_image_finish_read(&image,background,pBits,row_stride,&colormap)) {
    // do something...
} else {
    // err
}

上面用到了background,pBits,row_stride,colormap,我逐个解释一下:
background的类型是png_colorp,一般用不上吧大概,不用的话可以放NULL;
pBits是一个void指针,它指的地址就是用来放读取好的数据的,所以要先分配好足够的空间,
    具体怎么分配,那就看你的格式了,比如BGRA那么就是(4 * w * h)bytes了.
row_stride的类型是int32,解释起来有点麻烦,我就只说常用的:
    读/写大概是一行一行进行的,这个参数指定了怎么组织这些行,
    常见的就上到下逐行扫描和从下到上逐行扫描,
    上到下:设为0就可以了,得到正常的从左到右,从上到下的图像数据.
    下到上:设为-PNG_IMAGE_ROW_STRIDE(image),别忘了前面的负号,
           得到从左到右,从下到上的图像数据.
    注:bitmap是从下到上的.
colormap一般不用,NULL就可以了.

然后就没有然后了

不需要再用image的时候就png_image_free(&image);
不需要再用图像数据的时候就free(pBits);

嗯就这样.
回复 赞! 靠!

使用道具 举报

发表于 2014-6-7 15:53:28 | 显示全部楼层
正需要这方面的例子,网上挺少的
回复 赞! 靠!

使用道具 举报

发表于 2015-12-18 11:55:19 | 显示全部楼层
这个真的不错哦。
回复 赞! 靠!

使用道具 举报

发表于 2016-2-25 01:58:07 | 显示全部楼层
谢谢LZ,好人一生平安
回复 赞! 靠!

使用道具 举报

发表于 2016-5-20 19:11:29 | 显示全部楼层
谢谢楼主!
回复

使用道具 举报

发表于 2016-5-20 19:11:54 | 显示全部楼层
谢谢楼主!
咋不能评论?
回复 赞! 靠!

使用道具 举报

发表于 2016-8-17 16:04:06 | 显示全部楼层
正在ARM9平台裸机解PNG,学习一下
回复 赞! 靠!

使用道具 举报

发表于 2016-8-17 16:04:22 | 显示全部楼层

RE: 【C】libpng的使用

正在ARM9平台裸机解PNG,学习一下。
回复 赞! 靠!

使用道具 举报

发表于 2016-8-17 16:04:59 | 显示全部楼层

RE: 【C】libpng的使用

正在ARM9平台裸机解PNG,学习一下。
回复 赞! 靠!

使用道具 举报

发表于 2016-8-29 16:43:05 | 显示全部楼层
详细好东西,谢谢分享
回复 赞! 靠!

使用道具 举报

发表于 2016-9-3 10:26:39 | 显示全部楼层
hello        阿斯顿
回复 赞! 靠!

使用道具 举报

本版积分规则

QQ|Archiver|小黑屋|技术宅的结界 ( 滇ICP备16008837号 )|网站地图

GMT+8, 2024-11-22 01:07 , Processed in 0.049094 second(s), 33 queries , Gzip On.

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表