图片压缩



转自:手机游戏怎样压缩美术资源?

不知道题主的游戏是什么引擎开发的,仅从思路回答一下吧。
首先是去重,去重的方法很多,可挖掘的也很深,最简单的是去掉重复出现的图,用缩放,其次能循环的切割成小图,翻转使用等等
在没有重复的情况下压缩从三个方面入手,量、质、技法
只要是压缩,必然有损失,在损失程度可接受之内可以依据以下优先级尝试
技法是最优先考虑的,比如说人物动作动画,用骨骼动画代替序列帧动画,可以节省至少1/10的k数,骨骼动画制作的巧妙不比序列帧动画差
量的压缩比较直观,你去掉多少内容,就压缩多少美术。比如序列帧中的抽帧,代价就是动画不如之前流畅,但只要你掌握好了动画的重点和节奏,很多帧都是可以删除的,抽帧作的比较好的例子是COC,打开资源包就可以发现,只留下了最关键的画面,人物动作仍然很到位。
质的压缩应该是最后一步,2d压jpgpng等等,3d减面数压缩画质建议在可接受范围内,并且要有一个优先级,比如先压缩背景,后压缩人物;先压缩出现率低的,后压缩出现率高的
美术引擎不同具体的实施方法有很多,还可以多解压缩一些知名游戏的美术包来学习.



-------------------------------------------------------------------------------------------------------------------------------------------------


转自:手机游戏图片资源的压缩

设计
部分PNG图片的尺寸较大,难以满足游戏开发对资源大小的控制。
如果是调色板模式的PNG图片,通常可以压缩到比较小的尺寸,但是如果图片中含有透明度信息,那么图片就不能够被压缩成调色板模式。原生PNG调色板图片最多能支持的透明度是全透明或全不透明。
那么有没有一种既可以是调色板模式(或者不是调色板模式,但是能做到差不多压缩效果的)又能保持透明度的格式呢?当然是没有的,如果有,我也没有必要重复的造轮子去了。
其实,没有这样的图片格式,说明了一个问题,那就是很难找到有这种一箭双雕特效的编码能符合大众需求。那么我现在面临的情况是这样吗?我并不需要为符合大众需求设计一种图形格式,来兼顾大小和质量。手机游戏有游戏的特点,经过仔细观察,我发现手机游戏的图片多为卡通图,这种图色彩不是很丰富,大多非常简单。第二这种图片对透明度的要求并不是非常苛刻。大多时候,0%(全透明),50%(半透明)和100%(不透明)三种透明度足矣。
所以我为这种特殊的情况设计了一种特殊的图片格式,我叫它.px格式。
这种格式是这样的:
 |   头  部   |   调色板   |   颜色索引   |
头部包含三个整数,共12字节:宽度,高度,调色板数量。
调色板包含 调色板数量x3的字节数,分别表示了RGB三种色彩的分量。
色彩索引包含 宽度x高度个字节,用来索引调色板。但是这个索引是7位的,也就是说最多允许128色的调色板。这个字节的最高位用来表示透明度:0代表半透明,1代表不透明。那么全透明怎么表示呢?就用这个字节都是0来表示,因为全透明中RGB分量是没有意义的。这样的话会占用掉调色板的一个颜色,所以我的调色板最多只能允许127色。
图片格式是这样了,就完了么?没有。这种格式其实并不够苗条,里面还有油水。主要是如果,图片有大的色块的,数据区就会有重复的此数的所有,由于最多是127色的调色板,所有颜色索引这个图片的主体部分还是很有油水可榨的,榨干它的方法很简单,找个外围压缩库,把图片数据整体再压缩一下就可以了。经过测试,基本能把240x320的PNG图片压缩到35%左右的数据量。而且图片效果还不错,除了用渐变的图片比较惨以外手机游戏图片资源的压缩 - lava_hammer - lava_hammer的博客(不过这些是少量图片,如果效果不好,我们还可以选择不压缩此图)。
实践证明,用这个方法基本能使游戏安装包变为原来的一半大小。而图片装载的速度基本和原来的PNG图片差不多。

实践中的问题

