在Android中的大图片合成-PNG(二) PNG解压

有了上一章在Android中的大图片合成-PNG(一) PNG格式详解对png格式有个大概了解,本章将对PNG如何解压做一下了解

为了确保解压和压缩的正确,大家可以重新生成一张PNG来校验。我们以baseFile命名PNG图片,saveFile为新的PNG图片

首先将准备好的PNG以文件字节的形式读入内存

 FileInputStream input = null;
 BufferedInputStream buffInput = null;
 input = new FileInputStream(baseFile);
 buffInput = new BufferedInputStream(input);
其次,读入头8个字节,改8字节用来判断改文件是否为PNG,直接写入saveFile中,之后连续读入一些字节。

先读入4个字节,该4字节是接下去PNG块的长度,然后写入saveFile中。紧接着再读入4个字节,该4字节表示该块的类型,如IDAT,IHDR等。接下来就可读取该块的数据了,该date数据长度就是前面读取的4字节,最后的就是校验了,校验算法是CRC(类型+数据)生成的。此处我们最为关心的是IHDR以及IDAT2个数据块

关于IHDR

我们将会读入13字节长度的信息,Width(4)+Height(4)+Bit depth+ColorType+Compression method+Filter method+Interlace method,从中我们可以知道图片的长宽,以及图片的字节信息。根据colortype我们可以知道图像一个像素的字节数,此处我们看看2和6的表示,如果是2就是没有透明度的占3个字节,6就是带透明度的占4个字节。

colortype颜色类型:
0:灰度图像, 1,2,4,8或16 
2:真彩色图像,8或16 
3:索引彩色图像,1,2,4或8 
4:带α通道数据的灰度图像,8或16 
6:带α通道数据的真彩色图像,8或16

关于IDAT

一张PNG中或许有N多该类型的数据块。此处我们需要了解“行”,“filterType”,“Inflater”,“deflater”的概念。

一行的长度是 图片宽度×根据colortype得到的字节数,记住,所有的IDAT数据块加起来的解压后的字节数应该是 (图片长×字节数+1)×图片宽。如果后期发现计算有问题,就是解压出错了。

filterType:前面提到的+1就是该位,用来描述前后行每个像素的关系。一共有5中关系,0-4.

Filters may use the original values of the following bytes to generate the new byte value:

x the byte being filtered;
a the byte corresponding to x in the pixel immediately before the pixel containing x (or the byte immediately before x, when the bit depth is less than 8);
b the byte corresponding to x in the previous scanline;
c the byte corresponding to b in the pixel immediately before the pixel containing b (or the byte immediately before b, when the bit depth is less than 8).


Type Name Filter Function Reconstruction Function
0 None Filt(x) = Orig(x) Recon(x) = Filt(x)
1 Sub Filt(x) = Orig(x) - Orig(a) Recon(x) = Filt(x) + Recon(a)
2 Up Filt(x) = Orig(x) - Orig(b) Recon(x) = Filt(x) + Recon(b)
3 Average Filt(x) = Orig(x) - floor((Orig(a) + Orig(b)) / 2) Recon(x) = Filt(x) + floor((Recon(a) + Recon(b)) / 2)
4 Paeth Filt(x) = Orig(x) - PaethPredictor(Orig(a), Orig(b), Orig(c)) Recon(x) = Filt(x) + PaethPredictor(Recon(a), Recon(b), Recon(c))

Paeth算法

    p = a + b - c
    pa = abs(p - a)
    pb = abs(p - b)
    pc = abs(p - c)
    if pa <= pb and pa <= pc then Pr = a
    else if pb <= pc then Pr = b
    else Pr = c
    return Pr

给出filter算法,此处需要注意的就是byte和int的转换,以及byte相加溢出的算法。255+255=255哦


