如何将16位RGB565转换为24位RGB888?

Jos*_*Lee 20 algorithm rgb colors

我已经掌握了16位rgb565图像(特别是Android帧缓冲区转储),我想将其转换为24位rgb888,以便在普通显示器上查看.

问题是,如何将5位或6位通道转换为8位?明显的答案是改变它.我开始写这篇文章:

puts("P6 320 480 255");
uint16_t buf;
while (read(0, &buf, sizeof buf)) {
    unsigned char red = (buf & 0xf800) >> 11;
    unsigned char green = (buf & 0x07e0) >> 5;
    unsigned char blue = buf & 0x001f;
    putchar(red << 3);
    putchar(green << 2);
    putchar(blue << 3);
}
Run Code Online (Sandbox Code Playgroud)

但是,这没有我想要的一个属性,而是用于0xffff映射到0xffffff,而不是0xf8fcf8.我需要以某种方式扩展价值,但我不确定这应该如何运作.

Android SDK附带了一个名为ddms(Dalvik Debug Monitor)的工具,可以捕获屏幕截图.据我所知,通过阅读代码,它实现了相同的逻辑; 但它的截图不同,白色映射为白色.

这是原始帧缓冲,ddms 的智能转换,以及上述算法的哑转换.请注意,后者稍微更暗,更绿.

(顺便说一下,这个转换是在ffmpeg中实现的,但它只是执行上面列出的哑转换,使LSB全部为零.)

我想我有两个问题:

  • 将rgb565转换为rgb888最明智的方法是什么?
  • DDMS如何转换其截图?

cle*_*tus 21

您希望将这些中的每一个从5/6位空间映射到8位空间.

  • 5位= 32个值
  • 6位= 64个值
  • 8位= 256个值

您正在使用的代码是采用天真的方法x5*256/32 = x8其中256/32 = 8并且乘以8是左移3但是,正如您所说,这不一定填满新的数字空间"正确的".最大值的5到8是31到255,其中包含解决方案的线索.

x8 = 255/31 * x5
x8 = 255/63 * x6
Run Code Online (Sandbox Code Playgroud)

其中x5,x6x8分别是5,6和8位的值.

现在有一个关于实现这个的最佳方法的问题.它确实涉及除法和整数除法,你将失去任何余数结果(基本上向下舍入)所以最好的解决方案可能是做浮点运算然后将一半向上舍回到整数.

通过简单地使用此公式为5位和6位转换中的每一个生成查找表,可以大大加快这一点.

  • 刚刚运行测试,这相当于重复使用前三位,但有时它会增加或减少1.我想那时没有真正的优势. (2认同)

Ano*_*ous 20

我的几分钱:

如果您关心精确映射,但快速算法可以考虑这个:

R8 = ( R5 * 527 + 23 ) >> 6;
G8 = ( G6 * 259 + 33 ) >> 6;
B8 = ( B5 * 527 + 23 ) >> 6;
Run Code Online (Sandbox Code Playgroud)

它仅使用:MUL,ADD和SHR - >所以它非常快!从另一方面来看,它与100%兼容到具有适当舍入的浮点映射:

// R8 = (int) floor( R5 * 255.0 / 31.0 + 0.5);
// G8 = (int) floor( G6 * 255.0 / 63.0 + 0.5);
// B8 = (int) floor( R5 * 255.0 / 31.0 + 0.5);
Run Code Online (Sandbox Code Playgroud)

一些额外的分数:如果你对888到565转换感兴趣,这也很有效:

R5 = ( R8 * 249 + 1014 ) >> 11;
G6 = ( G8 * 253 +  505 ) >> 10;
B5 = ( B8 * 249 + 1014 ) >> 11;
Run Code Online (Sandbox Code Playgroud)

使用强力搜索和早期拒绝的常数被发现,以加快速度.

  • 你是如何计算这些价值的?(我想对其他频道深度使用相同的技巧.) (5认同)
  • 要学究,floor(x + 0.5)与round(x)是不同的东西,而您实际上想要后者。floor(x + 0.5)对0.5f-1ulp做错了,将其四舍五入。0.5f-1ulp不会在这里发生。您可以通过从最后要进行的右移开始计算这样的值(例如,在本例中为/ 64),然后乘以比例因子(255/31)。您得到255 * 64/31 = 526.45。这是一个近似值。幸运的是,没有多少数字可以使用。因此,如果您足够接近,通常可以得到完全正确的结果。 (2认同)

Jak*_*org 16

你可以移动然后或者用最重要的位移动; 即

Red 10101 becomes 10101000 | 101 => 10101101
    12345         12345---   123    12345123
Run Code Online (Sandbox Code Playgroud)

这具有您寻找的属性,但它不是从一个空间到另一个空间的值的最线性映射.但速度很快.:)

Cletus的答案更完整,可能更好.:)


Cam*_*mer 5

iOS vImage转换

iOS的加速框架介绍以下算法的vImageConvert_RGB565toARGB8888功能:

Pixel8 alpha = alpha
Pixel8 red   = (5bitRedChannel   * 255 + 15) / 31
Pixel8 green = (6bitGreenChannel * 255 + 31) / 63
Pixel8 blue  = (5bitBlueChannel  * 255 + 15) / 31
Run Code Online (Sandbox Code Playgroud)

对于一次性转换,这将足够快,但如果您想要处理许多帧,您想要使用类似iOS vImage转换的东西,或者使用NEON内在函数自己实现.

来自ARMs社区论坛教程

首先,我们将研究将RGB565转换为RGB888.我们假设寄存器q0中有8个16位像素,我们希望将红色,绿色和蓝色分成三个寄存器d2到d4的8位元素.

 vshr.u8      q1, q0, #3      @ shift red elements right by three bits,
                                @  discarding the green bits at the bottom of
                                @  the red 8-bit elements.
vshrn.i16    d2, q1, #5      @ shift red elements right and narrow,
                                @  discarding the blue and green bits.
vshrn.i16    d3, q0, #5      @ shift green elements right and narrow,
                                @  discarding the blue bits and some red bits
                                @  due to narrowing.
vshl.i8      d3, d3, #2      @ shift green elements left, discarding the
                                @  remaining red bits, and placing green bits
                                @  in the correct place.
vshl.i16  q0, q0, #3      @ shift blue elements left to most-significant
                                @  bits of 8-bit color channel.
vmovn.i16    d4, q0          @ remove remaining red and green bits by
                                @  narrowing to 8 bits.
Run Code Online (Sandbox Code Playgroud)

上面的注释中描述了每条指令的效果,但总的来说,在每个通道上执行的操作是:使用移位移除相邻通道的颜色数据,以将位推出元素的任一端.使用第二个移位将颜色数据定位在每个元素的最高有效位中,并缩小以将元素大小从16位减少到8位.

注意在此序列中使用元素大小来寻址8和16位元素,以实现某些屏蔽操作.

一个小问题

你可能会注意到,如果你使用上面的代码转换为RGB888格式,你的白人不是很白.这是因为,对于每个通道,最低的两位或三位是零,而不是一位; RGB565中表示的白色(0x1F,0x3F,0x1F)在RGB888中变为(0xF8,0xFC,0xF8).这可以使用带插入的移位来固定,以将一些最高有效位放入低位.

对于Android特定示例,我发现了用内在函数编写的YUV到RGB转换.