VC6 下 libpng 库的编译与初步使用
目录
- libong 库的介绍
- VC6 下 libpng 的编译
- 下载 libpng 与 zlib
- 进行编译
- 得到 .lib 文件
- 初步使用
- 对 VC6 进行设置
- 将 .lib 文件添加到工程设置中
- 使用 libpng 检测文件是否为 png 格式的图像
- libpng 官方手册
一、libpng 库的介绍
libpng 用于处理 png 格式的图片, 是一套比较完善的 png 图片处理库, 免费、开源, 因此受到了很高的好评。遗憾的是, 它的官方网站: htp://www.libpng.org/ 现已不能直接访问。但是我们依然能够通过 sourceforge 来下载所需的相关文件。
libpng 在 sourceforge 上的项目地址: http://sourceforge.net/projects/libpng/
目前最新版本的 libpng 为 1.61。
二、VC6 下 libpng 的编译
1. 下载 libpng 与 zlib
笔者所用的IDE依然为 Visual Studio 6.0 ( VC++ 6 ), 所以这里以 VC6 下的编译\使用来作介绍。
首先下载两个库, 一个是 libpng , 另一个则是 zlib 库, zlib 库是一套用于压缩数据的库, libpng 借助了该库作为压缩引擎, 也就是说, libpng 依赖于 zlib 库。
关于这两个库的版本选择, 最新版本的 libpng 和 zlib 都没有提供适用于 VC6 的工程文件, 但是提供的有 vc 9\10 的, 当然, 新版本的 VS 可以将旧的工程文件来进行转换, 但是反过来不行。 因此要下载到适合 VC6 进行编译的版本。
笔者的选择是:
- libpng 1.4.12 版本, 下载地址: http://sourceforge.net/projects/libpng/files/libpng14/1.4.12/lpng1412.zip/download
- zlib 1.2.3 版本, 下载地址: http://sourceforge.net/projects/libpng/files/zlib/1.2.3/zlib123.zip/download
如果使用的是更高版本的VS, 那么你也可以从该页 http://sourceforge.net/projects/libpng/files/ 选择适合你的 libpng 和 zlib。
2. 进行编译
将下载到的文件( lpng1412.zip、zlib123.zip )进行解压后(任意目录, 例如: E:\\lpng1412), 进入到 libpng 的解压目录, 找到 libpng 的工作空间文件, 位于:
\\lpng1412\\projects\\visualc6\\libpng.dsw
将该工作区文件打开时, VC6会提示要找到 zlib 库的工程文件, 如图:
这时通过文件选择对话框找到 zlib 工程文件 zlib.dsp 所在的位置后, 点OK进行确定。 zlib 工程文件位于 zlib 解压文件夹中的:
\\zlib123\\projects\\visualc6\\zlib.dsp
此时工作区中有三个项目, libpng、pngtest、zlib, 在组建工具栏中, 如图:
选择需要的编译方式, 如果你的 VC6 常用工具栏中没有如图所示的选项, 右键工具栏的空白处, 在弹出的菜单中选择 "Build" 将该工具栏调出, VC6安装时默认只有一个 "Build MiniBar"。
一般情况下, 我们需要以 .lib 方式进行调用, 所以这里使用 Win32 LIB Debug 和 Win32 LIB Release 两种模式对 libpng 各进行一次编译, 也就得到了两个 lib 版本, 一个份 debug 的, 一份是 release 的。
如果需要以 dll 方式进行调用( Win32 DLL Debug\Release ), 或者打算给 VB 进行调用( Win32 DLL VB ), 那么也可以根据自己需要选择其他的编译方式。
3. 得到 .lib 文件
编译完成后, 如果没有出现错误, 此时在 libpng 和 zlib 库的工程文件所在的文件夹下均会出现 Win32_LIB_Debug 和 Win32_LIB_Release 两个文件夹, 检查这4个文件夹中是否有以下4个文件:
- 1>. zlib 库:
- zlibd.lib ( Win32_LIB_Debug 模式生成 )
- zlib.lib ( Win32_LIB_Release 模式生成 )
- 2>. libpng 库:
- libpngd.lib ( Win32_LIB_Debug 模式生成 )
- libpng.lib ( Win32_LIB_Release 模式生成 )
如果存在, 恭喜, 编译完成。
三、初步使用
1. 对 VC6 进行设置
库虽然已经编译完成了, 但是如果要使用, 我们还得将其加入到 VC6 的目录中, 让 VC6 在编译链接时能够找到这两个库。
菜单栏 -> 工具(Tools) -> 选项(Options) -> 目录选项卡(Directories)
在 include files 选项中, 将 libpng 和 zlib 库所在的文件夹加入其中(.h\.c源码文件所在的文件夹)。
在 library files 选项中, 将刚刚编译得到的 .lib 文件所在的文件夹加入其中。
完成后如上图所示, 当然, 如果你嫌lib路径中一次添加4个文件夹路径有点浪费, 那么你完全可以将那 4 个 .lib 文件放在一个文件夹下再添加进去, 或者直接复制到已有的 lib 路径文件夹下都行。
2. 将 .lib 文件添加到工程设置中
新建一个工程, 工程类型就选 Win32 Console Application 的空工程吧, 在工程设置(Projet Settings)中 ( 工作空间 -> 右键工程 -> 设置...(Settings...) 的 链接(Link) 选项卡中的 对象\库模块(Object\library modules) 中添加刚刚编译得到的 .lib 文件名称.
Win32 Debug 添加 zlibd.lib libpngd.lib , Win32 Release 模式添加 zlib.lib libpng.lib
如果你不想这样做, 也可以使用 #pragma comment(lib, "libpng.lib") 这个的命令来完成。
3. 使用 libpng 检测文件是否为 png 格式的图像
假设上面的步骤都已经完成后, 我们来写一段代码测试下 libpng 是否已经可用, 这段代码的作用是检查一个文件是否为 png 格式的图片:
按 Ctrl+C 复制代码
#include
#include "png.h" // libpng 库的一个重要头文件 int main() { int is_png; //是否为png char cbHeader[8]; //文件头 FILE *fp = fopen( "test.png", "rb" ); if( !fp ) { puts( "文件打开错误!" ); return 0; } fread( cbHeader, 1, 8, fp ); //从文件中读取文件头 is_png = png_sig_cmp( cbHeader, 0, 8 ); //检测该文件头是否为 png 格式的图片 is_png == 0 ? puts( "是png" ) : puts( "不是png" ); return 0; }
按 Ctrl+C 复制代码
编译运行, 会发现出现了几下如下的错误:
Compiling...
main.c
Linking...
MSVCRT.lib(MSVCRT.dll) : error LNK2005: __snprintf already defined in LIBCD.lib(snprintf.obj)
MSVCRT.lib(MSVCRT.dll) : error LNK2005: _malloc already defined in LIBCD.lib(dbgheap.obj)
MSVCRT.lib(MSVCRT.dll) : error LNK2005: _free already defined in LIBCD.lib(dbgheap.obj)
LINK : warning LNK4098: defaultlib "MSVCRT" conflicts with use of other libs; use /NODEFAULTLIB:library
Debug/libpng_demo.exe : fatal error LNK1169: one or more multiply defined symbols found
Error executing link.exe.
libpng_demo.exe - 4 error(s), 1 warning(s)
大致的意思是说 xxx 在 LIBCD.lib 中已经有了定义, 这个比较好解决, 我们选择将 LIBCD.lib 这个库进行忽略即可, 忽略的方式为, 进入工程设置, 找到 Link 选项卡, 在分类(Categor)中选择 输入(Input), 下面有个 忽略库(Ignore libraries), 将 LIBCD.lib 添加进去确定。如果选择Win32 Release 模式, 也会出现这个错误, 不过重复定义的库变成了 LIBC.lib, 同样方式进行忽略即可。
再次编译运行, 错误即消失, 0 error(s), 0 warning(s), 十分惬意。
代码解说:
在上段代码中, 我们运用了 libpng 提供的一个 png_sig_cmp 函数, 该函数的作用是通过文件头检测文件是否为png格式的图片, 对于 png 格式的图片, 文件开头的 8 字节来表示该文件是不是PNG文件(详见png的文件结构), 当 是png时, 函数返回 0。
因此, 在代码中我们定义了 8 个字节的存储空间 char cbHeader[8]; 并且使用标准输入输出函数读取了文件的前8个字节的内容, 将得到的文件头再交给 png_sig_cmp 进行判断。
笔者也是刚接触这个 libpng 库, 对于详细的使用方法还有待研究, 由于官网... 就不说了, 相关的完整资料也不太好找, 不过还是通过.找到了一份完整的文档(pdf, 英文)。
下载地址: http://files.cnblogs.com/mr-wid/libpng-1.4.0-manual.zip
PNG文件结构分析
PNG是20世纪90年代中期开始开发的图像文件存储格式,其目的是企图替代GIF和TIFF文件格式,同时增加一些GIF文件格式所不具备的特性。流式网络图形格式(Portable Network Graphic Format,PNG)名称来源于非官方的“PNG's Not GIF”,是一种位图文件(bitmap file)存储格式,读成“ping”。PNG用来存储灰度图像时,灰度图像的深度可多到16位,存储彩色图像时,彩色图像的深度可多到48位,并且还可存储多到16位的α通道数据。PNG使用从LZ77派生的无损数据压缩算法。
PNG数据块(Chunk)
PNG定义了两种类型的数据块,一种是称为关键数据块(critical chunk),这是标准的数据块,另一种叫做辅助数据块(ancillary chunks),这是可选的数据块。关键数据块定义了4个标准数据块,每个PNG文件都必须包含它们,PNG读写软件也都必须要支持这些数据块。虽然PNG文件规范没有要求PNG编译码器对可选数据块进行编码和译码,但规范提倡支持可选数据块。
下表就是PNG中数据块的类别,其中,关键数据块部分我们使用深色背景加以区分。
PNG文件格式中的数据块
|
数据块符号
|
数据块名称
|
多数据块
|
可选否
|
位置限制
|
IHDR |
文件头数据块 |
否 |
否 |
第一块 |
cHRM |
基色和白色点数据块 |
否 |
是 |
在PLTE和IDAT之前 |
gAMA |
图像γ数据块 |
否 |
是 |
在PLTE和IDAT之前 |
sBIT |
样本有效位数据块 |
否 |
是 |
在PLTE和IDAT之前 |
PLTE |
调色板数据块 |
否 |
是 |
在IDAT之前 |
bKGD |
背景颜色数据块 |
否 |
是 |
在PLTE之后IDAT之前 |
hIST |
图像直方图数据块 |
否 |
是 |
在PLTE之后IDAT之前 |
tRNS |
图像透明数据块 |
否 |
是 |
在PLTE之后IDAT之前 |
oFFs |
(专用公共数据块) |
否 |
是 |
在IDAT之前 |
pHYs |
物理像素尺寸数据块 |
否 |
是 |
在IDAT之前 |
sCAL |
(专用公共数据块) |
否 |
是 |
在IDAT之前 |
IDAT |
图像数据块 |
是 |
否 |
与其他IDAT连续 |
tIME |
图像最后修改时间数据块 |
否 |
是 |
无限制 |
tEXt |
文本信息数据块 |
是 |
是 |
无限制 |
zTXt |
压缩文本数据块 |
是 |
是 |
无限制 |
fRAc |
(专用公共数据块) |
是 |
是 |
无限制 |
gIFg |
(专用公共数据块) |
是 |
是 |
无限制 |
gIFt |
(专用公共数据块) |
是 |
是 |
无限制 |
gIFx |
(专用公共数据块) |
是 |
是 |
无限制 |
IEND |
图像结束数据 |
否 |
否 |
最后一个数据块 |
为了简单起见,我们假设在我们使用的PNG文件中,这4个数据块按以上先后顺序进行存储,并且都只出现一次。
数据块结构
PNG文件中,每个数据块由4个部分组成,如下:
名称 |
字节数 |
说明 |
Length (长度) |
4字节 |
指定数据块中数据域的长度,其长度不超过(231-1)字节 |
Chunk Type Code (数据块类型码) |
4字节 |
数据块类型码由ASCII字母(A-Z和a-z)组成 |
Chunk Data (数据块数据) |
可变长度 |
存储按照Chunk Type Code指定的数据 |
CRC (循环冗余检测) |
4字节 |
存储用来检测是否有错误的循环冗余码 |
CRC(cyclic redundancy check)域中的值是对Chunk Type Code域和Chunk Data域中的数据进行计算得到的。CRC具体算法定义在ISO 3309和ITU-T V.42中,其值按下面的CRC码生成多项式进行计算:
x32+x26+x23+x22+x16+x12+x11+x10+x8+x7+x5+x4+x2+x+1
下面,我们依次来了解一下各个关键数据块的结构吧。
IHDR
文件头数据块IHDR(header chunk):它包含有PNG文件中存储的图像数据的基本信息,并要作为第一个数据块出现在PNG数据流中,而且一个PNG数据流中只能有一个文件头数据块。
文件头数据块由13字节组成,它的格式如下表所示。
域的名称
|
字节数
|
说明
|
Width |
4 bytes |
图像宽度,以像素为单位 |
Height |
4 bytes |
图像高度,以像素为单位 |
Bit depth |
1 byte |
图像深度: 索引彩色图像:1,2,4或8 灰度图像:1,2,4,8或16 真彩色图像:8或16 |
ColorType |
1 byte |
颜色类型: 0:灰度图像, 1,2,4,8或16 2:真彩色图像,8或16 3:索引彩色图像,1,2,4或8 4:带α通道数据的灰度图像,8或16 6:带α通道数据的真彩色图像,8或16 |
Compression method |
1 byte |
压缩方法(LZ77派生算法) |
Filter method |
1 byte |
滤波器方法 |
Interlace method |
1 byte |
隔行扫描方法: 0:非隔行扫描 1: Adam7(由Adam M. Costello开发的7遍隔行扫描方法) |
由于我们研究的是手机上的PNG,因此,首先我们看看MIDP1.0对所使用PNG图片的要求吧:
- 在MIDP1.0中,我们只可以使用1.0版本的PNG图片。并且,所以的PNG关键数据块都有特别要求:
IHDR
- 文件大小:MIDP支持任意大小的PNG图片,然而,实际上,如果一个图片过大,会由于内存耗尽而无法读取。
- 颜色类型:所有颜色类型都有被支持,虽然这些颜色的显示依赖于实际设备的显示能力。同时,MIDP也能支持alpha通道,但是,所有的alpha通道信息都会被忽略并且当作不透明的颜色对待。
- 色深:所有的色深都能被支持。
- 压缩方法:仅支持压缩方式0(deflate压缩方式),这和jar文件的压缩方式完全相同,所以,PNG图片数据的解压和jar文件的解压可以使用相同的代码。(其实这也就是为什么J2ME能很好的支持PNG图像的原因:))
- 滤波器方法:尽管在PNG的白皮书中仅定义了方法0,然而所有的5种方法都被支持!
- 隔行扫描:虽然MIDP支持0、1两种方式,然而,当使用隔行扫描时,MIDP却不会真正的使用隔行扫描方式来显示。
- PLTE chunk:支持
- IDAT chunk:图像信息必须使用5种过滤方式中的方式0 (None, Sub, Up, Average, Paeth)
- IEND chunk:当IEND数据块被找到时,这个PNG图像才认为是合法的PNG图像。
- 可选数据块:MIDP可以支持下列辅助数据块,然而,这却不是必须的。
bKGD cHRM gAMA hIST iCCP iTXt pHYs
sBIT sPLT sRGB tEXt tIME tRNS zTXt
关于更多的信息,可以参考http://www.w3.org/TR/REC-png.html
PLTE
调色板数据块PLTE(palette chunk)包含有与索引彩色图像(indexed-color image)相关的彩色变换数据,它仅与索引彩色图像有关,而且要放在图像数据块(image data chunk)之前。
PLTE数据块是定义图像的调色板信息,PLTE可以包含1~256个调色板信息,每一个调色板信息由3个字节组成:
颜色 |
字节 |
意义 |
Red |
1 byte |
0 = 黑色, 255 = 红 |
Green |
1 byte |
0 = 黑色, 255 = 绿色 |
Blue |
1 byte |
0 = 黑色, 255 = 蓝色 |
因此,调色板的长度应该是3的倍数,否则,这将是一个非法的调色板。
对于索引图像,调色板信息是必须的,调色板的颜色索引从0开始编号,然后是1、2……,调色板的颜色数不能超过色深中规定的颜色数(如图像色深为4的时候,调色板中的颜色数不可以超过2^4=16),否则,这将导致PNG图像不合法。
真彩色图像和带α通道数据的真彩色图像也可以有调色板数据块,目的是便于非真彩色显示程序用它来量化图像数据,从而显示该图像。
IDAT
图像数据块IDAT(image data chunk):它存储实际的数据,在数据流中可包含多个连续顺序的图像数据块。
IDAT存放着图像真正的数据信息,因此,如果能够了解IDAT的结构,我们就可以很方便的生成PNG图像。
IEND
图像结束数据IEND(image trailer chunk):它用来标记PNG文件或者数据流已经结束,并且必须要放在文件的尾部。
如果我们仔细观察PNG文件,我们会发现,文件的结尾12个字符看起来总应该是这样的:
00 00 00 00 49 45 4E 44 AE 42 60 82
不难明白,由于数据块结构的定义,IEND数据块的长度总是0(00 00 00 00,除非人为加入信息),数据标识总是IEND(49 45 4E 44),因此,CRC码也总是AE 42 60 82。
实例研究PNG
以下是由Fireworks生成的一幅图像,图像大小为8*8, 为了方便大家观看,我们将图像放大:
使用UltraEdit32打开该文件,如下:
00000000~00000007:
可以看到,选中的头8个字节即为PNG文件的标识。
接下来的地方就是IHDR数据块了:
00000008~00000020:
- 00 00 00 0D 说明IHDR头块长为13
- 49 48 44 52 IHDR标识
- 00 00 00 08 图像的宽,8像素
- 00 00 00 08 图像的高,8像素
- 04 色深,2^4=16,即这是一个16色的图像(也有可能颜色数不超过16,当然,如果颜色数不超过8,用03表示更合适)
- 03 颜色类型,索引图像
- 00 PNG Spec规定此处总为0(非0值为将来使用更好的压缩方法预留),表示使压缩方法(LZ77派生算法)
- 00 同上
- 00 非隔行扫描
- 36 21 A3 B8 CRC校验
00000021~0000002F:
可选数据块sBIT,颜色采样率,RGB都是256(2^8=256)
00000030~00000062:
这里是调色板信息
- 00 00 00 27 说明调色板数据长为39字节,既13个颜色数
- 50 4C 54 45 PLTE标识
- FF FF 00 颜色0
- FF ED 00 颜色1
- …… ……
- 09 00 B2 最后一个颜色,12
- 5F F5 BB DD CRC校验
00000063~000000C5:
这部分包含了pHYs、tExt两种类型的数据块共3块,由于并不太重要,因此也不再详细描述了。
000000C0~000000F8:
以上选中部分是IDAT数据块
- 00 00 00 27 数据长为39字节
- 49 44 41 54 IDAT标识
- 78 9C…… 压缩的数据,LZ77派生压缩方法
- DA 12 06 A5 CRC校验
IDAT中压缩数据部分在后面会有详细的介绍。
000000F9~00000104:
IEND数据块,这部分正如上所说,通常都应该是
00 00 00 00 49 45 4E 44 AE 42 60 82
至此,我们已经能够从一个PNG文件中识别出各个数据块了。由于PNG中规定除关键数据块外,其它的辅助数据块都为可选部分,因此,有了这个标准后,我们可以通过删除所有的辅助数据块来减少PNG文件的大小。(当然,需要注意的是,PNG格式可以保存图像中的层、文字等信息,一旦删除了这些辅助数据块后,图像将失去原来的可编辑性。)
删除了辅助数据块后的PNG文件,现在文件大小为147字节,原文件大小为261字节,文件大小减少后,并不影响图像的内容。
其实,我们可以通过改变调色板的色值来完成一些又趣的事情,比如说实现云彩/水波的流动效果,实现图像的淡入淡出效果等等,在此,给出一个链接给大家看也许更直接:http://blog.csdn.net/flyingghost/archive/2005/01/13/251110.aspx,我写此文也就是受此文的启发的。
如上说过,IDAT数据块是使用了LZ77压缩算法生成的,由于受限于手机处理器的能力,因此,如果我们在生成IDAT数据块时仍然使用LZ77压缩算法,将会使效率大打折扣,因此,为了效率,只能使用无压缩的LZ77算法,关于LZ77算法的具体实现,此文不打算深究,如果你对LZ77算法的JAVA实现有兴趣,可以参考以下两个站点:
- http://jazzlib.sourceforge.net/
- http://www.jcraft.com/jzlib/index.html
参考资料:
PNG文件格式白皮书:http://www.w3.org/TR/REC-png.html
为数不多的中文PNG格式说明:http://dev.gameres.com/Program/Visual/Other/PNGFormat.htm
RFC-1950(ZLIB Compressed Data Format Specification):
RFC-1950(DEFLATE Compressed Data Format Specification):
LZ77算法的JAVA实现:http://jazzlib.sourceforge.net/
LZ77算法的JAVA实现,包括J2ME版本:http://www.jcraft.com/jzlib/index.html
png图片的压缩与解压缩
在J2ME平台上PNG图片格式几乎成为了标准,无数台手持设备上运行的J2ME程序几乎都选用PNG来显示图像,包括大量的手机游戏以及手机应用,所以对PNG文件格式的了解,可以更有效的减少Jar Size,保护自有知识产权。
PNG文件格式: PNG文件格式分为PNG-24和PNG-8,其最大的区别是PNG-24是用24位来保存一个像素值,是真彩色,而PNG-8是用8位索引值来在调色盘中索引一个颜色,因为一个索引值的最大上限为2的8次方既128,故调色盘中颜色数最多为128种,所以该文件格式又被叫做PNG-8 128仿色。
PNG-24因为其图片容量过大,而且在Nokia和Moto等某些机型上创建图片失败和显示不正确等异常时有发生,有时还会严重拖慢显示速度,故并不常用,CoCoMo认为这些异常和平台底层的图像解压不无关系。不过该格式最大的优点是可以保存Alpha通道,同事也曾有过利用该图片格式实现Alpha 混合的先例,想来随着技术的发展,手机硬件平台的提升,Alpha混合一定会被广泛的应用,到那时该格式的最大优势才会真正发挥。
PNG-8文件是目前广泛应用的PNG图像格式,其主要有六大块组成:
1.文件头
2.IHDR块
3.PLTE块
4.tRNS块
5.IDAT块
6.文件尾
这六大块按顺序排列,也就是说IDAT块永远是在PLTE块之后,期间也会有许多其他的区块用来描述信息,例如图像的最后修改时间是多少,图像的创建者是谁等,不过这些区块的信息对我们来说都是可有可无的描述信息,故压缩时一般先向这些区块开刀。
数据块:
除了文件头,其中四大数据块和文件尾都是由统一的数据块文件结构描述的:
Chunk Length: 4byte
Chunk Type: 4byte
Chunk Data: Chunk Length的长度
Chunk CRC: 4byte
例如IHDR块的数据长度为13,既
Chunk Length = 13
Chunk Type = "IHDR"
文件头:
用来标示PNG文件,为固定的64个字节:0x89504e47 0x0d0a1a0a
IHDR块:
用来描述图像的基本信息,其格式为:
图像宽: 4byte
图像高: 4byte
图像色深: 4byte
颜色类型: 1byte
压缩方法: 1byte
滤波方法: 1byte
扫描方法: 1byte
曾经有人问过我,撒叫滤波方法和扫描方法,汗,说实话我也不知道,不过我们是在做手机游戏,不是在搞图形学不是嘛。
PLTE块:
这个就是传说中放置调色盘数据的地方啦,其格式为:
循环
RED: 1byte
GREEN:1byte
BLUE: 1byte
END
循环长度嘛,不就是Chunk Length / 3的长度嘛,而且Chunk Length一定为3的倍数。
tRNS块:
这个块时有时无,主要是看你是否使用了透明色。该区块的格式为:
循环
if(对应调色盘颜色非透明)
0xFF: 1byte
else
0x00: 1byte
END
循环长度为调色盘的颜色数,相当于调色盘颜色表的一个对应表,标识该颜色是否透明,0xFF不透明,0x00透明。故如果用UltraEdit查看PNG文件的二进制编码,如果看到一大片FF,一般就是tRNS区块啦,因为一个PNG文件一般只有一个透明色。
IDAT块:
这个就是存放图像数据的地方啦,这里要注意的是一个PNG文件可能有多个IDAT区块,而其他三大区块只可能有一个。
IDAT 区块是经过压缩的,所以数据不可读,压缩算法一般为LZ77滑动窗口算法,如果硬要看里面的数据的话,用zlib库也是可以的,CoCoMo当年就见过 Windows Mobile上的帝国时代巨变态的用zlib库压缩和解压该区块来进一步减少PNG文件大小,真是寸K寸金啊。
IEND块:
该区块虽然也按照数据块的结构,但Chunk Data是没有的,所以是固定的96个字节:0x00000000 0x49454e44 0xae426082
PNG图像压缩:
了解了PNG的文件结构,压缩就有的放矢了。压缩有6个级别,可以根据需要选择。
Level1:读取PNG文件,将除六大块之外的所有区块都过滤掉
Level2:文件头是固定的0x89504e47 0x0d0a1a0a,文件尾是固定的0x00000000 0x49454e44 0xae426082,去掉!
Level3:每个区块的Chunk Type我们是否需要呢?很明显,我们自己写的压缩格式自己应该清楚是按照什么样的顺序,去掉!
Level4:每个区块的Chunk Length我们是否需要呢?
IHDR块:定长13个字节,明显不需要,去掉。
PLTE块:最多128个颜色,为撒要用4byte来记录区块长度而不是用1byte来记录颜色数呢?
tRNS块:既然有颜色数,tRNS又是调色盘颜色表的对应表,既数量与颜色数相同,为撒还需要呢?
IDAT块:我想这个是唯一需要4byte来记录长度的区块。
Level5:每个区块的Chunk CRC是否需要呢?
因为计算CRC需要一些时间,但对于字节较少的区块一般可以忽略不计,所以对于这个问题还是由程序员自己决定吧。对于CRC的计算可以参看CoCoMo的另一篇Blog“PNG文件的CRC码计算”
Level6:每个区块我们是否要原封不动的保存期数据呢?
IHDR块:除了宽、高、色深是需要的,后面那4byte的信息是固定的0x03000000
PLTE块:为撒要用3byte来表示RGB而不是2byte的565格式?压缩方法可以参看CoCoMo的另一篇Blog“关于PNG图像压缩的一点感悟”
tRNS块:我想tRNS块是冗余最多的区块了吧,大段大段的0xFF明显没有必要,一般的PNG文件只有一个透明色,为撒要用对应表的方法而不是一个索引来记录到底哪个是透明色呢?由于颜色数最多128,所以只需1byte就可以代替tRNS那么多0xFF啦。
IDAT块:么想法,如果你够变态,把zlib加进来吧!
PNG图像解压:
创建了自定义的文件,J2ME端读取后,就面临解压的问题了。我们可以利用此函数来创建Image:
static Image
createImage(byte[] imageData, int imageOffset, int imageLength)
前提是传入的imageData与PNG未被压缩前的一致。因为PNG文件格式是固定的,所以读取自定义的压缩文件后,开始将那些默认的数据再添加进去,实现解压的目的。下面就开始解压之旅吧!
首先要创建一个ByteArrayOutputStream out,
1.写入文件头:
out.writeInt(0x89504e47);
out.writeInt(0x0d0a1a0a);
2.写入IHDR块
out.writeInt(13);
out.writeInt(0x49484452); //0x49484452为Chunk Type "IHDR"
out.writeInt(width);
out.writeInt(height);
out.writeByte(depth);
out.writeInt(0x03000000); //压缩时舍掉的4byte,默认0x03000000
out.writeInt(crc);
其他区块方法一致,故略过。。。
3.写入文件尾
out.writeInt(0x00000000);
out.writeInt(0x49454e44);
out.writeInt(0xae426082);
4.转换成数组,创建Image
byte[] pngBuffer = out.toByteArray();
Image image = Image.createImage(pngBuffer, 0, pngBuffer.length);
哈哈,大功告成。这里注意如果中途数据写入有错误,经常会出现创建Image失败的异常,而且非常不好调试,不过只要自定的压缩格式定下来后,对应的创建Image的函数只要写一次,以后基本不会出问题哈。
PNG图像加解密:
很多人都担心自己辛苦创作的漂亮的美术图片很easy就被别人拿到了,究其原因是由于PNG文件格式是固定的,稍微了解的人用UltraEdit很容易就能找到IHDR,PLTE等标识了。CoCoMo就经常看GameLoft的图像文件,哈哈。一般是2byte的Length,然后紧接着图片数据,都放在一个文件里,直接拷贝2进制然后粘贴到一个新文件里就是一幅图。后来的加密技术会把PNG分块,例如前100个字节一块,紧接着1K一块,最后剩余字节一块,然后把块顺序打乱,用2byte来记录总长度,1byte记录顺序,但是这并没有从根本上消除IHDR,IEND这些显眼的定位标识,好像在对者说:嘿,看,我就在这里!
现在了解了之前的压缩和解压技术,这个问题也就迎刃而解了,因为Chunk Length,Chunk Type和Chunk CRC这些东西都消失了,甚至连数据块本身的数据都修改了,我可以按照ImageWidth、ImageHeight、ImageDepth的顺序写数据,也可以倒过来写。我想再牛的PNG分析器也是无能为力的吧,唯一可以定位的就只有IDAT区块了,不过就算得到该区块的数据,也应该是一张黑白图。