我已经掌握了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 framebuffer,smart conversion为ddms,dumb conversion为上述算法。请注意,后者稍微更暗,更绿。
(顺便说一句,转换is implemented in ffmpeg,但它只是执行上面列出的哑转换,让LSB全部为零。)
我想我有两个问题:
答案 0 :(得分:21)
您希望将每个从5/6位空间映射到8位空间。
你正在使用的代码是采用天真的方法,即x5 * 256/32 = x8,其中256/32 = 8,乘以8是左移3,但正如你所说,这并不一定会填补新的数字空间“正确”。最大值的5到8是31到255,其中包含解决方案的线索。
x8 = 255/31 * x5
x8 = 255/63 * x6
其中x5
,x6
和x8
分别为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 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自行实现。
首先,我们将研究将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)
,但是,缩放绿色分量以补偿这种变化。幸运的是,它可以完美扩展!