图形图像基础 之 png介绍

一、png相关概念

png—一种无损压缩算法的位图格式

PNG:Portable Network Graphics,是一种支持无损压缩(使用从LZ77派生的无损数据压缩算法)的位图图形格式,支持索引、灰度、RGB三种颜色方案以及Alpha通道等特性。PNG的开发目标是改善并取代GIF作为适合网络传输的格式而不需专利许可,所以被广泛应用于互联网及其他方面上。

png和bmp、jpeg图片比较

bmp常见是非压缩的,它size最大但是显示简单,主要是最后用作本地存储 和 显示阶段-无需解压, 传输都会选择png和jpeg。jpeg图片的优势是压缩比例大,它由于存在转换到yuv域并进行下采样420,通过有损压缩算法 能得到比png更高的压缩效率,常见的大图都会选择jpeg而非png。png相对jpeg它的优势是包含alpha通道(可以做半透明效果,而jpeg是不存在的alpha通道),并且常用无损压缩,常见用在一些精细小图标,或者必须带上aplha透明度的图片的使用场景。

libpng—一款C语言编写的读写PNG文件的跨平台的库

libpng是一款C语言编写的读写PNG文件的跨平台的库,libpng 可作为 ANSI C (C89) 源代码使用,并且需要zlib 1.0.4 或更高版本提供压缩算法支持。

二、png文件格式—文件标签+特定格式数据块

PNG图像格式文件由一个8字节的PNG文件标签file signature按特定结构组织的3个以上的后续数据块chunk组成
1、文件标签file signature为一组固定8字节文件署名,用于识别png文件。
2、数据块chunk分为 :1、关键块critical chunk,是文件必须包含的关键块,读取软件也必须支持解析;2、辅助块ancillary chunks,允许软件忽略它不认识的附加块,允许PNG格式在扩展时仍能保持与旧版本兼容。每个数据块都由下面所示的的4个域组成:
在这里插入图片描述
CRC(cyclic redundancy check)域中的值是对Chunk Type Code域和Chunk Data域中的数据进行计算得到的。CRC具体算法定义在ISO 3309和ITU-T V.42中。
在这里插入图片描述
我们选择如下图片做举例:
请添加图片描述
在这里插入图片描述

2.1 PNG文件标签,8字节—用于识别png文件

PNG文件包括8字节文件署名(89 50 4E 47 0D 0A 1A 0A,十六进制),用来识别PNG格式
在这里插入图片描述在这里插入图片描述

2.2 PNG关键块critical chunk介绍

关键块critical chunk*中有4个标准数据块:
文件头数据块IHDR(header chunk):包含有图像基本信息,作为第一个数据块出现并只出现一次。
调色板数据块PLTE(palette chunk):必须放在图像数据块之前。
图像数据块IDAT(image data chunk):存储实际图像数据。PNG数据允许包含多个连续的图像数据块。
图像结束数据IEND(image trailer chunk):放在文件尾部,表示PNG数据流结束。

2.2.1 文件头数据块IHDR(header chunk)

IHDR含有PNG文件中存储的图像数据的基本信息,并要作为第一个数据块出现在PNG数据流中,而且一个PNG文件中只能有一个文件头数据块。文件头数据块由13字节组成,它的格式如下表所示
在这里插入图片描述
在这里插入图片描述
8~ 11字节:0xd表示IHDR长度13字节,12~ 15字节:就是ASCII码的IHDR字符表示块类型;紧接着13个字节为IHDR的内容:0x27b 宽度635,0x19b 高度 411,0x8 色深度 8bit,0x6 颜色类型,带alpha通道的真彩,0x0 压缩算法LZ77派生算法,0x0 滤波器方法,0x0 非隔行扫描;剩下的是CRC校验码;

2.2.2 调色板数据块PLTE(palette chunk)

PLTE包含有与索引彩色图像((indexed-color image))相关的彩色变换数据,它仅与索引彩色图像有关,而且要放在图像数据块(image data chunk)之前。真彩色的PNG数据流也可以有调色板数据块,目的是便于非真彩色显示程序用它来量化图像数据,从而显示该图像。(类似bmp也有相同的调色板机制)
在这里插入图片描述
调色板实际是一个彩色索引查找表,它的表项数目可以是1~256中的一个数,每个表项有3字节,因此调色板数据块所包含的最大字节数为768。

2.2.3 图像数据块IDAT(image data chunk)

它存储实际的数据,在数据流中可包含多个连续顺序的图像数据块。

2.2.4 图像结束数据IEND(image trailer chunk)

它用来标记PNG文件或者数据流已经结束,并且必须要放在文件的尾部。内容为 00 00 00 00 49 45 4E 44 AE 42 60 82
在这里插入图片描述

三、libpng使用基础

3.1 libpng库 的下载编译安装

1、下载libpng源码:https://sourceforge.net/projects/libpng/files/,这里我下载的最新libpng-1.6.37版本。
2、阅读INSTALL文件进行编译

# prefix=path是可添加的附加参数,表示指定编译结果安装的目录
./configure --prefix=/home/study/libpng/libpng_build
make check
make install

