前段时间在做PNG的解析问题,对于PNG格式基于字节的读写有了一定了解,此文记录如何解析PNG图片的数据字段,也就是真实像素的二进制解析,PNG的data数据基本数据块的组成为 :Filter + 压缩数据。(这里除去校验头尾),二进制读取自己写即可,文件头的解析等,本文也不记录,可以参看https://www.w3.org/TR/PNG/#9Filter或者参考这里中文解释https://www.jianshu.com/p/ecacf2f60cb2,这里面有一些介绍,原理已经讲的很明白了,但是没有代码的干货,这里补充一下,下面的算法是假设我们已经将压缩的数据使用zlip压缩解压了,但是需要根据Filter 类型去还原像素值,算法如下:
/*
-------------
|C | B | D |
------------
|A | x | |
------------
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))
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).
*/
enum FilterTypes
{
eFTNone =0,
eFTSub = 1,
eFTUp =2,
eFTAverage = 3,
eFTPaeth =4,
};
void GsPNG::Filtering(FilterTypes etype, unsigned char * row, int length)
{
if (m_Type != eRGBA8)//先实现常用的rgba,这里其实不用管通道顺序,只需要知道几个通道即可,
return;
m_CacheBefore.Allocate(length);
switch (etype)
{
//原值不处理
case eFTNone:break;
case eFTSub:
{
for (int i = 4; i < length; i += 4)
{
row[i] += row[i - 4];
row[i + 1] += row[i - 3];
row[i + 2] += row[i - 2];
row[i + 3] += row[i - 1];
}
}
break;
case eFTUp:
{
for (int i = 0; i < length; i += 4)
{
row[i] += m_CacheBefore.BufferHead()[i];
row[i + 1] += m_CacheBefore.BufferHead()[i + 1];
row[i + 2] += m_CacheBefore.BufferHead()[i + 2];
row[i + 3] += m_CacheBefore.BufferHead()[i + 3];
}
}
break;
case eFTAverage:
{
row[0] += floor((m_CacheBefore.BufferHead()[0] + 0) / 2);
row[1] += floor((m_CacheBefore.BufferHead()[1] + 0) / 2);
row[2] += floor((m_CacheBefore.BufferHead()[2] + 0) / 2);
row[3] += floor((m_CacheBefore.BufferHead()[3] + 0) / 2);
for (int i = 4; i < length; i += 4)
{
row[i] += floor((m_CacheBefore.BufferHead()[i]+ row[i - 4])/2);
row[i + 1] += floor((m_CacheBefore.BufferHead()[i+1] + row[i - 3]) / 2);
row[i + 2] += floor((m_CacheBefore.BufferHead()[i+2] + row[i - 2]) / 2);
row[i + 3] += floor((m_CacheBefore.BufferHead()[i+3] + row[i - 1]) / 2);
}
}
break;
case eFTPaeth:
{
//从0开始,超出的像素用0 代替,这里在官网有一排小字介绍
row[0] += PaethPredictor(0, m_CacheBefore.BufferHead()[0], 0);
row[1] += PaethPredictor(0, m_CacheBefore.BufferHead()[1], 0);
row[2] += PaethPredictor(0, m_CacheBefore.BufferHead()[2], 0);
row[3] += PaethPredictor(0, m_CacheBefore.BufferHead()[3], 0);
for (int i = 4; i < length; i += 4)
{
row[i] += PaethPredictor(row[i - 4], m_CacheBefore.BufferHead()[i], m_CacheBefore.BufferHead()[i - 4]);
row[i + 1] += PaethPredictor(row[i - 3], m_CacheBefore.BufferHead()[i+1], m_CacheBefore.BufferHead()[i - 3]);
row[i + 2] += PaethPredictor(row[i - 2], m_CacheBefore.BufferHead()[i+2], m_CacheBefore.BufferHead()[i - 2]);
row[i + 3] += PaethPredictor(row[i - 1], m_CacheBefore.BufferHead()[i+3], m_CacheBefore.BufferHead()[i - 1]);
}
}
break;
default:
break;
}
//记录一下,下次取BC的值
m_CacheBefore.Copy(row, length);
}
再次重申上面的代码是将已经zlip解压后的数据使用Filtering 过滤反算后得到真实的像素值的过程,正算一样的,这里就不做过多描述