高质量高解码速度的高彩图象压缩

云风 2002.7.10

2D 游戏中, 图片通常是最占内存的一种资源, 当内存因为游戏用图的量增加而吃紧的时候, 寻找高解码速度的图象压缩算法通常是最直接的解决方案之一. 这些方法的一个共性就是需要 engine 的编写者有较高的汇编能力. 需要自己实现基本的 blit 函数, 再就是解码算法要相当简单, 能够满足实时的要求, 使得可以直接在内存中存放压缩的数据, 在显示的时候, 一边解码一边传送 到显示 buffer 中去.

最为常用的是 RLE 算法, 但是只对 sprite 效果比较明显, 可以抠去大约 30%~50% 的 透明色数据. 其次就是在高彩游戏中使用 256 色数据, 和 256 色游戏不同的是, 往往是采用 多个调色盘, 使得色彩足够丰富, 这样可以节省 50% 的内存空间. 不过对于大块色彩丰富的 图象, 容易形成马赛克, 或一些花斑.

在这里, 云风将介绍一种自己发现的, 更为有效的方法. 我命名为动态 16 色压缩算法. 压缩方法就是将一张高彩图片, 按 16x16 像素块分割开. 对每块 256 个像素点进行统计分析, 找到 16 个不同的颜色, 能够最精确的描述这 256 个像素. 然后以每个像素 4bit 的方式进行 保存下 256 个 index 值. 并在这个块首保存下 16 个 index 对应的 16 种颜色. 每种颜色 16bit (高彩) 这样. 每个 16x16 的块只占用了 4 bits*16*16+16 bits*16=1280 bits=160 bytes. 而以 256 色方式保存却需要 8bits*16*16=256 bytes. 体积只有之 62.5%. 相对高彩的原图, 体积仅为 31.25%. 而只需要适当的编写对应的 blit 函数, 速度比 256 色有过之而无不及.

这个方法的起源于云风对 jpeg 压缩的研究. 有了理论基础, 我在没有看到效果前, 并不对这种压缩结果的画质有丝毫的怀疑. 但是毕竟写出压缩程序, 真正处理出压缩图片欣赏, 更有成功感. 而且恐怕很多朋友会十分怀疑局部 16 色的表现能力. 下面我展示几张图.

原图(170,446 字节)

上面这张是大话西游这个游戏中的一个场景. 采用 jpeg 格式. 让大家看到全貌 :) (在大话西游中, 硬盘上就是以 jpeg 算法压缩的场景, 采用多线程动态加载的方式管理图片, 用辅线程只在需要的时候将屏幕附近的场景读入内存,并解码. 这也是一种节省内存的好方法)

为了能更细致的比较, 我将抽取局部, 用几种方案压缩比较, 并采用无损的 png, 格式放在下面:

原图一角(208,649 字节) 这是原图一角. 真彩格式, 当然我们游戏里使用的高彩色. 我们可以看到右上角的蓝天白云 非常细腻.

Octree(71,265 字节) 我用 octree 算法, 转换成 256 色的图片, 可以看到, 画面的右边的云层已经明显出了问题.

Octree Dither(78,740 字节) 当我用 octree 的同时, 做了一下 Dither (误差扩散) 情况有了明显的改善, 但是由于 256 色色彩数的限制, 那些白云中出现了杂点.

云风的压缩算法(170,446 字节) 我们可以看一下动态16色的算法的效果了. 即使不加 Dither 也很不错不是? 做一些细微的 Dither 会更好. 我们留在下面展示.

其实, 上面这一组图尚不能体现这种新算法相对 256 色图片的优势. 我们下面来看 mm 吧 :)

过度色(71,265 字节) 这张图有很多的过渡色, 这将是 256 色模式的致命之处

转换为 256 色(71,265 字节) 很糟糕:( 不是吗? 过渡色的细节全丢失了, 留下了大快的色斑. 这就是简单用 Octree 算法裁减 调色盘的结果.

转换为 256 色, 并 Dither(78,740 字节) 经过 Dither 后, 基本不错了:) 只是 mm 的面部变得粗糙, 画面上的背景也多了一些脏乱的杂点.

下面还是用无损压缩暂时细节, 但为了节约图片的显示时间, 我只放了局部上面. (动态16色技术, 和 256 色图片不同, 它的局部不会被整体影响, 而 256 色图将受到整体调色盘的牵制)

云风的压缩算法(91,492 字节) 换用云风本文中提到的算法, 效果比简单的 256 色图片好很多. 当然, 细节还是很难看.

云风的压缩算法加入Dither处理(59,140 字节) 现在 Dither 一下. 情况能改善许多.

8*8采样(110,516 字节) 当需要提高画质的时候, 我们可以把 16x16 的采样块减少到 12x12 甚至 8x8. 当 8x8 为一个块的时候. 每快的数据为 64bytes (其实 32bytes 为颜色索引) 这和 256 色图片相当. 只是画质不可同日而语. 几乎接近了无损压缩.

在此算法的基础上, 我们还可以做简单的 RLE 压缩等等处理. 达到更高的压缩比. 希望大家看了本文能有所启发 :) 我个人认为此方法优于将图片转换成 256 色来节省空间. 它的画质比 256 色高, 而压缩比更大. 如果依此定义出一种新的图象格式, 在技术上完全能取代 GIF. 当然 gif 的流行并非技术推动出来的.

附: 在色彩空间里找 16 个颜色可以最接近, 16*16=256 个像素 的颜色这个问题可能比一般人想的稍微复杂一点, 由于颜色教少, 像传统的 octree 基于色彩空间裁减的方法我试了后不太适用. 在写 encode 程序的时候还尝试过用模拟退火法逼近最优解. 可惜收敛速度过慢. 后来采用了一个苯方法, 建立一个 256*256 的表, 硬在里面搜索和匹配划简, 反而取得了很好的效果. 就是速度太慢. 编码一张 1024x768 的图片, 我的 1GHz CPU 都感觉慢吞吞的. 实在是有待改进啊.