我找到了三种 RGB888 到 RGB565 转换算法,但我不确定哪个是最正确的。 前两个产生相同的结果。 第三个产生不同的结果,但与在线计算器一致。 第三个是有道理的,因为它确实可以缩放。 但前两个会更快,因为没有整数乘法或除法。
uint8_t r8, g8, b8;
uint16_t r5, g6, b5, val;
r8 = someredvalue;
g8 = somegreenvalue;
b8 = somebluevalue;
// first method:
b5 = (b8 >> 3) & 0x001F;
g6 = ((g8 >> 2) << 5) & 0x07E0;
r5 = ((r8 >> 3) << 11) & 0xF800;
val = (r5 | g6 | b5);
printf("%d, %d, %d, %u\n", r8, g8, b8, val);
// second method:
val = ((r8 & 0b11111000) << 8) | ((g8 & 0b11111100) << 3) | (b8 >> 3);
printf("%d, %d, %d, %u\n", r8, g8, b8, val);
// third method:
r5 = r8 * 31 / 255;
g6 = g8 * 63 / 255;
b5 = b8 * 31 / 255;
val = (r5 << 11) | (g6 << 5) | (b5);
printf("%d, %d, %d, %u\n", r8, g8, b8, val);
前两个产生相同的结果。
因为它们是等价的,至少对于支持
0bxxx
风格的二进制常量的编译器来说是等价的,这是对标准 C 的扩展。两者都将 565 格式的颜色值作为相应颜色最高有效位的正确数量888 格式的值。
第三个产生不同的结果,但与在线计算器一致。
第三个是有道理的,因为它确实可以缩放。
他们都进行缩放。 第三个只是使用与其他稍微不同的比例因子。
考虑红色通道。 五位可以表示从 0 到 31 的值。八位可以表示从 0 到 255 的值。从数学上讲,从后一个范围到前一个范围的线性缩放因子是 31 / 255,如第三种方法所使用的。 另外两个除以 8(通过移位),这在算术上与缩放 32 / 256 相同。当然,它们不是相同的缩放因子,但它们很接近。
但是在您认为方法 3 更正确之前,您应该考虑整数运算的影响。 例如,哪些 8 位红色值映射到 5 位表示中的 31? 使用方法 3,只有一种方法:255。值 254 缩放到略小于 31,即被截断为 30。因此,就每种 RGB565 颜色对应有多少 RGB888 颜色而言,这种转换变化是不均匀的。
另一方面,移位方法在这个意义上是统一的。 以这种方式转换时,每种 RGB565 颜色对应于相同数量 (256) 的 RGB888 颜色。
我不确定哪个是最正确的。
总体而言,两次重新缩放产生的结果非常相似,每个通道相差 1 个单位。 两者都是线性的,直到截断为整数。 从绝对意义上讲,两者都不比另一个更正确。 位移替代方案的特点是将结果均匀分布在 RGB565 空间上。 另一种替代方案的特点是仅将最大 RGB888 强度映射到最大 RGB565 强度。 也可能存在性能差异,但如果这对您很重要,那么您应该进行衡量。 对于大多数目的,我倾向于选择一种位移方法的一致性,但您可能会有不同的想法。
@CraigEstey 我运行了几次测试,结果相同。该代码在 RPi Pico 上运行。我本以为方法#2 也是最快的;令#1 感到惊讶的是。 – 迈克
嗯...我在一台较旧的 x86 PC 上运行测试(没有 RPi)。还是...
gcc
)m1
clang
更快。但是,这是因为它使用了循环展开的 x86 SIMD 指令。为了让我们达成共识(只是为了好玩),我附上了针对您的算法的完整基准程序。
这是测试程序(
all.c
):
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#if USE_BYTE_ARG
typedef unsigned char arg_t;
#else
typedef unsigned int arg_t;
#endif
typedef unsigned short u8;
typedef unsigned short u16;
typedef unsigned int u32;
#ifndef MAX_Y
#define MAX_Y 1080
#endif
#ifndef MAX_X
#define MAX_X 1920
#endif
u8 bigbuf_888[MAX_Y * MAX_X * 3];
u16 bigbuf_565[MAX_Y * MAX_X];
#define inline_always static inline __attribute__((always_inline))
inline_always u32
m1(arg_t r8,arg_t g8,arg_t b8)
{
u32 b5 = (b8 >> 3) & 0x001F;
u32 g6 = ((g8 >> 2) << 5) & 0x07E0;
u32 r5 = ((r8 >> 3) << 11) & 0xF800;
u32 val = (r5 | g6 | b5);
return val;
}
inline_always u32
m2(arg_t r8,arg_t g8,arg_t b8)
{
u32 val = ((r8 & 0b11111000) << 8) | ((g8 & 0b11111100) << 3) | (b8 >> 3);
return val;
}
inline_always u32
m3(arg_t r8,arg_t g8,arg_t b8)
{
u32 r5 = r8 * 31 / 255;
u32 g6 = g8 * 63 / 255;
u32 b5 = b8 * 31 / 255;
u32 val = (r5 << 11) | (g6 << 5) | (b5);
return val;
}
inline_always u32
m4(arg_t r8,arg_t g8,arg_t b8)
{
u32 r5 = (r8 * 31) / 256;
u32 g6 = (g8 * 63) / 256;
u32 b5 = (b8 * 31) / 256;
u32 val = (r5 << 11) | (g6 << 5) | (b5);
return val;
}
#define CAT(_a,_b) \
_a##_b
#define DOTEST(_m) \
CAT(dotest_,_m)
#define M m1
#include "test.c"
#define M m2
#include "test.c"
#define M m3
#include "test.c"
#define M m4
#include "test.c"
typedef struct tst {
void (*tst_fnc)(u16 *dst,const u8 *src,u32 count);
const char *tst_sym;
double tst_elap;
} tst_t;
#define DEFTST(_sym) \
{ .tst_fnc = dotest_##_sym, .tst_sym = #_sym }
tst_t tstlist[] = {
DEFTST(m1),
DEFTST(m2),
DEFTST(m3),
DEFTST(m4),
};
#define FORALL \
tst_t *tst = tstlist; tst < &tstlist[countof(tstlist)]; ++tst
#define countof(_arr) \
sizeof(_arr) / sizeof(_arr[0])
double
tscgetf(void)
{
struct timespec ts;
double sec;
clock_gettime(CLOCK_MONOTONIC,&ts);
sec = ts.tv_nsec;
sec /= 1e9;
sec += ts.tv_sec;
return sec;
}
void
dotest(tst_t *tst)
{
tst->tst_elap = 1e9;
for (int iter = 1; iter <= 10; ++iter) {
double tscbeg = tscgetf();
tst->tst_fnc(bigbuf_565,bigbuf_888,MAX_Y * MAX_X);
double tscend = tscgetf();
tscend -= tscbeg;
if (tscend < tst->tst_elap)
tst->tst_elap = tscend;
}
#if 0
printf("%.9f %s\n",tst->tst_elap,tst->tst_sym);
#endif
}
int
tstcmp(const void *alhs,const void *arhs)
{
const tst_t *lhs = alhs;
const tst_t *rhs = arhs;
int cmp;
do {
cmp = -1;
if (lhs->tst_elap < rhs->tst_elap)
break;
cmp = 1;
if (lhs->tst_elap > rhs->tst_elap)
break;
cmp = 0;
} while (0);
return cmp;
}
int
main(void)
{
printf("%ux%u\n",MAX_X,MAX_Y);
for (FORALL)
dotest(tst);
qsort(tstlist,countof(tstlist),sizeof(tst_t),tstcmp);
tst_t *otst = tstlist;
for (FORALL) {
printf("%.9f %s (%.3fx slower than %s)\n",
tst->tst_elap,tst->tst_sym,
tst->tst_elap / otst->tst_elap,otst->tst_sym);
#if 0
otst = tst;
#endif
}
return 0;
}
这是它需要的
test.c
(必须是一个单独的文件):
void
DOTEST(M)(u16 *rgb565,const u8 *rgb888,u32 count)
{
for (u32 idx = 0; idx < count; idx += 1, rgb888 += 3)
rgb565[idx] = M(rgb888[0],rgb888[1],rgb888[2]);
}
#undef M
使用
gcc
8.3.1 编译:
gcc -Wall -O3 -o all all.c
输出为:
1920x1080
0.003618755 m2 (1.000x slower than m2)
0.003766094 m1 (1.041x slower than m2)
0.005401992 m4 (1.493x slower than m2)
0.007230262 m3 (1.998x slower than m2)
使用
clang
7.0.1 编译:
clang -Wall -O3 -o all all.c
输出:
1920x1080
0.002214470 m1 (1.000x slower than m1)
0.002892669 m2 (1.306x slower than m1)
0.005501939 m4 (2.485x slower than m1)
0.007757501 m3 (3.503x slower than m1)