1.统计原始图片的色彩
要做调色板图片,第一步自然是统计原图中有多少种颜色,每种颜色出现过多少次。这是在我要开发的离线工具中的功能,并非需要在游戏运行时进行的东西,所以效率不是很关键,但是我仍然不能让程序运行的速度慢的离谱,使工具程序影响工作效率。
最直接的办法是用三层循环。前两层用宽高来遍历所以像素,最内层来遍历当前颜色是否已经编入表中,如果是,则计数加一,否则要编入新的色彩数据。
这种办法效率很低,因为我统计中,一张400X500的图片有将近4万种色彩,三层循环耗时不少。而真正的运算还没开始呢。所以,我优化了一下算法,将颜色按照灰度进行统计(其实直接使用R,G,B中的某个分量也可以),创建256张色彩表,根据灰度来将颜色存入对应的颜色表中。这样内层循环时寻找相同色彩的话就不需要在整个色彩表中寻找了,因为同色必然同灰度,直接在它所在的灰度颜色表中查找即可。

2.生成调色板
生成调色板是我最纠结的一件事。中间走了弯路。最后,我的方案是这样的:定义一个覆盖率,定义一个容差。将颜色表按照颜色出现的次数排序,最多取其前127色,然后统计这些颜色出现的总次数,看是否满足覆盖率(我定义的是99%)。如果不满足,那么就增加容差值,然后根据容差值来合并颜色表中在容差范围内的色彩。合并后再次检查覆盖率,如此往复,直到满足条件为止。

3.合并颜色表
颜色表中的每一项包含两条数据:颜色和出现的次数。合并颜色表意味着,我需要判断两种颜色的差别是否在容差范围内,如果在,则合并(颜色以出现次数多的那个为准,而不是按比例混合)。最野蛮的做法是用两层循环来做两两对比。考虑四万色的颜色表,两两对比的运算量是亿级数量级的运算,一定巨慢无比。于是我找到一个方法。说这个方法前,我先说下容差的算法。算两种颜色的差值就是算两种色彩的R,G,B分量中差值最大的那个绝对值。那么如何优化呢?我把颜色按照一个顺序进行了排序。这个顺序是将此颜色和黑(R=0, G=0, B=0)进行差值运算,根据这个结果进行排序。然后在这个表的基础上进行合并。根据一个特征,就是如果A色彩能和B色彩合并,那么A和黑色的差值减去B和黑色的差值的绝对值一定在容差范围内,反之,这个绝对值在容差范围内的数值不一定就能合并。根据此点,我从头开始遍历颜色表(也是两层循环)当遍历到不可合并的颜色时,会有两种情况:1)此色无法合并 2)此色无法合并并且与黑色差值绝对值超出容差范围。对于第二种情况,就说明内层循环就可以终止了。因为后面的一定都不能合并。此法大大减少了合并颜色表的时间,使多次合并成为了可能。

4.大尾码与小尾码BigEndian&LittleEndian
在我完成核心部分,开始写java装载函数的时候,栽进了这个窟窿。C#/C++是LittleEndian,而Java是BigEndian。需要改变字节顺序,好在我的图片格式只有开头的三个整数需要调整,后面都是按字节存的。改完之后测试OK。

5.java不支持unsigned数据
这个坑会导致读入的byte被解释成负数,好在只要int x = b&0xff就可以解决了。



-------------------------------------------------------------------------------------------------------------------------------



转自:图片的压缩的一些想法

最近觉得手机游戏的程序包比较大,其中主要就是资源太大。游戏中主要使用PNG图片,所以就想去压缩一下这些图片。其实,手头有一些PNG压缩工具,能够把PNG压缩到原来的30%左右,效果的确不错。可以这类工具都有一个缺陷,那就是不能够压缩透明图片,含有透明信息的图片,被压缩后就没透明效果了。因此我在想自己做一个工具来压缩PNG,具体的方法就是使用带透明度的调色板来压缩带透明度的PNG图片。原理很简单,但是实际上做起来还是很难。主要是有了透明度这个选择之后调色板就很难生成(我想所以标准的PNG图片不支持透明度调色板吧)。
具体的原因是这样的,调色板本身最多只有256色。最多支持一个全透明,其它的255色还行,但是如果支持半透明,那么调色板的数量就会比较受影响。我开始将透明度按20%来划分,发现还是不行。最后终于狠下心来把透明度限制在0%,50%和100%这三个档上。用0x00表示完全透明,其余情况用符号位的0表示半透明,1表示不透明,而后面7位用来索引调色板,这样可以允许有128-1(去掉一个0x00),共127色的调色板。之所以这样切分透明度,是因为考虑到手机游戏的特殊允许,支持50%的透明度就可以解决图片毛边的问题,和半透明的应用需求。
其实我还发现,许多图片周围有许多空隙,也许将来可以将图片用四叉树划分成小块,将一致的小块用统一的颜色来表示。不过这个貌似很复杂,而且如果划分的不好还会增加体积。而且,装载时间也不能一点都不考虑。。



