mugen吧 关注:78,928贴子:1,493,725
  • 12回复贴,共1

【SFFV2.00&2.01】文件格式说明详解

取消只看楼主收藏回复

SFFV1的格式说明已经完结了,格式信息图:71bbs.people.com.cn/postImages/E3/F3/93/8F/1502767977359.png
里面完整介绍了SFFV1的格式信息和需要注意的事项,以及各种类型的图像的处理方法
千寻技术文章区也会同步更新
-------------------------
接下来说正文:SFFV2
SFFV2有两个版本,也就是2.00和2.01,因为两个版本仅有一些不大的差别,所以放在一起讲,至于什么差别到了后面就会详细说,这里只要知道2.00和2.01几乎一样就行了
看V2之前最好能看懂V1的,因为很多知识都是要联系到上个帖子的内容,首先先说说V2相对于V1的主要差别
V1支持的位数:8
V2支持的位数:5/8/24/32(其中24/32是2.01添加的)
关于位数,这里也说一下到底是什么,我们平常使用的素材基本都会到PS进行索引,当然也有一些例外,经过索引后的图片呢,会有一个调色板

一般的调色板都是16X16的,也就是说一共256个颜色,也称为色表
这种拥有256个颜色的色表的图像,称为8位图像,也就是索引后的图像,因为2的8次方刚好就是256,也就是说,这张图像最多只能显示256种颜色
而没经过索引的图像,也就是日常见到的RGB图像,一般为24位,24位图像是没有色表的,但是能显示的颜色更多
而32位能显示的颜色和24位是一样多的,只是多了透明度通道,同样没有色表
然后就是5位图像,也就是说,色表只有32个颜色(2的5次方),在FF3中可以5/8位互转

5位图像往往能得到极高的压缩率,这与SFFV2的压缩算法有关,等下就会说到
需要纠正的几个容易犯的错误就是,不要认为只有V2才支持PNG
任何版本的SFF都支持PNG,BMP,PCX,最重要的还是看图像的属性(位深),索引后的PNG照样能使用到V1当中
另外就是BMP/PNG在FF3中添加时,色表会翻转,而PCX和从ACT色表文件添加则不会,FF3中导出色表:导出的是FF3显示的调色板的色表翻转后的色表,MUGEN不显示色表的第一位色(而非最后一位)
.
SFFV2相对于V1的另外一个区别就是支持了压缩
SFFV1:Null
SFFV2:Lz5/Rle5/Rle8/PNG8/PNG24/PNG32(最后三个是2.01添加的)
丰富的压缩算法使SFFV2在获取更佳的画质下体积更小,每个压缩算法在后面都会细说