3、编译后查看编译出内容:其中最关键的是静态库libpng16.a和动态库libpng16.so,还有输出的文档

├── bin
│   ├── // 略
├── include
│   ├── libpng16
│   │   ├── pngconf.h
│   │   ├── png.h
│   │   └── pnglibconf.h
│   ├── pngconf.h -> libpng16/pngconf.h
│   ├── png.h -> libpng16/png.h
│   └── pnglibconf.h -> libpng16/pnglibconf.h
├── lib
│   ├── libpng16.a
│   ├── // 略
│   ├── libpng.so -> libpng16.so
└── share
    └── man

3.2 libpng库的解码图片示例

接下来通过libpng库写一个png的解析程序,用来解析一张png图片,代码可以参考源码目录的example.c举例。
libpng的整体用法和libjpeg的设计非常类似,都是先注册一个异常跳转的调用函数方便调用库失败异常通知和处理,然后设置输入流可以来源文件或者内存,再调用解析头函数,然后可以查看到png文件各种属性,接下来根据需要有选择的进行一些输出属性设置(比如位宽控制,是否丢弃alpha通道,是否做缩放,格式转化 如把灰度数据转化为RGB存储数据,数据排序调整,滤波设置),然后申请内存,调用读取接口把png的数据读出。

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

int main(int argc, char *argv[]) {
	if (argc < 2) {
	    printf("please intput like: ./a.out xxx.png\n");
		return -1;
	}

	// 打开输入文件
	FILE *infile = fopen(argv[1], "rb");
	if (infile == NULL) {
	    printf("intput file %s open failed!\n", argv[1]);
	    return -1;
	}
	// 创建输出文件
	FILE *outfile = fopen("./output.bit", "w+");
	if (outfile == NULL) {
	    printf("out file open failed!\n");
	    return -1;
	}

	png_structp png_ptr = NULL;
	png_infop info_ptr = NULL;
	png_uint_32 width, height;
	int bit_depth, colortype, interlace_type;
	// 创建libpng库依赖的解码示例上下文
	png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0);
	if (png_ptr == NULL) {
   		printf("png_create_read_struct failed!\n");
      	return -1;
	}
	info_ptr = png_create_info_struct(png_ptr);
	if (info_ptr == NULL) {
   		printf("png_create_info_struct failed!\n");
      	return -1;
	}
	if (setjmp(png_jmpbuf(png_ptr))) {
   		printf("setjmp failed!\n");   	
      	return -1;
	}
	// 设置输入流
	png_init_io(png_ptr, infile);

	// 读取png文件头
	png_read_info(png_ptr, info_ptr);
	// 获取png文件解析信息
	png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &colortype, &interlace_type, NULL, NULL);
	// 根据获取文件信息做输出属性调整,得打ARGB8888的输出格式
	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深度强制8bit
	if(bit_depth == 16)
		png_set_strip_16(png_ptr); // 16bit深度要求强制8bit
	if(png_get_valid(png_ptr,info_ptr,PNG_INFO_tRNS))
		png_set_tRNS_to_alpha(png_ptr); // 扩展RGB数据的alpha通道到ARGB数据类型
	if(colortype == PNG_COLOR_TYPE_GRAY || colortype == PNG_COLOR_TYPE_GRAY_ALPHA)
		png_set_gray_to_rgb(png_ptr); //灰度必须转换成RGB
	
	// 申请读取size
	int size = width * height * 4; // 输出类型为argb8888每个像素4字节
	char *outbuf = malloc(size);
	if (outbuf == NULL) {
   		printf("malloc failed!\n");   	
      		return -1;
	}
	png_bytep *row_pointers = (png_bytep *)malloc(height * sizeof(char *)); // 申请行指针数组用于读取
	if (row_pointers == NULL) {
   		printf("malloc failed!\n");   	
      		return -1;
	}
	int row;
	for (row = 0; row < height; row++)
		row_pointers[row] = outbuf + row * width * 4;
	// 读取数据,这里使用一次读取完成的方式
	png_read_image(png_ptr, row_pointers);
	png_read_end(png_ptr, info_ptr);
	
	// 读取的数据写到输出文件
	fwrite(outbuf, size, 1, outfile);
	
	// 销毁解码示例上下文
	png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
	// 关闭全部文件
	free(outbuf);
	fclose(infile);
	fclose(outfile);
	return 0;
}

编译执行:

gcc sample_png2.c -lpng
./a.out ./test.png

解析出来的工具用裸数据 ARGB格式查看output.bit如下,说明解析ok:
在这里插入图片描述

参考

维基百科png介绍:https://chi.jinzhao.wiki/wiki/PNG
libpng官网:http://www.libpng.org/pub/png/libpng.html
libpng源码:https://sourceforge.net/projects/libpng/files/
https://www.w3.org/TR/PNG/#4Concepts.Sourceimage
https://dev.gameres.com/Program/Visual/Other/PNGFormat.htm
https://blog.csdn.net/hherima/article/details/45847043

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值