int filterType = InflaterArray[0];//获取每一行第一个字节,就是filter信息
	
	int mLineBytes = (imgWidth * imgPixel + 1);
	
	switch (filterType) {
	case 0:
		
		break;
	case 1:
		for (int k = imgPixel + 1; k < mLineBytes; k++) {
			InflaterArray[k] = (byte)(b2i(InflaterArray[k]) + b2i(InflaterArray[k - imgPixel]));
        }
		break;
	case 2:
		for (int k = 1; k < mLineBytes; k++) {
			InflaterArray[k] = (byte)(b2i(InflaterArray[k]) + b2i(lastLineArray_old[k]));
        }
		break;
	case 3:
		InflaterArray[1] = (byte)(b2i(InflaterArray[1]) + b2i(lastLineArray_old[1])/2);
		InflaterArray[2] = (byte)(b2i(InflaterArray[2]) + b2i(lastLineArray_old[2])/2);
		InflaterArray[3] = (byte)(b2i(InflaterArray[3]) + b2i(lastLineArray_old[3])/2);
        if (imgPixel == 4)
        	InflaterArray[4] = (byte)(b2i(InflaterArray[4]) + b2i(lastLineArray_old[4])/2);
        
        for (int k = imgPixel + 1; k < mLineBytes; k++) {
        	InflaterArray[k] = (byte)(b2i(InflaterArray[k]) + (b2i(InflaterArray[k - imgPixel]) + b2i(lastLineArray_old[k]))/2);
        }
		break;
	case 4:
		InflaterArray[1] = (byte)(b2i(InflaterArray[1]) + b2i(lastLineArray_old[1]));
		InflaterArray[2] = (byte)(b2i(InflaterArray[2]) + b2i(lastLineArray_old[2]));
		InflaterArray[3] = (byte)(b2i(InflaterArray[3]) + b2i(lastLineArray_old[3]));
        if (imgPixel == 4)
        	InflaterArray[4] = (byte)(b2i(InflaterArray[4]) + b2i(lastLineArray_old[4]));
        
        for (int k = imgPixel + 1; k < mLineBytes; k++) {
        	InflaterArray[k] = (byte)(b2i(InflaterArray[k]) + paeth(b2i(InflaterArray[k - imgPixel])
        			, b2i(lastLineArray_old[k]), b2i(lastLineArray_old[k - imgPixel])));
        }
		break;
	default:
		break;
	}


	for (int i = 0; i < mLineBytes; i++){//保存正Filter
		lastLineArray_old[i] = InflaterArray[i];
	}



接下来我们就来看看如何给IDAT的数据做解压。在android和Java中都给我们提供了一个很好的Inflater和Deflater,以及稍后将会用来校验的CRC32,这些类都在java.util.zip包中。此类使用流行的 ZLIB 压缩程序库为通用解压缩提供支持。ZLIB 压缩程序库最初是作为 PNG 图形标准的一部分开发的,不受专利的保护。具体用法可参考http://www.cjsdn.net/Doc/JDK60/java/util/zip/Inflater.html

//以下代码片段演示使用 Deflater 和 Inflater 压缩和解压缩字符串的详细过程。

 try {
 	// Encode a String into bytes
 String inputString = "blahblahblah??";
 byte[] input = inputString.getBytes("UTF-8");

 // Compress the bytes
 byte[] output = new byte[100];
 Deflater compresser = new Deflater();
 compresser.setInput(input);
 compresser.finish();
 int compressedDataLength = compresser.deflate(output);

 // Decompress the bytes
 Inflater decompresser = new Inflater();
 decompresser.setInput(output, 0, compressedDataLength);
 byte[] result = new byte[100];
 int resultLength = decompresser.inflate(result);
 decompresser.end();

 // Decode the bytes into a String
 String outputString = new String(result, 0, resultLength, "UTF-8");
 } catch(java.io.UnsupportedEncodingException ex) {
     // handle
 } catch (java.util.zip.DataFormatException ex) {
     // handle
 }

对于CRC校验,我们需要将数据块的类型type以及数据块内容data一起进行计算

crc32.update(typeArray);
crc32.update(outputStream.toByteArray());
int value = (int) crc32.getValue();


下面给出一些算法

//byte转int
	public static int byteArrayToInt(byte[] b, int offset) {
	       int value= 0;
	       for (int i = offset; i < (offset+4); i++) {
	           int shift= (4 - 1 - i) * 8;
	           value +=(b[i] & 0x000000FF) << shift;
	       }
	       return value;
	 }
	
	//int转byte
	public static byte[] intToByteArray(int i) {
		byte[] result = new byte[4];
		result[0] = (byte) ((i >> 24) & 0xFF);
		result[1] = (byte) ((i >> 16) & 0xFF);
		result[2] = (byte) ((i >> 8) & 0xFF);
		result[3] = (byte) (i & 0xFF);
		return result;
	}
	
	public static int sature(int x, int y) {
		int r = x + y;
		if (r > 0xFF)
			return 0xFF;
		return r;
	}
	
	public static int b2i(byte x) {
		return x & 0xFF;
	}



如有需要可留言给我,我将代码发邮件给你们


评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值