pvz一代特殊图像格式分析——ptx2X系列(安卓iOS篇)

声明:本教程内容包含:ptx2X系列编码解码、ptxPS3系列图像查看、texTV系列编码解码、texiOS系列编码解码、txz图像编码解码,若你根据本教程写出了对应工具,请务必注明是参考了本教程。
一代安卓iOS中的ptx图像共有七种格式,分别为0,1,2,3,21,22,23,其中0,1,2,3不在此讨论,下面以解码ptx22格式为例,给出21,22,23格式分析和编码解码代码实现
前面是发现其存储方式的过程,后面是代码实现,(插播文章水印:b站迎风听雨丶制作讨论群1017246977)如果耐心看完,相信你一定能明白原理。
使用iPad Air 2(iOS8系统,游戏版本为付费HD版1.9.12)截图游戏加载界面,发现分辨率为2048*1536,在rsb中ptx信息段搜索00 08 00 00 00 06 00 00(此信息为图像宽度2048和图像高度1536的32位十六进制小端序表示),发现所有分(插播文章水印:b站迎风听雨丶制作讨论群1017246977)辨率为2048*1536的ptx图像存储格式均为0x16即22,所以此图像存储格式肯定为22。


用PS打开游戏中截图,放大,对比左上(插播文章水印:b站迎风听雨丶制作讨论群1017246977)角颜色,发现红框内颜色都相同,蓝框内颜色都相同,如图所示

游戏加载界面背景图像ptx文件大小为6mb=6291456b,而6291456/(2048*1536)=2,也就是说平均每个像素用2个字节存储。所以先猜测其存储图像方式为rgb565或rgba5551。
用十六进制编辑器打开游戏加载界面背景图像ptx,发现有很多70 D6和70 DE,其中70 D6就是1101011001110000,而红框内颜(插播文章水印:b站迎风听雨丶制作讨论群1017246977)色对应r值为11010110,g值为11001111,b值为10000100,r的前五位为11010,g的前六位为110011,b的前五位为10000,拼接在一起正好是1101011001110000,同理蓝框内的颜色也对应后面的70 DE,所以图像存储格式应该是rgb565。正常的rgb565图像是依次存储每一行的所有像素的rgb值的,用此方(插播文章水印:b站迎风听雨丶制作讨论群1017246977)式将图像转码为png,发现得到了乱码,如图所示,但主体色调十分接近截图色调,所以存储方式应该是rgb565没错,猜测是游戏做了进一步处理。

我们回到十六进制编辑器,可以看到两个70 D6间隔0x40即64字节,也就是说,图像rgb信息向右写了32像素之后转而向下开始写下一行的rgb信息。莫非这个图片是每向右写16像素的颜色值就换行写下一行,直到这一列写完之后(插播文章水印:b站迎风听雨丶制作讨论群1017246977)去写下一列的颜色值?按照此猜想写代码解码为png,如图,还是乱码,只不过比之前更有规律了,已经能看出来原图像的部分内容了。

为什么会出现32*32的像素块?难道是游戏将(插播文章水印:b站迎风听雨丶制作讨论群1017246977)原图像分为很多个32*32的像素块,然后分别存储每个像素块的值吗?按照这个猜想,改进了一下程序,竟然解出了正确的原图像。

所以这个格式为22的ptx,应该就是将图(插播文章水印:b站迎风听雨丶制作讨论群1017246977)像分为很多32*32的像素块,每个像素块存储方式为依次存储每一行每个像素的rgb565形式的颜色,然后依次存储每一个32*32的像素块。
下面给出C#代码实现。其中,HexReader为我为了处理二进制文件方便,自己写的类,可以直接换成BinaryReader类;LockBitmap类是提供锁定内存法快速实现读写图像某一点像素的类,可以直接删去然后使用Bitmap。此方法(插播文章水印:b站迎风听雨丶制作讨论群1017246977)传入一个使用ptx图像构造的HexReader对象和ptx图像的宽度高度,返回一个基于此图像的Bitmap类,可以直接存储为文件,也可以在pictureBox控件中显示。
//解码
static Bitmap Get_R5G6B5C32(HexReader HR, int height, int width)
{
Bitmap img = new Bitmap(width, height);
int temp;
LockBitmap img2 = new LockBitmap(img);
img2.LockBits();
for (int i = 0; i < height; i += 32)
{
for (int w = 0; w < width; w += 32)
{
for (int j = 0; j < 32; j++)
{
for (int k = 0; k < 32; k++)
{
temp = HR.ReadUInt16(); //(插播文章水印:b站迎风听雨丶制作讨论群1017246977)
img2.SetPixel(w + k, i + j, Color.FromArgb((temp & 0xF800) >> 8, (temp & 0x7E0) >> 3, (temp & 0x1F) << 3));
}
}
}
}
}
//编码
static void Save_R5G6B5C32(HexWriter HW, Bitmap img)
{
int height = img.Height;
int width = img.Width;
if (height % 32 != 0 || width % 32 != 0)
{
throw new Exception("ptx22格式的图像高度或宽度应为块边长的整数倍!");
}
Color temp2;
for (int i = 0; i < height; i += 32)
{
for (int w = 0; w < width; w += 32)
{
for (int j = 0; j < 32; j++) //(插播文章水印:b站迎风听雨丶制作讨论群1017246977)
{
for (int k = 0; k < 32; k++)
{
temp2 = img.GetPixel(w + k, i + j);
HW.WriteUInt16((ushort)(((temp2.B & 0xF8) >> 3) + ((temp2.G & 0xFC) << 3) + ((temp2.R & 0xF8) << 8)));
}
}
}
}
}
其中C32代表chunk32(按照32*32像素分块)。
ptx21与ptx23和此原理类似,只不过(插播文章水印:b站迎风听雨丶制作讨论群1017246977)图像分别使用rgba4444和rgba5551存储,在此不再赘述。
我使用C#写了支持一代ptx、tex、txz图像的图像查看器,可用于查看、编码、解码这些图像,需要下载可加群1017246977获取。