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行程编码在GUI图标压缩中的流程 原始图标数据 按行扫描像素 检测连续重复像素 (游程长度 >= 2) 编码 判断是否重复 编码:[次数][颜色值] 编码:[0x00][长度][数据] 合并输出压缩数据 解压:逐段还原像素 解压速度极快 仅需循环+内存拷贝 适合嵌入式实时系统

从这张图能看出来,RLE的整个流程非常直接。没有复杂的变换,没有多轮迭代。这也是为什么它在嵌入式系统里这么受欢迎——简单、可靠、可预测。

最后说一句,RLE虽然简单,但别小看它。在合适的场景下,它能把图标资源压缩到原来的三分之一甚至更小。而且解压速度几乎不影响UI响应。对于资源紧张的嵌入式系统来说,这已经是很划算的买卖了。