-------------------------------------------------------------------------------------------------------------------------------



转自:手机游戏开发png图片的压缩与解压缩

在J2ME平台上PNG图片格式几乎成为了标准,无数台手持设备上运行的J2ME程序几乎都选用PNG来显示图像,包括大量的手机游戏以及手机应用,所以对PNG文件格式的了解,可以更有效的减少Jar Size,保护自有知识产权。

PNG文件格式:
        PNG文件格式分为PNG-24和PNG-8,其最大的区别是PNG-24是用24位来保存一个像素值,是真彩色,而PNG-8是用8位索引值来在调色盘中索引一个颜色,因为一个索引值的最大上限为2的8次方既128,故调色盘中颜色数最多为128种,所以该文件格式又被叫做PNG-8 128仿色。
       PNG-24因为其图片容量过大,而且在Nokia和Moto等某些机型上创建图片失败和显示不正确等异常时有发生,有时还会严重拖慢显示速度,故并不常用,CoCoMo认为这些异常和平台底层的图像解压不无关系。不过该格式最大的优点是可以保存Alpha通道,同事也曾有过利用该图片格式实现Alpha 混合的先例,想来随着技术的发展,手机硬件平台的提升,Alpha混合一定会被广泛的应用,到那时该格式的最大优势才会真正发挥。
       PNG-8文件是目前广泛应用的PNG图像格式,其主要有六大块组成:
1.文件头
2.IHDR块
3.PLTE块
4.tRNS块
5.IDAT块
6.文件尾
这六大块按顺序排列,也就是说IDAT块永远是在PLTE块之后,期间也会有许多其他的区块用来描述信息,例如图像的最后修改时间是多少,图像的创建者是谁等,不过这些区块的信息对我们来说都是可有可无的描述信息,故压缩时一般先向这些区块开刀。

数据块:
除了文件头,其中四大数据块和文件尾都是由统一的数据块文件结构描述的:
        Chunk Length: 4byte
        Chunk Type:   4byte
        Chunk Data:   Chunk Length的长度
        Chunk CRC:    4byte
例如IHDR块的数据长度为13,既
        Chunk Length = 13
        Chunk Type = "IHDR"

文件头:
用来标示PNG文件,为固定的64个字节:0x89504e47 0x0d0a1a0a

IHDR块:
用来描述图像的基本信息,其格式为:
       图像宽:    4byte
       图像高:    4byte
       图像色深: 4byte
       颜色类型: 1byte
       压缩方法: 1byte
       滤波方法: 1byte
       扫描方法: 1byte
曾经有人问过我,撒叫滤波方法和扫描方法,汗,说实话我也不知道,不过我们是在做手机游戏,不是在搞图形学不是嘛。

PLTE块:
这个就是传说中放置调色盘数据的地方啦,其格式为:
      循环
           RED:    1byte
           GREEN:1byte
           BLUE:  1byte
      END
循环长度嘛,不就是Chunk Length / 3的长度嘛,而且Chunk Length一定为3的倍数。

tRNS块:
这个块时有时无,主要是看你是否使用了透明色。该区块的格式为:
      循环
           if(对应调色盘颜色非透明)
               0xFF:  1byte
           else
               0x00:  1byte
      END
循环长度为调色盘的颜色数,相当于调色盘颜色表的一个对应表,标识该颜色是否透明,0xFF不透明,0x00透明。故如果用UltraEdit查看PNG文件的二进制编码,如果看到一大片FF,一般就是tRNS区块啦,因为一个PNG文件一般只有一个透明色。

IDAT块:
这个就是存放图像数据的地方啦,这里要注意的是一个PNG文件可能有多个IDAT区块,而其他三大区块只可能有一个。
IDAT 区块是经过压缩的,所以数据不可读,压缩算法一般为LZ77滑动窗口算法,如果硬要看里面的数据的话,用zlib库也是可以的,CoCoMo当年就见过 Windows Mobile上的帝国时代巨变态的用zlib库压缩和解压该区块来进一步减少PNG文件大小,真是寸K寸金啊。

IEND块:
该区块虽然也按照数据块的结构,但Chunk Data是没有的,所以是固定的96个字节:0x00000000 0x49454e44 0xae426082
PNG图像压缩:
        了解了PNG的文件结构,压缩就有的放矢了。压缩有6个级别,可以根据需要选择。