IP属地:广东1楼2017-08-15 20:41回复

    这是SFFV2的文件头部结构,以一个V2.01的SFF文件为例

    图中红框部分为版本号,V2.00为00 00 00 02,而V2.01为00 01 00 02
    而涂红部分的则为所有的有效数据
    可以发现,到了V2,图像和色表是分开了的,不再只有图像子节点,而多了色**节点
    同时,数据分成了两种类型,ldata和tdata,什么区别下面说


    IP属地:广东2楼2017-08-15 20:52
    收起回复
      关于ldata和tdata的区别,由于SFFV2支持了压缩,为了满足某些需求,则增加了一种图像设定,在FF3也有:

      在V1版本的SFF中,这个勾打不打都没有任何影响的,就是个摆设
      到了V2,一旦打了这个勾,则这张图像如果压缩了,在游戏加载的时候,则先解压,再读取
      而ldata则存放压缩数据和无压缩数据,而tdata则存在加载时要先解压的数据


      IP属地:广东3楼2017-08-15 20:56
      回复
        之后看看V2的图像子节点结构

        每一个图像子节点占了28字节,相对V1,添加了宽和高数据(后面有大用处),而多了个压缩算法,只占一个字节:
        为0时,表示数据不压缩,为原始数据
        为2时,表示采用Rle8算法压缩
        为3时,表示采用Rle5算法压缩
        为4时,表示采用Lz5算法压缩
        为10时,表示采用PNG8算法压缩
        为11时,表示采用PNG24算法压缩
        为12时,表示采用PNG32算法压缩
        其中Rle8和PNG8是针对8位图像压缩的,而Rle5和Lz5是针对5位图像压缩的,PNG24和PNG32分别针对24位图像和32位图像
        .
        另外还多了位深和标志,标志就是表示当前图像使用的是ldata数据块还是tdata数据块
        可以看到还有一个数据,“ldata和tdata偏移”,这就是图像的储存位置(相对于数据块计算),比如ldata=512,标志=0,则图像储存的位置为ldata的位置+512
        .
        最后就是色表,到了V2,多了**iao子节点后,则不存在共享色表,而是让图像指定一个色表索引,当色表索引为n时,表示图像使用第(n+1)个**iao子节点的色表


        IP属地:广东4楼2017-08-15 21:04
        收起回复
          接下来就是色##表##子节点的结构

          每一个色##表##子节点只占16字节,色##表##的数据一定存放在ldata数据库,另外就是多了色数,这是用来区分是5位图像色表还是8位图像色表的


          IP属地:广东5楼2017-08-15 21:08
          回复
            读取SFFV2的流程,我的建议是先把所以的色表读取出来并储存,因为到了V2,除了24/32图像是不需要色表的,其他图像都是没有色表的,先读取色表的话,在读取图像的过程中就可以直接调用数据了
            读取色表的思路也很简单,因为色表和色表之间是紧密的,中间不会存在数据,也就是说,第一个色表##子节点的下一个数据开始就是第二个色表##子节点了
            所以读取色表,只要先读取文件头部,获取第一个色表##子节点的位置和ldata的位置,然后循环,循环次数就是色表数量,以每16个字节一个子节点的方式读取出每一个色表在ldata中的偏移和色表数据长度,在到位置(ldata位置+ldata偏移)中读取相应长度的色表数据
            当然,读出来的色表数据自然是不能直接使用的,在SFFV1中,色表都是一个颜色占3个字节,一共768字节,而到了SFFV2,一个颜色则占用4个字节,其中第四个字节是无用数据,也就是说,读取色表的时候,读出来的数据要经过处理,处理的方法就是删除第n个数据(n除以4的余为0),可以看出,原始的每一个色表数据占1024字节,也就是256*4


            IP属地:广东6楼2017-08-15 21:19
            回复
              先读取完色表,储存好方便接下来的调用,也就是图像数据的获取,读取图像的方法也很简单,因为图像与图像之间也是紧密的,中间也没有别的数据,每一个图像字节点占28个字节,那么和色表的读取思路就一模一样了
              从文件头部读取ldata的坐标和tdata的坐标,以及第一个图像字节点的位置
              搞一个循环,循环次数为图片数量,然后根据第一个图像字节点的位置,以每28个字节一个图像字节点的方法读取图像的信息数据(组和索引,XY,宽高),都是short型的短整数(2字节),之后读取标志和图像长度,在对应的数据库读取图像数据,再根据压缩算法进行解析
              .
              为了方便,先说PNG24/32图像的解析方法,这两个算法是最简单的了,因为它们不需要色表,而且是完整的PNG文件数据,随便搞一个SFF,加入一些24/32位的图像,然后就可以开始测验了,读取出的图像数据的头部如下

              根据对比,发现头部存在数据{89,50,4E,47,0D,0A,1A,0A}(十六进制),这明显就是PNG文件的开始标志

              PNG文件署名域,占8个字节,数据如上图,也就是说,PNG24/32的图像数据就是一个完整的PNG文件
              那么前四个多出来的字节又是什么呢?校验长度
              由于SFFV2支持了压缩,所以被压缩的数据块前多了四个数据,为整数型数据,此为该压缩块解压后的数据长度
              也就是说,在读取压缩数据的时候,偏移要+4,同时数据长度也要-4,(不减的话最后一张图就有异常)
              之后直接写出数据到文件中,可以看出已经成功提取出来了:


              IP属地:广东7楼2017-08-15 21:41
              回复
                继续讲PNG8的读取办法,PNG8是经过索引的PNG图像,也就是说,它需要色表,根据PNG24/32的方法写出数据,图像却显示成了全黑色

                用UE打开,确实是PNG文件

                可为什么显示为黑色呢?
                这就要说到索引图像的显示方式,8位图的色表为256种颜色,而每一个像素,则是一个数,这个数最小为1,最大为256,然后呢根据色表显示颜色,我们称这个数为颜色索引,也就是说,它的数字是什么,就显示色表中的对应的颜色,所有的像素点显示后,就是一个完整的图像
                那么显示为黑色可以直接判断为色表问题了,上面我们说过,到了V2,色表和图像都分开了,所以需要色表的图像,必须获取色表数据并添加,所以我上面推荐你们先读取色表,到了这时候,就要给PNG加上对应的色表,根据色表索引获取色表
                那么色表要添加到什么位置呢?这就扯到了PNG的文件结构

                PNG由许多数据块组成,这里我们只说关键数据块,里面有一个PLTE子数据块,用来存放调色板数据
                看到位置限制,可以肯定这个数据块肯定在文件前面部分,不会隔太远

                在第四行看到了一个数据{50,4C,54,45},根据ASCII转成文本就是PLTE,看看下面的内容,都是00,怪不得显示为黑色,因为下面的768个数据都是00
                那么我们就要用正确的色表数据替换掉这768个00,方法也不难,寻找字节集,从头部开始找,找PLTE,第一个遇到的肯定是,之后用色表数据替换掉PLTE之后的768个数据,就行了,当然,色表数据肯定是要经过处理后的768字节色表,而不是1024字节色表

                添加色表后就显示正常了


                IP属地:广东8楼2017-08-15 22:02
                收起回复
                  到这里,PNG8/24/32都说完了,之后就是Rle8/5和Lz5,这三种类型的读取方式都一样,只是解压算法上的差异,以Rle8为例子进行说明
                  先说说Rle8是个什么东西,R就是run,L就是length,E就是encoding,翻译下来就是游程编码,或者叫行程长度编码,举个简单的例子
                  一个TXT文本,内容为:AAABBCCCC
                  经过压缩后就是:3A2B4C(减少了3个字节)
                  当然这种压缩算法比较水,比如内容为:ABCD,压缩后就变成了1A1B1C1D(翻了一倍),也就是说Rle8对连续重复数据较多的图像压缩率更高,反之可能会使数据更大
                  用FF3加入几张8位图,保存为2.00就是Rle8压缩了,这是默认的,也是自动的
                  贴上Rle8的解压代码,这是从ikemen中提取的(Lua),可以参考,其实并不复杂
                  public void rle8Decode(^ubyte px=)
                  {
                  if(#px == 0) ret;
                  ^/ubyte rle = px;
                  px.new(`rct.w * `rct.h);
                  loop{
                  int leng = #px;
                  index i = 0, j = 0;
                  while;
                  do:
                  loop{
                  int size;
                  ubyte d = rle[i++];
                  branch{
                  cond (d&0xC0) == 0x40:
                  size = (int)(d & 0x3F);
                  d = rle[i++];
                  else:
                  size = 1;
                  }
                  while;
                  do:
                  px[j++] = d;
                  while --size >= 0:}
                  while j < leng:}
                  }
                  我来简单说下解压思路,对每一个字节进行判断,然后与C0(十进制为192)进行位与操作
                  如果结果为40(十进制为64):则将当前字节与3F(十进制为63)进行位与,结果就是游程长度len,而下一个字节则为游程内容byt,输出len份byt
                  如果结果不为40:则表示游程长度为1,输出1份byt
                  所有字节都进行完毕后,就导出一份原始数据,当然事情还没结束,这次不再是完整的PNG文件了,而是真正的原始数据
                  (当然解压的时候记得跳过校验长度,具体看上面的)
                  以官方KFM的9000,0为例

                  我将图像数据读取,并进行Rle8解压,得到了一份625字节的数据,而恰好小头像的长X宽=625
                  这并不是巧合,上面已经说过了,索引图片的显示方式是每一个像素点显示一个颜色,而长为25,宽也为25的图像的像素点一共就是625,也就是说,这份解压后的数据,就是图像的像素数据
                  像素数据肯定不能用来直接显示的,需要组合成文件,以生成PCX为例(为什么不是PNG/BMP,下面会说)
                  PCX=文件头数据+图像像素数据+12(色表的开始标志)+调色板数据
                  现在我们有了:图像像素数据和调色板数据(通过色表索引获取),缺的就是文件头数据,来看看PCX文件头的数据结构

                  为了制作文件头数据,我推荐拿一份现成的PCX的文件头,然后作为常量,之后根据要生成的图像的信息进行数据修改,这样可以省去很多操作,这里我准备了一个:
                  { 10, 5, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 120, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
                  当然这是十进制的,长度一共为128,这个长度是固定的,根据文件头结构,我们要修改的数据是:图像大小(8字节),每行字节数(2字节)
                  只需要改两个就好了,首先就是图像大小,也就是说第四个数据(值为8)后面的8个数据,这8个数据其实由4个short型整数组成,分别是Xmin,Ymin,Xmax,Ymax,前两个是固定的,也就是0,后两个,Xmax为(宽度-1),Ymax为(高度-1),要十分注意的是必须为短整型
                  而每行字节数也是一个short型,值为当前图像的宽度
                  修改完PCX头后,进行组合:
                  图像数据=PCX头部+解压后的像素数据+{12}+调色板数据
                  之后将图像数据写到PCX,就是正常显示的PCX图片
                  关于为什么不用PNG和BMP:最主要的原因是PNG和BMP的像素数据的顺序,是从左下角到右上角的,而我们提取出来的数据是从左上角到右下角的,如果要PNG和BMP则需要重新调整像素数据的位置,而且PNG和BMP的结构很复杂,修改的地方很多


                  IP属地:广东9楼2017-08-15 22:39
                  回复
                    最后还有Rle5/Lz5这两个算法,都是针对5位图像压缩的,由于5位图像只有32种颜色,根据位图的显示方式,每一个像素点的数字最小为1,而最多也不过是32,所以压缩率高,特别是Lz5算法,压缩率更是翻倍,但是编码复杂
                    Rle5和Lz5的读取方法基本和Rle8一样(注意跳过校验长度),只是解压时使用的算法差异,下面放出Rle5和Lz5的解压代码(Lua),同样提取自ikemen
                    代码地址:71bbs.people.com.cn/postImages/E6/69/84/F9/1502809261305.png
                    ----------------------
                    经过解压后的数据,根据Rle8的方法组合成PCX就行了
                    通过原始的数据导出的PCX,是没有压缩过的,而PCX也支持Rle压缩,不过不是Rle8,而是新的改进后的游程算法
                    所以如果要导出压缩的PCX,就是先对压缩数据进行对应的压缩算法的解压,然后再用PCXRle的算法进行压缩,再进行组合,需要注意的是,此时文件头的第3个数据必须为1


                    IP属地:广东10楼2017-08-15 23:01
                    回复
                      最后还有一个,就是压缩算法为0时,也就是无压缩数据,这个数据直接就是像素数据了,但是有一个特别要注意的地方就是,当压缩算法为0时,是没有校验长度的,直接按原来的图像数据和图像位置读取就好,不需要跳过了
                      之后也是用组合PCX文件的方法


                      IP属地:广东11楼2017-08-15 23:03
                      回复
                        关于链接型图像的处理方式,和V1是一样的,当链接索引为n时,则表示当前图像与第(n+1)幅图像相同
                        另外就是如果“加载时压缩”打勾了,则图像数据则在tdata中,需要根据标志来灵活判断
                        另外就是FF3保存的默认压缩算法
                        保存为V1:仅8位,无压缩
                        保存为V2.00:仅5/8位,5位图采用Rle5,8位图采用Rle8
                        保存为V2.01:5/8/24/32,5位图采用Lz5,8位图采用PNG8,24位图和32位图分别采用PNG24和PNG32


                        IP属地:广东13楼2017-08-15 23:07
                        收起回复
                          完整信息都写到千寻了:https://qxmugen.com/article/12075.html


                          IP属地:广东16楼2017-08-16 10:40
                          回复