有了上一章在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;
}
如有需要可留言给我,我将代码发邮件给你们