Level1:读取PNG文件,将除六大块之外的所有区块都过滤掉
Level2:文件头是固定的0x89504e47 0x0d0a1a0a,文件尾是固定的0x00000000 0x49454e44 0xae426082,去掉!
Level3:每个区块的Chunk Type我们是否需要呢?很明显,我们自己写的压缩格式自己应该清楚是按照什么样的顺序,去掉!
Level4:每个区块的Chunk Length我们是否需要呢?
           IHDR块:定长13个字节,明显不需要,去掉。
           PLTE块:最多128个颜色,为撒要用4byte来记录区块长度而不是用1byte来记录颜色数呢?
           tRNS块:既然有颜色数,tRNS又是调色盘颜色表的对应表,既数量与颜色数相同,为撒还需要呢?
           IDAT块:我想这个是唯一需要4byte来记录长度的区块。
Level5:每个区块的Chunk CRC是否需要呢?
           因为计算CRC需要一些时间,但对于字节较少的区块一般可以忽略不计,所以对于这个问题还是由程序员自己决定吧。对于CRC的计算可以参看CoCoMo的另一篇Blog“PNG文件的CRC码计算”
Level6:每个区块我们是否要原封不动的保存期数据呢?
          IHDR块:除了宽、高、色深是需要的,后面那4byte的信息是固定的0x03000000
          PLTE块:为撒要用3byte来表示RGB而不是2byte的565格式?压缩方法可以参看CoCoMo的另一篇Blog“关于PNG图像压缩的一点感悟”
          tRNS块:我想tRNS块是冗余最多的区块了吧,大段大段的0xFF明显没有必要,一般的PNG文件只有一个透明色,为撒要用对应表的方法而不是一个索引来记录到底哪个是透明色呢?由于颜色数最多128,所以只需1byte就可以代替tRNS那么多0xFF啦。
          IDAT块:么想法,如果你够变态,把zlib加进来吧!

PNG图像解压:
        创建了自定义的文件,J2ME端读取后,就面临解压的问题了。我们可以利用此函数来创建Image:
static Image
createImage(byte[] imageData, int imageOffset, int imageLength)
     前提是传入的imageData与PNG未被压缩前的一致。因为PNG文件格式是固定的,所以读取自定义的压缩文件后,开始将那些默认的数据再添加进去,实现解压的目的。下面就开始解压之旅吧!
首先要创建一个ByteArrayOutputStream out,

1.写入文件头:
out.writeInt(0x89504e47);
out.writeInt(0x0d0a1a0a);

2.写入IHDR块
out.writeInt(13);
out.writeInt(0x49484452);  //0x49484452为Chunk Type "IHDR"
out.writeInt(width);
out.writeInt(height);
out.writeByte(depth);
out.writeInt(0x03000000);  //压缩时舍掉的4byte,默认0x03000000
out.writeInt(crc);
其他区块方法一致,故略过。。。

3.写入文件尾
out.writeInt(0x00000000);
out.writeInt(0x49454e44);
out.writeInt(0xae426082);

4.转换成数组,创建Image
byte[] pngBuffer = out.toByteArray();
Image image = Image.createImage(pngBuffer, 0, pngBuffer.length);

哈哈,大功告成。这里注意如果中途数据写入有错误,经常会出现创建Image失败的异常,而且非常不好调试,不过只要自定的压缩格式定下来后,对应的创建Image的函数只要写一次,以后基本不会出问题哈。

PNG图像加解密:
        很多人都担心自己辛苦创作的漂亮的美术图片很easy就被别人拿到了,究其原因是由于PNG文件格式是固定的,稍微了解的人用UltraEdit很容易就能找到IHDR,PLTE等标识了。CoCoMo就经常看GameLoft的图像文件,哈哈。一般是2byte的Length,然后紧接着图片数据,都放在一个文件里,直接拷贝2进制然后粘贴到一个新文件里就是一幅图。后来的加密技术会把PNG分块,例如前100个字节一块,紧接着1K一块,最后剩余字节一块,然后把块顺序打乱,用2byte来记录总长度,1byte记录顺序,但是这并没有从根本上消除IHDR,IEND这些显眼的定位标识,好像在对破解者说:嘿,看,我就在这里!
       现在了解了之前的压缩和解压技术,这个问题也就迎刃而解了,因为Chunk Length,Chunk Type和Chunk CRC这些东西都消失了,甚至连数据块本身的数据都修改了,我可以按照ImageWidth、ImageHeight、ImageDepth的顺序写数据,也可以倒过来写。我想再牛的PNG分析器也是无能为力的吧,唯一可以定位的就只有IDAT区块了,不过就算得到该区块的数据,也应该是一张黑白图。

你可能感兴趣的:(图片压缩)