[TOC]
PNG图片格式
PNG(Portable Network Graphics) 是一种光栅化的,无损压缩的图片文件格式。其设计的目的是替换GIF,是目前网络中用得最广的无损压缩图片格式。我们可以用工具将前面的Bitmap转换为PNG。
下面是从上一章所说的 BMP转换过来的png_4x2_32bit.png的PNG图片,图片比较小,看仔细了:
PNG特点
请参考百度百科PNG图片特性
- 体积小
网络通讯中因受带宽制约,在保证图片清晰、逼真的前提下,网页中不可能大范围的使用文件较大的bmp格式文件。 - 无损压缩 PNG文件采用LZ77算法的派生算法进行压缩,其结果是获得高的压缩比,不损失数据。它利用特殊的编码方法标记重复出现的数据,因而对图像的颜色没有影响,也不可能产生颜色的损失,这样就可以重复保存而不降低图像质量。
- 索引彩色模式
PNG-8格式与GIF图像类似,同样采用8位调色板将RGB彩色图像转换为索引彩色图像。图像中保存的不再是各个像素的彩色信息,而是从图像中挑选出来的具有代表性的颜色编号,每一编号对应一种颜色,图像的数据量也因此减少,这对彩色图像的传播非常有利。 - 更优化的网络传输显示
PNG图像在浏览器上采用流式浏览,即使经过交错处理的图像会在完全下载之前提供浏览者一个基本的图像内容,然后再逐渐清晰起来。它允许连续读出和写入图像数据,这个特性很适合于在通信过程中显示和生成图像。 - 支持透明效果
PNG可以为原图像定义256个透明层次,使得彩色图像的边缘能与任何背景平滑地融合,从而彻底地消除锯齿边缘。这种功能是GIF和JPEG没有的。 - PNG同时还支持真彩和灰度级图像的Alpha通道透明度。
- 最高支持24位真彩色图像以及8位灰度图像。
- 支持Alpha通道的透明/半透明特性。
- 支持图像亮度的Gamma校准信息。
- 支持存储附加文本信息,以保留图像名称、作者、版权、创作时间、注释等信息。
- 渐近显示和流式读写,适合在网络传输中快速显示预览效果后再展示全貌。
- 使用CRC防止文件出错。
- 最新的PNG标准允许在一个文件内存储多幅图像。
PNG文件结构
PNG图像格式文件(或者称为数据流)由一个8字节的PNG文件署名(PNG file signature)域和按照特定结构组织的3个以上的数据块(chunk)组成。
我们以二进制的形式将PNG图png_4x2_32bit.png打开:
00000000: 8950 4e47 0d0a 1a0a 0000 000d 4948 4452 .PNG........IHDR
00000010: 0000 0004 0000 0002 0806 0000 007f a87d ...............}
00000020: 6300 0000 0173 5247 4200 aece 1ce9 0000 c....sRGB.......
00000030: 0004 6741 4d41 0000 b18f 0bfc 6105 0000 ..gAMA......a...
00000040: 0009 7048 5973 0000 0ec3 0000 0ec3 01c7 ..pHYs..........
00000050: 6fa8 6400 0000 1849 4441 5418 5763 0082 o.d....IDAT.Wc..
00000060: ffff 8118 4c81 2110 8029 b0e0 ffff 0005 ....L.!..)......
00000070: 2b10 f0fa b027 6800 0000 0049 454e 44ae +....'h....IEND.
00000080: 4260 82 B`.
- 文件头
PNG文件包含8字节的签名:
8950 4e47 0d0a 1a0a
值Value | 作用Purpose |
---|---|
89 |
超出了ASCII字符的范围,避免某些软件将图片当着文本来处理 |
50 4E 47 |
‘PNG’的ASCII值,PNG文件的标识 |
0D 0A |
Dos风格的的回车,检测DOS-Unix行结束数据的转换 |
1A |
Dos风格的换行符号 |
0A |
Unix风格的回车 |
- 数据块
PNG文件中包含3个以上的数据块,数据块间以特定的顺序组成。而数据块又分为关键数据块和辅助数据块。基本每个数据块都用下面的结构描述:
名称 | 字节数 | 说明 |
---|---|---|
Length (长度) | 4字节 | 指定数据块中数据域的长度,其长度不超过(231-1)字节 |
Chunk Type Code (数据块类型码) | 4字节 | 数据块类型码由ASCII字母(A-Z和a-z)组成 |
Chunk Data (数据块数据) | 可变长度 | 存储按照Chunk Type Code指定的数据 |
CRC (循环冗余检测) | 4字节 | 存储用来检测是否有错误的循环冗余码 |
- 关键数据块包括
数据块符号 | 数据块名称 | 多数据块 | 可选否 | 位置限制 |
---|---|---|---|---|
IHDR | 文件头数据块 | 否 | 否 | 第一块 |
IDAT | 图像数据块 | 是 | 否 | 与其他IDAT连续 |
IEND | 图像结束数据 | 否 | 否 | 最后一个数据块 |
和Bitmap相比,关键数据块多了一个IEND块。
- IHDR数据块
IHDR也称为文件头数据块,它包含有PNG文件中存储的图像数据的基本信息,并要作为第一个数据块出现在PNG数据流中,而且一个PNG数据流(文件)中只能有一个文件头数据块。
IHDR文件头数据块由13字节组成
00000000: ---- ---- ---- ---- 0000 000d 4948 4452 .PNG........IHDR
00000010: 0000 0004 0000 0002 0806 0000 007f a87d ...............}
00000020: 63-- ---- ---- ---- ---- ---- ---- ---- c....sRGB.......
它的格式如下表所示:
offset | 值 Value | 域的名称 | 字节数 | 说明 |
---|---|---|---|---|
0000 000d |
13 | size | 4 | 数据块大小 |
4948 4452 |
IHDR |
type | IHDR的ASCII | 数据块的类型 |
0000 0004 |
4 | Width | 4 bytes | 图像宽度,以像素为单位 |
0000 0002 |
2 | Height | 4 bytes | 图像高度,以像素为单位 |
08 |
8 | Bit depth | 1 byte | 图像深度: 索引彩色图像:1,2,4或8 灰度图像:1,2,4,8或16 真彩色图像:8或16 |
06 |
6 | ColorType | 1 byte | 颜色类型: 0:灰度图像, 1,2,4,8或16 2:真彩色图像,8或16 3:索引彩色图像,1,2,4或8 4:带α通道数据的灰度图像,8或16 6:带α通道数据的真彩色图像,8或16 |
00 |
0 | Compression method | 1 byte | 压缩方法(LZ77派生算法) |
00 |
0 | Filter method | 1 byte | 滤波器方法 |
00 |
0 | Interlace method | 1 byte | 隔行扫描方法: 0:非隔行扫描 1: Adam7(由Adam M. Costello开发的7遍隔行扫描方法) |
7fa8 7d63 |
CRC | 4 | CRC校验码 |
按照我们的文件来~
- sRGB数据块
00000020: --00 0000 0173 5247 4200 aece 1ce9 ---- c....sRGB.......
offset | 值 Value | 域的名称 | 字节数 | 说明 |
---|---|---|---|---|
00 0000 01 |
1 | size | 4 | 数据块大小 |
73 5247 42 |
sRGB |
type | sRGB的ASCII | 数据块的类型 |
00 |
0 | sRGB模式 | 1 bytes | 有0 1 2 3 四种模式可选,可以参考W3对sRGB的介绍
|
aece 1ce9 |
CRC | 4 | CRC校验码 |
不是所有的解码器都支持sRGB,所以有sRGB数据块,就必须有gAMA 数据块,也可以有cHRM 数据块,以兼容不支持sRGB的解码器。
- gAMA 数据块
00000020: ---- ---- ---- ---- ---- ---- ---- 0000 c....sRGB.......
00000030: 0004 6741 4d41 0000 b18f 0bfc 6105 0000 ..gAMA......a...
offset | 值 Value | 域的名称 | 字节数 | 说明 |
---|---|---|---|---|
0000 0004 |
4 | size | 4 | 数据块大小 |
6741 4d41 |
gAMA |
type | gAMA 的ASCII | 数据块的类型 |
0000 b18f |
0 | gama校验 | 1 bytes | gama校验 |
0bfc 6105 |
CRC | 4 | CRC校验码 |
如果有sRGB数据块或者iCCP数据块,gAMA数据块就不用了,被override。
- pHYs 数据块
期望的物理像素的尺寸,或比例当显示图片时。
00000030: ---- ---- ---- ---- ---- ---- ---- 0000 ..gAMA......a...
00000040: 0009 7048 5973 0000 0ec3 0000 0ec3 01c7 ..pHYs..........
00000050: 6fa8 64-- ---- ---- ---- ---- ---- ---- o.d....IDAT.Wc..
offset | 值 Value | 域的名称 | 字节数 | 说明 |
---|---|---|---|---|
0000 0009 |
9 | size | 4 | 数据块大小 |
7048 5973 |
pHYs |
type | pHYs 的ASCII | 数据块的类型 |
0000 0ec3 |
3779 | x axis | 4 bytes | gama校验 |
0000 0ec3 |
3779 | y axis | 4 bytes | gama校验 |
01 |
1 | Unit specifier | 1 | 单位说明 |
c76f a8 64 |
CRC | 4 | CRC校验码 |
Unit specifier,有下列定义的值:
0:unit is unknown;只是定义了缩放比例
1:unit is the metre
接下来终于到我们的数据块了
- IDAT 数据块
关键数据块,这里就数据就是png图片的数据
00000050: ---- --00 0000 1849 4441 5418 5763 0082 o.d....IDAT.Wc..
00000060: ffff 8118 4c81 2110 8029 b0e0 ffff 0005 ....L.!..)......
00000070: 2b10 f0fa b027 6800 0000 0049 454e 44ae +....'h....IEND.
00000080: 4260 82 B`.
offset | 值 Value | 域的名称 | 字节数 | 说明 |
---|---|---|---|---|
00 0000 18 |
24 | size | 4 | 数据块大小 |
49 4441 54 |
IDAT |
type | IDAT 的ASCII | 数据块的类型 |
1857 6300 82ff ff81 184c 8121 1080 29b0 e0ff ff00 052b 10f0
|
png编码数据 | 24 | png编码数据 | |
fab0 2768 |
CRC | 4 | CRC校验码 |
- IEND 数据块
关键数据块
00000070: ---- ---- ---- --00 0000 0049 454e 44ae +....'h....IEND.
00000080: 4260 82 B`.
offset | 值 Value | 域的名称 | 字节数 | 说明 |
---|---|---|---|---|
00 0000 00 |
4 | size | 4 | 数据块大小 |
49 454e 44 |
IEND |
type | IEND 的ASCII | 数据块的类型 |
ae 4260 82 |
CRC | 4 | CRC校验码 |
以上就是PNG文件结构的描述~~~
PNG压缩原理
这部分请参考原博客: PNG格式图片原理
PNG的压缩过程是完全无损的,压缩过的文件可以准确的还原出原图,这要分两个阶段完成:推断(又称过滤[filtering])和压缩。
过滤
差分编码(Delta encoding)是最强大的数字压缩法之一。原理是根据前一个数据的值将后面的值替换成其他值,例如:
[2,3,4,5,6,7,8]可以变成[2,1,1,1,1,1,1],算法是
[2, 3-2=1, 4-3=1, 5-4=1, 6-5=1, 7-6=1, 8-7=1]
这样看就很明显了,如果你的数据是线性相关的(线性相关的意思是,一组数据里的前后值都差别不大,或者具有相关性),就可以把你的数据集转换成一组重复的、低的值,这样的值更容易被压缩。
PNG格式使用了差分编码(Delta encoding)里的过滤。原理是,对于每一行的像素,一个当前的像素都跟它的左边像素、上边的像素和左上角的像素有关系。
举个例子,如果我们要编码一个给定的像素通过它与A和B平均值的差异(X-(A+B)/2),那么我们将得到:
我们使用了ABC去推断出X的值,然后我们将X替换成更小的值。
需要注意的是,每一行的像素都有可能不同,PNG允许5种不同的推断算法,它们是:
- 不过滤
- X-A
- X-B
- X-(A+B)/2(又称平均值)
Paeth推断(A,B,C的线性方法,这种比较复杂可看W3C的规定)
这里说明一下,每一行像素应该选择最适合的过滤算法,这样才能得到最少数量的特殊值。下面是我们关于不同模式的例子:
需要注意的是这些过滤器都是对每一行像素起作用而不是单个像素。也就是说过滤器会对每一行的红色像素起作用,再分别对蓝色的像素起作用。(尽管同一行的像素会用同样的过滤器)
现在PNG格式在选择过滤器上有一些不错的方法,开发人员根据对不同类型图片的使用经验摸索出一些不错的规律。例如对于调色板的图像(palette images)和8位的灰色图就不要过滤。对于其他图片,就选择那种能最大限度地减少绝对差异总和的值的过滤器:将所有值的绝对值相加,然后对比不同过滤器得到的值,选择那个相加起来得到最小值的过滤器。
压缩
在一行像素被过滤后,就会执行DEFLATE压缩,这是LZ77延伸出来的一种算法。该算法结合了LZ77编码和哈夫曼编码,它跟PKWARE、PKZIP、GZIP等差不多相同。这种实现方式虽然是现成的,但用在压缩图片数据上,还是有一些需要注意的点:
- Deflate算法只能匹配3到258个之间符号,所以最大的压缩比只能到1035:1;
- 如果匹配到的符号小于3,那么你会产生一些额外的开销来表示这些符号;
上面的这两点意味着你的图片大小会受到每一行像素的匹配程度影响。
你可以看一下面这两张图片,左边那张270x90的图只有20k,而右边那张270x92的图是左边那张的2倍大。
这似乎不符合逻辑,一张图片多540像素在压缩率上就少了一半。不过我们看仔细点,就能知道原因了,下面这张图表示压缩器怎么去压缩一个给定的像素的。深蓝色代表压缩率很高的区域,黄色/红色代表没怎么被压缩的区域。
这是怎么出现的呢,原因是小图的每一行像素的匹配度更高,那么它的压缩率就更高。你要是调整了大小,匹配度一变化,这种情况就有可能出现。一些潜在的匹配对象不在压缩区域里,它们没有匹配到,这就又可能导致出现一张大图。
如果你想知道你的PNG图片的压缩率如何,可以下个PNGThermal看一下。
PNG图片编解码 libpng
PNG图片的编解码,已经有很多开源的项目。libpng都比较成熟的官方的png编解码器,以动态库的形式提供,使用时load libpng的库就可以使用。LodePNG是一个集成度比较高的编解码器,没有依赖,不用加载zlib或libpng等,直接可以使用, https://github.com/lvandeve/lodepng。 LodePNG建议大家去了解一下,通过LodePNG能够很好理解PNG的编解码实现。我们这里侧重用libpng使用。
Android平台上,libpng的源码在external/libpng
目录下;当前的版本应该是16.34。
https://sourceforge.net/projects/libpng/files/libpng16/1.6.34/libpng-1.6.34.tar.gz
png采用的压缩算法是分开的:
git clone https://github.com/madler/zlib.git
我们用Android Studio,创建一个纯Native的应用,直接在 native中去编译,使用libpng和zlib。
样例代码请参考github Codec-PngCodec , 这是我们的PngCodec的文件结构:
├── build.gradle
├── CMakeLists.txt
├── libs
├── png
│ └── libpng-1.6.34
├── src
│ ├── AndroidManifest.xml
│ ├── cpp
│ │ └── PngCodecNativeActivity.cpp
│ ├── java
│ └── res
└── zlib
└── zlib-1.2.11
将libpng和zlib,放到工程目录下。将工程连到CMakeLists.txt。
zlib的CMake
add_library( zlib
STATIC
zlib/zlib-1.2.11/adler32.c
zlib/zlib-1.2.11/compress.c
zlib/zlib-1.2.11/crc32.c
zlib/zlib-1.2.11/deflate.c
zlib/zlib-1.2.11/gzclose.c
zlib/zlib-1.2.11/gzlib.c
zlib/zlib-1.2.11/gzread.c
zlib/zlib-1.2.11/gzwrite.c
zlib/zlib-1.2.11/infback.c
zlib/zlib-1.2.11/inflate.c
zlib/zlib-1.2.11/inftrees.c
zlib/zlib-1.2.11/inffast.c
zlib/zlib-1.2.11/trees.c
zlib/zlib-1.2.11/uncompr.c
zlib/zlib-1.2.11/zutil.c )
target_include_directories(zlib PRIVATE
zlib/zlib-1.2.11 )
libpng的CMake
add_library( png
STATIC
png/libpng-1.6.34/png.c
png/libpng-1.6.34/pngerror.c
png/libpng-1.6.34/pngget.c
png/libpng-1.6.34/pngmem.c
png/libpng-1.6.34/pngpread.c
png/libpng-1.6.34/pngread.c
png/libpng-1.6.34/pngrio.c
png/libpng-1.6.34/pngrtran.c
png/libpng-1.6.34/pngrutil.c
png/libpng-1.6.34/pngset.c
png/libpng-1.6.34/pngtrans.c
png/libpng-1.6.34/pngwio.c
png/libpng-1.6.34/pngwrite.c
png/libpng-1.6.34/pngwtran.c
png/libpng-1.6.34/pngwutil.c )
target_include_directories(png PRIVATE
png/libpng-1.6.34
zlib/zlib-1.2.11 )
native应用的CMake
add_library( png_codec
SHARED
src/main/cpp/PngCodecNativeActivity.cpp )
target_include_directories(png_codec PRIVATE
${ANDROID_NDK}/sources/android/native_app_glue
png/libpng-1.6.34
zlib/zlib-1.2.11 )
target_link_libraries( png_codec
native_activity_glue
android
zlib
png
log )
Native应用我们编译为libpng_codec.so,在AndroidManifest中使用:
<application
...
android:hasCode="false">
<activity android:name="android.app.NativeActivity">
<meta-data
android:name="android.app.lib_name"
android:value="png_codec" />
编译时,pnglibconf.h需要放在png/libpng-1.6.34目录下,从scripts/pnglibconf.h.prebuilt拷贝。我们这边注释掉NEON
的支持。
下面是一个简易的读取png图片的实现:
void readPngFile(char *name) {
ALOGE("readPngFile %s\n", name);
// 前边几句是扯淡,初始化各种结构
FILE *file = fopen(name, "rb");
png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0);
png_infop info_ptr = png_create_info_struct(png_ptr);
setjmp(png_jmpbuf(png_ptr));
// 这句很重要
png_init_io(png_ptr, file);
// 读文件了
png_read_png(png_ptr, info_ptr, PNG_TRANSFORM_EXPAND, 0);
// 得到文件的宽高色深
int m_width = png_get_image_width(png_ptr, info_ptr);
int m_height = png_get_image_height(png_ptr, info_ptr);
int color_type = png_get_color_type(png_ptr, info_ptr);
// 申请个内存玩玩,这里用的是c++语法,甭想再c上编过
int size = m_height * m_width * 4;
char* head = static_cast<char*>(malloc(size));
int pos = 0;
// row_pointers里边就是传说中的rgba数据了
png_bytep *row_pointers = png_get_rows(png_ptr, info_ptr);
// 拷贝!!注意,如果你读取的png没有A通道,就要3位3位的读。还有就是注意字节对其的问题,最简单的就是别用不能被4整除的宽度就行了。读过你实在想用,就要在这里加上相关的对齐处理。
ALOGE("png file %s size(%dx%d) pixles:", name, m_width, m_height);
for (int i = 0; i < m_height; i++) {
for (int j = 0; j < (4 * m_width); j += 4) {
head[pos] = row_pointers[i][j + 2]; // blue
pos++;
head[pos] = row_pointers[i][j + 1]; // green
pos++;
head[pos] = row_pointers[i][j]; // red
pos++;
head[pos] = row_pointers[i][j + 3]; // alpha
pos++;
ALOGE("%02x %02x %02x %02x", head[pos-4], head[pos-3], head[pos-2], head[pos-1]);
}
}
free(head);
// 好了,你可以用这个数据作任何的事情了。。。把它显示出来或者打印出来都行。
png_destroy_read_struct(&png_ptr, &info_ptr, 0);
fclose(file);
return;
}
我们将我们的png_4x2_32bit.png
,读出来,pixels为:
0000 00 ff 0000 ffff ff00 00ff 00ff 00ff
ffff ffff 00ff 00ff 0000 ffff ff00 00ff
还记得bmp的时候,你要转为png之前,pixels是什么样的吗?
ffff ff7f 00ff 007f 0000 ff7f ff00 007f 0000 0000 0000 ff00 ff00 0000 00ff 0000
对比一下:
- 转换的时候,Alpha被去掉了,都设置为不透明的
- png的扫描方式和bmp吧一样,png是从左上开始扫,bmp却是从左下。
再回过头看看测试代码:
png信息用结构体png_inforp
描述,本体是png_info_def
,定义在pnginfo.h中。
struct png_info_def
{
/* The following are necessary for every PNG file */
png_uint_32 width; /* width of image in pixels (from IHDR) */
png_uint_32 height; /* height of image in pixels (from IHDR) */
png_uint_32 valid; /* valid chunk data (see PNG_INFO_ below) */
png_size_t rowbytes; /* bytes needed to hold an untransformed row */
png_colorp palette; /* array of color values (valid & PNG_INFO_PLTE) */
png_uint_16 num_palette; /* number of color entries in "palette" (PLTE) */
png_uint_16 num_trans; /* number of transparent palette color (tRNS) */
png_byte bit_depth; /* 1, 2, 4, 8, or 16 bits/channel (from IHDR) */
png_byte color_type; /* see PNG_COLOR_TYPE_ below (from IHDR) */
/* The following three should have been named *_method not *_type */
png_byte compression_type; /* must be PNG_COMPRESSION_TYPE_BASE (IHDR) */
png_byte filter_type; /* must be PNG_FILTER_TYPE_BASE (from IHDR) */
png_byte interlace_type; /* One of PNG_INTERLACE_NONE, PNG_INTERLACE_ADAM7 */
... ...
};
png文件用结构体png_structp
描述,本体是png_struct_def
,定义在pngstruct.h中。
struct png_struct_def
{
#ifdef PNG_SETJMP_SUPPORTED
jmp_buf jmp_buf_local; /* New name in 1.6.0 for jmp_buf in png_struct */
png_longjmp_ptr longjmp_fn;/* setjmp non-local goto function. */
jmp_buf *jmp_buf_ptr; /* passed to longjmp_fn */
size_t jmp_buf_size; /* size of the above, if allocated */
#endif
png_error_ptr error_fn; /* function for printing errors and aborting */
#ifdef PNG_WARNINGS_SUPPORTED
png_error_ptr warning_fn; /* function for printing warnings */
#endif
png_voidp error_ptr; /* user supplied struct for error functions */
png_rw_ptr write_data_fn; /* function for writing output data */
png_rw_ptr read_data_fn; /* function for reading input data */
png_voidp io_ptr; /* ptr to application struct for I/O functions */
... ...
libpng都是用c写的,用的结构体,这样实现,C/C++中都可以用。
png信息的读取,用的png_read_png接口,定义在pngread.c中。在png_read_info函数中,采用循环的模式,将PNG的所有数据块数据都读出来。比如IHDR数据块,采用png_handle_IHDR进行处理。
/* Read and check the IDHR chunk */
void /* PRIVATE */
png_handle_IHDR(png_structrp png_ptr, png_inforp info_ptr, png_uint_32 length)
{
png_byte buf[13];
png_uint_32 width, height;
int bit_depth, color_type, compression_type, filter_type;
int interlace_type;
png_debug(1, "in png_handle_IHDR");
if ((png_ptr->mode & PNG_HAVE_IHDR) != 0)
png_chunk_error(png_ptr, "out of place");
/* Check the length */
if (length != 13)
png_chunk_error(png_ptr, "invalid");
png_ptr->mode |= PNG_HAVE_IHDR;
png_crc_read(png_ptr, buf, 13);
png_crc_finish(png_ptr, 0);
width = png_get_uint_31(png_ptr, buf);
height = png_get_uint_31(png_ptr, buf + 4);
bit_depth = buf[8];
color_type = buf[9];
compression_type = buf[10];
filter_type = buf[11];
interlace_type = buf[12];
/* Set internal variables */
png_ptr->width = width;
png_ptr->height = height;
png_ptr->bit_depth = (png_byte)bit_depth;
png_ptr->interlaced = (png_byte)interlace_type;
png_ptr->color_type = (png_byte)color_type;
#ifdef PNG_MNG_FEATURES_SUPPORTED
png_ptr->filter_type = (png_byte)filter_type;
#endif
png_ptr->compression_type = (png_byte)compression_type;
/* Find number of channels */
switch (png_ptr->color_type)
{
default: /* invalid, png_set_IHDR calls png_error */
case PNG_COLOR_TYPE_GRAY:
case PNG_COLOR_TYPE_PALETTE:
png_ptr->channels = 1;
break;
case PNG_COLOR_TYPE_RGB:
png_ptr->channels = 3;
break;
case PNG_COLOR_TYPE_GRAY_ALPHA:
png_ptr->channels = 2;
break;
case PNG_COLOR_TYPE_RGB_ALPHA:
png_ptr->channels = 4;
break;
}
/* Set up other useful info */
png_ptr->pixel_depth = (png_byte)(png_ptr->bit_depth * png_ptr->channels);
png_ptr->rowbytes = PNG_ROWBYTES(png_ptr->pixel_depth, png_ptr->width);
png_debug1(3, "bit_depth = %d", png_ptr->bit_depth);
png_debug1(3, "channels = %d", png_ptr->channels);
png_debug1(3, "rowbytes = %lu", (unsigned long)png_ptr->rowbytes);
png_set_IHDR(png_ptr, info_ptr, width, height, bit_depth,
color_type, interlace_type, compression_type, filter_type);
}
IHDR的大小为13,所以,直接申请13byte的buf。信息保存到info_ptr中。
图片数据,通过png_read_image进行获取。
void PNGAPI
png_read_image(png_structrp png_ptr, png_bytepp image)
{
png_uint_32 i, image_height;
int pass, j;
png_bytepp rp;
png_debug(1, "in png_read_image");
if (png_ptr == NULL)
return;
#ifdef PNG_READ_INTERLACING_SUPPORTED
if ((png_ptr->flags & PNG_FLAG_ROW_INIT) == 0)
{
pass = png_set_interlace_handling(png_ptr);
/* And make sure transforms are initialized. */
png_start_read_image(png_ptr);
}
else
{
if (png_ptr->interlaced != 0 &&
(png_ptr->transformations & PNG_INTERLACE) == 0)
{
/* Caller called png_start_read_image or png_read_update_info without
* first turning on the PNG_INTERLACE transform. We can fix this here,
* but the caller should do it!
*/
png_warning(png_ptr, "Interlace handling should be turned on when "
"using png_read_image");
/* Make sure this is set correctly */
png_ptr->num_rows = png_ptr->height;
}
/* Obtain the pass number, which also turns on the PNG_INTERLACE flag in
* the above error case.
*/
pass = png_set_interlace_handling(png_ptr);
}
#else
if (png_ptr->interlaced)
png_error(png_ptr,
"Cannot read interlaced image -- interlace handler disabled");
pass = 1;
#endif
image_height=png_ptr->height;
for (j = 0; j < pass; j++)
{
rp = image;
for (i = 0; i < image_height; i++)
{
png_read_row(png_ptr, *rp, NULL);
rp++;
}
}
}
在png_read_row中,通过png_read_IDAT_data去获取pixels数据。这里的数据是经过压缩的,需要用到zlib进行解压缩。解压用的zlib的inflateInit() + inflate() + inflateEnd()
等函数。
解压用到z_stream_s结构,用以读写zlib输入输出的数据。
typedef struct z_stream_s {
z_const Bytef *next_in; /* next input byte */
uInt avail_in; /* number of bytes available at next_in */
uLong total_in; /* total number of input bytes read so far */
Bytef *next_out; /* next output byte will go here */
uInt avail_out; /* remaining free space at next_out */
uLong total_out; /* total number of bytes output so far */
z_const char *msg; /* last error message, NULL if no error */
struct internal_state FAR *state; /* not visible by applications */
alloc_func zalloc; /* used to allocate the internal state */
free_func zfree; /* used to free the internal state */
voidpf opaque; /* private data object passed to zalloc and zfree */
int data_type; /* best guess about the data type: binary or text
for deflate, or the decoding state for inflate */
uLong adler; /* Adler-32 or CRC-32 value of the uncompressed data */
uLong reserved; /* reserved for future use */
} z_stream;
我们可以自己加log,将解压的PNG图片的像素数据打印出来
PngCodec: png_show_byte bnext_in 1857630082FFFF81184C8121108029B0E0FFFF00052B10F0
这和我们用二进制编辑器编辑png图片时的文件是不是一样的?
- libpng编码
编码和解码类似,编码时提供png_write_info
和png_write_image
,一个写Png文件信息,一个写像素点。我们来看实例。前面我们用png转码工具将bmp转换为png时,丢了Alpha信息,我们现在将Alpha信息加上。
void writePngFile(char *fileName, png_byte* src , int width, int height)
{
png_structp png_ptr;
png_infop info_ptr;
png_colorp palette;
FILE *fp = fopen(fileName, "wb");
if (fp == NULL)
return ;
png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
if (png_ptr == NULL)
{
fclose(fp);
return ;
}
/* Allocate/initialize the image information data. REQUIRED */
info_ptr = png_create_info_struct(png_ptr);
if (info_ptr == NULL)
{
fclose(fp);
png_destroy_write_struct(&png_ptr, NULL);
return ;
}
if (setjmp(png_jmpbuf(png_ptr)))
{
/* If we get here, we had a problem writing the file */
fclose(fp);
png_destroy_write_struct(&png_ptr, &info_ptr);
return ;
}
/* 接下来告诉 libpng 用 fwrite 来写入 PNG 文件,并传给它已按二进制方式打开的 FILE* fp */
png_init_io(png_ptr, fp);
/* 设置png文件的属性 */
png_set_IHDR(png_ptr, info_ptr, width, height, 8,
PNG_COLOR_TYPE_RGB,
PNG_INTERLACE_NONE,
PNG_COMPRESSION_TYPE_BASE,
PNG_FILTER_TYPE_BASE);
/* 分配调色板空间。常数 PNG_MAX_PALETTE_LENGTH 的值是256 */
palette = (png_colorp)png_malloc(png_ptr, PNG_MAX_PALETTE_LENGTH * sizeof(png_color));
png_set_PLTE(png_ptr, info_ptr, palette, PNG_MAX_PALETTE_LENGTH);
/* Write the file header information. REQUIRED */
png_write_info(png_ptr, info_ptr);
/* The easiest way to write the image (you may have a different memory
* layout, however, so choose what fits your needs best). You need to
* use the first method if you aren't handling interlacing yourself.
*/
png_uint_32 k;
png_byte *image;
png_bytep row_pointers[height];
image = src;
if (height > PNG_UINT_32_MAX/sizeof(png_bytep))
png_error (png_ptr, "Image is too tall to process in memory");
for (k = 0; k < height; k++)
row_pointers[k] = image + k*width*3;
/* One of the following output methods is REQUIRED */
png_write_image(png_ptr, row_pointers);
//end,进行必要的扫尾工作:
png_write_end(png_ptr, info_ptr);
png_free(png_ptr, palette);
png_destroy_write_struct(&png_ptr, &info_ptr);
fclose(fp);
ALOGE("success write png file %s\n", fileName);
return ;
}
记住,PNG图片是从左上开始扫描的,切格式为RGBA,所以,pixel是数据要对应的调整一下。
void buildPngFile(char *fileName) {
int width = 4;
int height = 2;
png_byte src[] = {
0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0xff, 0x00, 0xff,
0xff, 0xff, 0xff, 0x7f, 0x00, 0xff, 0x00, 0x7f, 0xff, 0x00, 0x00, 0x7f, 0x00, 0x00, 0xff, 0x7f
};
writePngFile(fileName,src,width, height, PNG_COLOR_TYPE_RGBA);
readPngFile(fileName);
}
log打出来的数据:
~ E/PngCodec: 00 00 00 ff
~ E/PngCodec: 00 00 ff ff
~ E/PngCodec: ff 00 00 ff
~ E/PngCodec: 00 ff 00 ff
~ E/PngCodec: ff ff ff 7f
~ E/PngCodec: 00 ff 00 7f
~ E/PngCodec: 00 00 ff 7f
~ E/PngCodec: ff 00 00 7f
保留Alpha后的PNG图片如下:
放大后的效果:
上面一行不透明,下面一行半透明。
Android平台上,一般不直接去调用libpng的库,Android对编解码提供了统一的管理,这个角色为skia。skia中除了png还可以用其他的,比如jpg,gif等。
Png就介绍到这里,简要的介绍了PNG文件格式,以及用libpng对Png图片进行编解码。再深的,大家继续去探索。
实例代码可以高github里面去下载哦~
Codec-PngCodec