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

时间:2010-03-14 15:11:39

标签: algorithm colors rgb

我已经掌握了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);
}

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

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

以上为raw framebuffersmart conversion为ddms,dumb conversion为上述算法。请注意,后者稍微更暗,更绿。

(顺便说一句,转换is implemented in ffmpeg,但它只是执行上面列出的哑转换,让LSB全部为零。)

我想我有两个问题:

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

9 个答案:

答案 0 :(得分: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

其中x5x6x8分别为5位,6位和8位值。

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

只需使用此公式为5位和6位转换中的每一位生成查找表,就可以大大加快速度。

答案 1 :(得分:20)

我的几分钱:

如果你关心精确的映射,你可以考虑快速算法:

R8 = ( R5 * 527 + 23 ) >> 6;
G8 = ( G6 * 259 + 33 ) >> 6;
B8 = ( B5 * 527 + 23 ) >> 6;

仅使用:MUL,ADD和SHR - &gt;所以它很快! 从另一方面来看,它与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);

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

R5 = ( R8 * 249 + 1014 ) >> 11;
G6 = ( G8 * 253 +  505 ) >> 10;
B5 = ( B8 * 249 + 1014 ) >> 11;

使用暴力搜索发现常数,并且早期拒绝,以加快速度。

答案 2 :(得分:16)

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

Red 10101 becomes 10101000 | 101 => 10101101
    12345         12345---   123    12345123

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

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

答案 3 :(得分:5)

iOS vImage转换

iOS Accelerate Framework记录vImageConvert_RGB565toARGB8888函数的以下算法:

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

对于一次性转换,这将足够快,但如果您想要处理许多帧,您想要使用类似iOS vImage转换的内容,或者使用NEON intrinsics自行实现。

来自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.
  

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

     

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

     

一个小问题

     

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

对于Android特定示例,我发现了一个用内在函数编写的YUV-to-RGB conversion

答案 4 :(得分:4)

试试这个:

red5 = (buf & 0xF800) >> 11;
red8 = (red5 << 3) | (red5 >> 2);

这会将所有零都映射到全零,所有1都映射到所有1,以及介于两者之间的所有零。您可以通过一步将位移动到位来提高效率:

redmask = (buf & 0xF800);
rgb888 = (redmask << 8) | ((redmask<<3)&0x070000) | /* green, blue */

同样适用于绿色和蓝色(6位,分别在顶部方法中左移2和右4)。

答案 5 :(得分:3)

一般的解决方案是将数字视为二进制分数 - 因此,6位数63/63与8位数255/255相同。您最初可以使用浮点数学计算,然后计算查找表,如其他海报所示。这也具有比比特抨击解决方案更直观的优点。 :)

答案 6 :(得分:3)

jleedev有错误!!!

unsigned char green = (buf & 0x07c0) >> 5;
unsigned char blue = buf & 0x003f;

好的代码

unsigned char green = (buf & 0x07e0) >> 5;
unsigned char blue = buf & 0x001f;

干杯, 安迪

答案 7 :(得分:0)

我使用了以下内容并取得了良好的效果。原来我的Logitek凸轮是16bit RGB555并使用以下转换为24bit RGB888允许我使用较小的动物ijg保存为jpeg:感谢在stackoverflow上找到的提示。

// Convert a 16 bit inbuf array to a 24 bit outbuf array
BOOL JpegFile::ByteConvert(BYTE* inbuf, BYTE* outbuf, UINT width, UINT height)
{     UINT row_cnt, pix_cnt;     
      ULONG off1 = 0, off2 = 0;
      BYTE  tbi1, tbi2, R5, G5, B5, R8, G8, B8;

      if (inbuf==NULL)
          return FALSE;

      for (row_cnt = 0; row_cnt <= height; row_cnt++) 
      {     off1 = row_cnt * width * 2;
            off2 = row_cnt * width * 3;
            for(pix_cnt=0; pix_cnt < width; pix_cnt++)
            {    tbi1 = inbuf[off1 + (pix_cnt * 2)];
                 tbi2 = inbuf[off1 + (pix_cnt * 2) + 1];
                 B5 = tbi1 & 0x1F;
                 G5 = (((tbi1 & 0xE0) >> 5) | ((tbi2 & 0x03) << 3)) & 0x1F;
                 R5 = (tbi2 >> 2) & 0x1F;
                 R8 = ( R5 * 527 + 23 ) >> 6;
                 G8 = ( G5 * 527 + 23 ) >> 6;
                 B8 = ( B5 * 527 + 23 ) >> 6;
                 outbuf[off2 + (pix_cnt * 3)] = R8;
                 outbuf[off2 + (pix_cnt * 3) + 1] = G8;
                 outbuf[off2 + (pix_cnt * 3) + 2] = B8;
            }
       }
       return TRUE;
}        

答案 8 :(得分:0)

代码如下:

namespace convert565888
{
    inline uvec4_t const _c0{ { { 527u, 259u, 527u,  1u } } };
    inline uvec4_t const _c1{ { {  23u,  33u,  23u,  0u } } };
} // end ns

uvec4_v const __vectorcall rgb565_to_888(uvec4_v const rgba) { 
    return(uvec4_v(_mm_srli_epi32(_mm_add_epi32(_mm_mullo_epi32(rgba.v,
           uvec4_v(convert565888::_c0).v), uvec4_v(convert565888::_c1).v), 6)));
}

对于 rgb 888 到 565 的转换:

namespace convert888565
{
    inline uvec4_t const _c0{ { {  249u, 509u,  249u,  1u } } };
    inline uvec4_t const _c1{ { { 1014u, 253u, 1014u,  0u } } };
} // end ns

uvec4_v const __vectorcall rgb888_to_565(uvec4_v const rgba) {
    return(uvec4_v(_mm_srli_epi32(_mm_add_epi32(_mm_mullo_epi32(rgba.v,
           uvec4_v(convert888565::_c0).v), uvec4_v(convert888565::_c1).v), 11)));
}

解释所有这些数字的来源,特别是我如何计算绿色的最佳乘数和偏差:

Desmos 图 - https://www.desmos.com/calculator/3grykboay1

该图不是最大的,但它显示了实际值与错误的关系——使用交互式滑块来查看不同的值如何影响输出。此图也适用于计算红色和蓝色值。通常绿色移位 10 位,红色和蓝色移位 11 位。 为了使其与内在 _mm_srli_epi32 / _mm_srl_epi32 一起使用,需要将所有组件移动相同的量。因此,在此版本中,所有内容都移动了 11 位 (rgb888_to_565),但是,缩放绿色分量以补偿这种变化。幸运的是,它可以完美扩展!

相关问题