正确的RGB888到RGB565转换

问题描述 投票:0回答:2

我找到了三种 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);
c algorithm rgb data-conversion
2个回答
2
投票

前两个产生相同的结果。

因为它们是等价的,至少对于支持

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 强度。 也可能存在性能差异,但如果这对您很重要,那么您应该进行衡量。 对于大多数目的,我倾向于选择一种位移方法的一致性,但您可能会有不同的想法。


0
投票

@CraigEstey 我运行了几次测试,结果相同。该代码在 RPi Pico 上运行。我本以为方法#2 也是最快的;令#1 感到惊讶的是。 – 迈克

嗯...我在一台较旧的 x86 PC 上运行测试(没有 RPi)。还是...

  1. 我知道方法 2 是最快的(
    gcc
  2. 但是,使用
    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)
© www.soinside.com 2019 - 2024. All rights reserved.