Openharmory中的图形像素格式转换
ARGB8888 → RGB565 像素格式转换详解
在嵌入式 Linux 显示开发中,ARGB8888 到 RGB565 的像素格式转换是一项高频操作。无论是帧缓冲区(framebuffer)输出、DRM/KMS 显示管线,还是 GUI 渲染引擎,都可能涉及这一转换。本文从原理到实现,系统梳理这一转换的方方面面。
一、两种格式的基本结构
ARGB8888(32位/像素)
每个像素占用 4 字节,各通道均为 8 位,取值范围 0255:
表格
| 字节偏移 | 通道 | 位数 | 取值范围 |
|---|---|---|---|
| Byte 3 | Alpha (A) | 8 bit | 0255 |
| Byte 2 | Red (R) | 8 bit | 0255 |
| Byte 1 | Green (G) | 8 bit | 0255 |
| Byte 0 | Blue (B) | 8 bit | 0255 |
注意:字节序(大端/小端)会影响实际内存排列,以下代码示例均基于小端序(x86/ARM 默认)。
RGB565(16位/像素)
每个像素占用 2 字节,无 Alpha 通道,位域分配如下:
Bit: 15 14 13 12 11 | 10 9 8 7 6 5 | 4 3 2 1 0
─────────────── ────────────────── ──────────────────
R4 R3 R2 R1 R0 G5 G4 G3 G2 G1 G0 B4 B3 B2 B1 B0
表格
| 通道 | 位数 | 取值范围 | 位掩码 |
|---|---|---|---|
| Red | 5 bit | 031 | 0xF800 |
| Green | 6 bit | 063 | 0x07E0 |
| Blue | 5 bit | 031 | 0x001F |
绿色通道多分配 1 位,是因为人眼对绿色光谱最为敏感。
二、转换原理
核心思路
ARGB8888 → RGB565 的本质是位宽压缩 + 丢弃 Alpha 通道:
- 丢弃 Alpha 通道:ARGB8888 的 A 通道在 RGB565 中不存在,直接忽略。
- 位宽截断:将 8 位通道压缩到 5 位或 6 位,保留高位,丢弃低位。
- 位域拼接:将截断后的 R、G、B 按 RGB565 的位域布局组合成 16 位值。
位操作详解
ARGB8888: [A7..A0] [R7..R0] [G7..G0] [B7..B0]
↓ 转换
RGB565: [R7..R3] [G7..G2] [B7..B3]
\__5bit__/ \__6bit__/ \__5bit__/
具体操作:
- R:取 8 位的高 5 位,右移 3 位 →
R8 >> 3 - G:取 8 位的高 6 位,右移 2 位 →
G8 >> 2 - B:取 8 位的高 5 位,右移 3 位 →
B8 >> 3
三、代码实现
基础版:直接位移
#include <stdint.h>
/**
* ARGB8888 转 RGB565(基础版)
* @param argb 32位 ARGB8888 像素值(小端序:0xAARRGGBB)
* @return 16位 RGB565 像素值
*/
static inline uint16_t argb8888_to_rgb565(uint32_t argb)
{
uint8_t r = (argb >> 16) & 0xFF; // 提取 R 通道
uint8_t g = (argb >> 8) & 0xFF; // 提取 G 通道
uint8_t b = (argb >> 0) & 0xFF; // 提取 B 通道
return (uint16_t)((r >> 3) << 11) | // R: 高5位,放到 bit[15:11]
((g >> 2) << 5) | // G: 高6位,放到 bit[10:5]
(b >> 3); // B: 高5位,放到 bit[4:0]
}
优化版:减少中间变量
/**
* ARGB8888 转 RGB565(优化版,减少移位次数)
* 利用整体右移来减少操作
*/
static inline uint16_t argb8888_to_rgb565_fast(uint32_t argb)
{
// 直接对 32 位值做位移和掩码
return (uint16_t)(((argb >> 8) & 0xF800) | // R: (argb>>8) 后取高5位
((argb >> 5) & 0x07E0) | // G: (argb>>5) 后取高6位
((argb >> 3) & 0x001F)); // B: (argb>>3) 后取高5位
}
批量转换:整帧缓冲区
/**
* 批量将 ARGB8888 帧缓冲区转换为 RGB565
* @param src 源缓冲区(ARGB8888,4字节/像素)
* @param dst 目标缓冲区(RGB565,2字节/像素)
* @param count 像素总数(width × height)
*/
void convert_argb8888_to_rgb565(const uint32_t *src,
uint16_t *dst,
uint32_t count)
{
for (uint32_t i = 0; i < count; i++) {
uint32_t pixel = src[i];
dst[i] = (uint16_t)(((pixel >> 8) & 0xF800) |
((pixel >> 5) & 0x07E0) |
((pixel >> 3) & 0x001F));
}
}
高精度版:带四舍五入
直接右移会截断低位,导致颜色偏暗。Apple 的 vImage 库采用四舍五入策略来减少误差:
/**
* ARGB8888 转 RGB565(带四舍五入,精度更高)
* 参考 Apple vImage 的转换公式
*/
static inline uint16_t argb8888_to_rgb565_round(uint32_t argb)
{
uint8_t r = (argb >> 16) & 0xFF;
uint8_t g = (argb >> 8) & 0xFF;
uint8_t b = (argb >> 0) & 0xFF;
// 四舍五入公式(来自 Apple vImage)
uint16_t r5 = (uint16_t)((r * (31 * 2) + 255) / (255 * 2)); // 0~31
uint16_t g6 = (uint16_t)((g * 63 + 127) / 255); // 0~63
uint16_t b5 = (uint16_t)((b * 31 + 127) / 255); // 0~31
return (r5 << 11) | (g6 << 5) | b5;
}
四、精度损失与色带问题
为什么会有色带(Color Banding)?
ARGB8888 可表示约 1677 万色(2²⁴),而 RGB565 仅能表示 65536 色(2¹⁶)。转换时低位信息被永久丢弃,在渐变区域会出现明显的色阶断层。
常用颜色对照
表格
| 颜色 | ARGB8888 | RGB565 |
|---|---|---|
| 纯黑 | 0xFF000000 |
0x0000 |
| 纯白 | 0xFFFFFFFF |
0xFFFF |
| 纯红 | 0xFFFF0000 |
0xF800 |
| 纯绿 | 0xFF00FF00 |
0x07E0 |
| 纯蓝 | 0xFF0000FF |
0x001F |
| 黄色 | 0xFFFFFF00 |
0xFFE0 |
| 青色 | 0xFF00FFFF |
0x07FF |
| 洋红 | 0xFFFF00FF |
0xF81F |
缓解色带的方案:抖动(Dithering)
在转换前对像素值加入微小随机噪声,打散色阶边界,视觉上可显著改善渐变效果。Apple vImage 提供了带抖动参数的转换接口。
// 简单的有序抖动示例(Bayer 2x2 矩阵)
static const int bayer[2][2] = {{0, 2}, {3, 1}};
static inline uint16_t argb8888_to_rgb565_dither(uint32_t argb, int x, int y)
{
int threshold = bayer[y % 2][x % 2] - 2; // -2 ~ +1
int r = ((argb >> 16) & 0xFF) + threshold;
int g = ((argb >> 8) & 0xFF) + threshold;
int b = ((argb >> 0) & 0xFF) + threshold;
// 钳位到 0~255
r = r < 0 ? 0 : (r > 255 ? 255 : r);
g = g < 0 ? 0 : (g > 255 ? 255 : g);
b = b < 0 ? 0 : (b > 255 ? 255 : b);
return (uint16_t)((r >> 3) << 11) | ((g >> 2) << 5) | (b >> 3);
}
五、在 Linux DRM/VKMS 中的应用
在 Linux 内核的 DRM 子系统中,VKMS(Virtual KMS)驱动实现了像素格式转换的框架。其核心思路是:
- 读取阶段:通过
pixel_read回调将源格式(如 ARGB8888)转换为内部统一格式pixel_argb_u16。 - 合成阶段:在
pixel_argb_u16格式下完成 alpha 混合。 - 写回阶段:通过
pixel_write回调将合成结果转换为目标格式(如 RGB565)。
其中 RGB565 的写回函数实现如下(来自内核源码):
static void argb_u16_to_RGB565(u8 *dst_pixels, struct pixel_argb_u16 *in_pixel)
{
u16 *pixels = (u16 *)dst_pixels;
s32 r = in_pixel->r >> 8; // u16 转 u8
s32 g = in_pixel->g >> 8;
s32 b = in_pixel->b >> 8;
*pixels = cpu_to_le16(r << 11 | g << 5 | b);
}
六、总结
| 对比项 | ARGB8888 | RGB565 |
|---|---|---|
| 位深 | 32 bit/px | 16 bit/px |
| 显存占用 | 4 × W × H | 2 × W × H |
| 颜色数 | ~1677万 | 65536 |
| Alpha 通道 | ✅ 支持 | ❌ 不支持 |
| 适用场景 | 高质量渲染、合成 | 资源受限的显示输出 |
ARGB8888 → RGB565 的转换本质上是一次有损压缩:丢弃 Alpha 通道,并将 R/B 从 8 位截断到 5 位、G 从 8 位截断到 6 位。在显存紧张的场景下(如 8MB RAM 堆),切换到 RGB565 可以将帧缓冲区占用减半,是嵌入式显示系统中非常实用的优化手段。
更多推荐

所有评论(0)