4. RLE行程编码:RLE原理、在GUI图标压缩中的应用、解压速度与压缩比的平衡
RLE,全称Run-Length Encoding,中文叫行程编码。说白了,就是一种极其朴素的压缩思路——把连续重复的数据,用“重复次数 + 数据值”来代替。
举个例子,你有一串像素数据:0x00 0x00 0x00 0x00 0x00 0xFF 0xFF 0xFF。用RLE编码后,就变成了 0x05 0x00 0x03 0xFF。5个0x00,3个0xFF,干净利落。
我第一次接触RLE是在做一款低端MCU的GUI时。那时候Flash空间抠得紧,每1KB都得省着用。图标资源动不动就几百字节,压缩一下能省出一半空间。嗯,RLE就成了我的首选。
RLE的核心原理
RLE的编码方式其实有好几种变体,但最常用的是下面这种:
- 连续重复的数据:编码为
[重复次数] [数据值] - 非重复的数据:编码为
[0x00] [数据长度] [原始数据...](或者用标志位区分)
为什么要有非重复数据的处理?你想想看,如果图标里有一段颜色完全不重复的像素,硬要编码成“1个A、1个B、1个C……”,那压缩比反而会变大,得不偿失。
关键点:RLE的压缩效率完全取决于数据的“游程”长度。游程越长,压缩比越高。对于GUI图标来说,纯色区域、渐变过渡、边框阴影,都是RLE的“肥肉”。
在GUI图标压缩中的应用
我做过一个项目,图标是16x16像素的,每个像素用16位色(RGB565)。原始大小是512字节。用RLE压缩后,平均能压到200-300字节。压缩比大概在1.5:1到2.5:1之间。
具体怎么用?我一般会把图标数据按行处理。每一行像素,从左到右扫描,遇到连续相同的颜色就合并。这样解压的时候,也是一行一行地还原,逻辑简单,速度也快。
我的习惯:对于小图标(比如16x16、32x32),我建议按行RLE。对于大图标或全屏图片,可以考虑按块RLE,但解压逻辑会复杂一些。我个人更倾向于简单可靠的方案。
下面是一个简单的RLE编码示例(C语言风格伪代码):
// 编码:输入像素数据,输出RLE压缩数据
void rle_encode(uint16_t *input, int len, uint8_t *output, int *out_len) {
int i = 0;
int o = 0;
while (i < len) {
uint16_t cur = input[i];
int count = 1;
while (i + count < len && input[i + count] == cur && count < 255) {
count++;
}
output[o++] = count; // 重复次数
output[o++] = cur >> 8; // 高字节
output[o++] = cur & 0xFF; // 低字节
i += count;
}
*out_len = o;
}
注意:上面的代码假设重复次数不超过255。如果游程超过255,需要拆分成多个编码段。我在实际项目中就踩过这个坑——一个纯色背景的图标,连续几百个相同像素,结果编码溢出,解压出来花屏了。后来加了个分段逻辑才解决。
解压速度与压缩比的平衡
RLE最大的优势是什么?解压速度极快。没有复杂的数学运算,没有查表,就是简单的循环和内存拷贝。在嵌入式系统里,这简直是福音。
但代价是什么?压缩比有限。对于复杂纹理、照片类图片,RLE基本没用,甚至可能越压越大。
我整理了一个对比表格,方便你直观感受:
| 场景 | 原始大小 | RLE压缩后 | 压缩比 | 解压速度 |
|---|---|---|---|---|
| 纯色图标(16x16) | 512字节 | ~50字节 | 10:1 | 极快 |
| 简单UI按钮(32x32) | 2KB | ~500字节 | 4:1 | 快 |
| 渐变背景(128x64) | 16KB | ~8KB | 2:1 | 快 |
| 照片类图片(64x64) | 8KB | ~12KB | 0.67:1(膨胀) | 快(但没用) |
从表格能看出来,RLE最适合那种“大块纯色”的场景。GUI图标正好符合这个特点——按钮、边框、文字、简单图形,都是RLE的菜。
我曾经在一个项目里,为了追求更高的压缩比,尝试过先对图标做颜色量化(减少颜色数),再用RLE压缩。效果确实不错,压缩比能再提升30%左右。但代价是图标颜色失真,UI设计师差点没把我骂死。后来我学乖了——压缩比够用就行,别过度优化。
平衡点建议:对于触摸屏上的GUI图标,我一般把RLE压缩比目标定在2:1到3:1之间。低于2:1,说明图标本身就不适合RLE,不如换其他算法。高于3:1,就要小心是否过度压缩导致失真。
RLE在嵌入式系统中的实现要点
如果你打算在嵌入式系统里用RLE,下面几个点值得注意:
- 内存对齐:解压时最好按字(16位或32位)操作,避免逐字节拷贝带来的性能损失。
- 缓冲区管理:解压后的数据可能比原始数据大(比如遇到非重复数据段),要预留足够的输出缓冲区。
- 中断安全:如果解压过程在中断里执行,要确保不会阻塞太久。RLE解压很快,但也要注意。
避坑指南:我曾经在一个RTOS项目里,把RLE解压放在了一个低优先级任务里。结果图标加载时,高优先级任务频繁抢占,导致图标显示出现撕裂。后来我把解压放到一个中等优先级任务,并加了双缓冲,问题才解决。
下面这张SVG图,展示了RLE在GUI图标压缩中的完整流程:
从这张图能看出来,RLE的整个流程非常直接。没有复杂的变换,没有多轮迭代。这也是为什么它在嵌入式系统里这么受欢迎——简单、可靠、可预测。
最后说一句,RLE虽然简单,但别小看它。在合适的场景下,它能把图标资源压缩到原来的三分之一甚至更小。而且解压速度几乎不影响UI响应。对于资源紧张的嵌入式系统来说,这已经是很划算的买卖了。