实现APNG图片解码及缩放显示

实现GIF图片缩放的文章中,我们使用了GIF解码器来实现GIF图片的解码,然后修改了SSIV的代码来实现动图的缩放。而APNG也是一种动态图片,是在APNG格式上进行扩展而来的,关于apng的文档参考这个链接,关于PNG的详细资料参考这个链接

APNG的结构图

普通的png的结构是Signature(校验)开头,然后是IHDR(包含图片信息),然后是各种辅助块,最后是IDAT数据块和IEDN结束块。而APNG的开始和PNG相同,只是在IDAT之前会有acTL块,标识着是APNG图片。APNG还比PNG多了fcTL和fdAT块,其中fcTL的信息是标识着下一帧图片的信息,如宽高,offset等,而fdAT则是数据块,和IDAT类似,却多了一个sequence_number(帧编号)。如果IDAT之前有fcTL那么IDAT的数据则当做第一帧(如上图),否则忽略(如下图)。

块的结构

每一块都是这个结构,4个字节标识数据的长度,4个字节标识块类型,然后是数据(如果数据长度为0则无数据内容),最后是CRC校验。除了PNG签名,其他数据都比较符合这个规则。

然后看一下每个部分的内容。

PNG signature:PNG签名,不是块类型,固定为8字节内容:137 80(P) 78(N) 71(G) 13 10 26 10 

IHDR块内容:宽高就表示图片宽高,其他表示图片绘制信息

扫描二维码关注公众号,回复: 9457430 查看本文章
Width	                4 bytes    宽度
Height	                4 bytes    高度
Bit depth        	1 byte    位深
Colour type        	1 byte
Compression method	1 byte
Filter method	        1 byte
Interlace method	1 byte

acTL块:包含了图片的帧数和循环次数(0表示无限循环)

num_frames     4bytes  帧数
num_plays      4bytes  循环次数

fcTL块:包含了当前帧图片的宽高,offset,延迟,和绘制方式

sequence_number       4bytes    帧编号
width                 4bytes    宽度
height                4bytes    高度
x_offset              4bytes    x偏移
y_offset              4bytes    y偏移
delay_num             2bytes    延迟时间分子
delay_den             2bytes    延迟时间分母(如果未0则视为100,num和len的比就是延迟的秒)
dispose_op            1bytes    绘制下一帧时需要对当前缓冲区的操作
blend_op              1bytes    绘制当前帧时是混合还是直接替换

IDAT块:包含了图片数据。

frame_data            Xbytes    数据,长度由块长度标识决定

fdAT块:包含了帧编号和图片数据

sequence_number       4bytes    帧编号
frame_data            Xbytes    数据,长度由块长度标识决定

IEND块:标识图片的结尾,数据为空。

解析过程:先从数据流中读取信息,把图片各种信息保存下来,相关代码如下

int length, type;
boolean done = false;
while (!err() && !done) {
    length = readInt(); // 长度
    type = readInt(); // chunk type
    switch (type) {
        case APngConstant.IHDR_VALUE:
            readIHDR();
        break;
        case APngConstant.acTL_VALUE:
            readAcTL();
        break;
        case APngConstant.fcTL_VALUE:
            readFcTL();
        break;
        case APngConstant.IEND_VALUE:
            done = true;
        break;
        …………
        default:
            skip(length);
        break;
    }
    readCRC();
    if(!done) done = rawData.position() >= rawData.limit();
}

读取帧信息:获取当前帧信息并组合成图片。先读取PNG签名和第一个IDAT之前的所有除了acTL和fcTL的块信息,如果是IDAT的信息则直接读取;如果是fdAT的信息则需要进行处理,把fdAT块的类型改为IDAT,然后去掉帧编号,加上数据信息,因为改变了块的内容,需要重新进行CRC签名后加上修正后的CRC签名;最后加上IEND块;

CRC签名的方式:先更新TYPE的内容,然后更新数据信息即可。相关代码如下

    public static void updateCRC(int offset, byte[] bytes, int dataLength) {
        CRC32 crc32 = new CRC32();
        // update type
        crc32.update(bytes, offset, 4);
        if (dataLength > 0) {
            // update data
            crc32.update(bytes, offset + 4, dataLength);
        }
        // 写入修正后的crc
        writeInt4ToBytes((int) crc32.getValue(), bytes, offset + 4 + dataLength);
    }

生成Bitmap:如果是第一帧,可以直接生成图片,如果是其他帧,则需要blend_op来绘制相关区域。先绘制底图,如果blend_op为0(APNG_BLEND_OP_SOURCE)则需要先清除当前区域再绘制;如果是1(APNG_BLEND_OP_OVER)则可以直接绘制该区域。关于底图的处理则是根据dispose_op来操作的,如果是0(APNG_DISPOSE_OP_NONE)则保存当前为底图;如果是1(APNG_DISPOSE_OP_BACKGROUND)则需要清除当前区域后再保存;如果是2(APNG_DISPOSE_OP_PREVIOUS)则不保存(相当于使用上一帧的底图)。

到此实现了整个APNG图片的解析,源码地址。当然还存在一些问题,例如生成bitmap的方式还有待改进。

结合GIF实现缩放的方式,使用同样的方式即可实现APNG图片的缩放。效果图如下

发布了53 篇原创文章 · 获赞 17 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/jklwan/article/details/101174080