我有一个可以绘制16位颜色的TFT显示器,格式化RGB 565.我想为我显示的内容添加一些透明度。
假设我有一个黑色背景(0x0000),我想画一个半透明的白色前景(0xFFFF)(不透明度由另一个字节控制),所以它会显示为灰色。如何以相同的RGB 565格式计算16位灰色,这样我就可以将它发送到我的TFT并且它会正确显示(可能有一些损失,但我不在乎)?
我需要一个如下功能:
unsigned short calcColor_RGB565(unsigned short background_RGB565, unsigned short foreground_RGB565, unsigned char opacity)
calcColor_RGB565(0x0000, 0xFFFF, 128)
会产生0x8410(或者0x1084,这并不重要,因为我向TFT发送了两个单独的字节,所以如果需要我只会反转顺序)
感谢任何可以帮助我的人,我已经尝试了一些东西,但我无法得到正确的结果,甚至没有关闭:/。
类似于C的伪代码,但我更喜欢解释如何做到这一点。
编辑:忘了说,我希望它尽可能快,因为它是一个旧的微处理器,所以如果单独计算2个字节更快(所以我也不必在以后将它们分开)那时我对这种优化非常感兴趣。
编辑9月27日:5天后,仍未解决。我可以从rgb565转换为rgb8888,进行alpha混合然后转换回rgb565,但这太慢了,必须有更好的方法!
答案 0 :(得分:6)
我的(未经测试的)解决方案:我将前景色和背景色分为(红色+蓝色)和(绿色)分量,并将它们与6位alpha值相乘。请享用! (只有它有效:)
// rrrrrggggggbbbbb
#define MASK_RB 63519 // 0b1111100000011111
#define MASK_G 2016 // 0b0000011111100000
#define MASK_MUL_RB 4065216 // 0b1111100000011111000000
#define MASK_MUL_G 129024 // 0b0000011111100000000000
#define MAX_ALPHA 64 // 6bits+1 with rounding
uint16 alphablend( uint16 fg, uint16 bg, uint8 alpha ){
// alpha for foreground multiplication
// convert from 8bit to (6bit+1) with rounding
// will be in [0..64] inclusive
alpha = ( alpha + 2 ) >> 2;
// "beta" for background multiplication; (6bit+1);
// will be in [0..64] inclusive
uint8 beta = MAX_ALPHA - alpha;
// so (0..64)*alpha + (0..64)*beta always in 0..64
return (uint16)((
( ( alpha * (uint32)( fg & MASK_RB )
+ beta * (uint32)( bg & MASK_RB )
) & MASK_MUL_RB )
|
( ( alpha * ( fg & MASK_G )
+ beta * ( bg & MASK_G )
) & MASK_MUL_G )
) >> 6 );
}
/*
result masks of multiplications
uppercase: usable bits of multiplications
RRRRRrrrrrrBBBBBbbbbbb // 5-5 bits of red+blue
1111100000011111 // from MASK_RB * 1
1111100000011111000000 // to MASK_RB * MAX_ALPHA // 22 bits!
-----GGGGGGgggggg----- // 6 bits of green
0000011111100000 // from MASK_G * 1
0000011111100000000000 // to MASK_G * MAX_ALPHA
*/
答案 1 :(得分:5)
正确的公式是这样的:
unsigned short blend(unsigned short fg, unsigned short bg, unsigned char alpha)
{
// Split foreground into components
unsigned fg_r = fg >> 11;
unsigned fg_g = (fg >> 5) & ((1u << 6) - 1);
unsigned fg_b = fg & ((1u << 5) - 1);
// Split background into components
unsigned bg_r = bg >> 11;
unsigned bg_g = (bg >> 5) & ((1u << 6) - 1);
unsigned bg_b = bg & ((1u << 5) - 1);
// Alpha blend components
unsigned out_r = (fg_r * alpha + bg_r * (255 - alpha)) / 255;
unsigned out_g = (fg_g * alpha + bg_g * (255 - alpha)) / 255;
unsigned out_b = (fg_b * alpha + bg_b * (255 - alpha)) / 255;
// Pack result
return (unsigned short) ((out_r << 11) | (out_g << 5) | out_b);
}
您可以使用快捷键除以255.编译器应该能够提供一些强度降低,但您可以通过使用以下公式来做得更好:
// Alpha blend components
unsigned out_r = fg_r * a + bg_r * (255 - alpha);
unsigned out_g = fg_g * a + bg_g * (255 - alpha);
unsigned out_b = fg_b * a + bg_b * (255 - alpha);
out_r = (out_r + 1 + (out_r >> 8)) >> 8;
out_g = (out_g + 1 + (out_g >> 8)) >> 8;
out_b = (out_b + 1 + (out_b >> 8)) >> 8;
注意函数中的大量变量......这没关系。如果您尝试通过重写方程式来“优化”代码,以便创建更少的临时变量,那么您只需要完成编译器已经为您完成的工作。除非你有一个非常糟糕的编译器。
如果这还不够快,可以选择一些方法。但是,选择正确的选项取决于分析的结果,代码的使用方式以及目标体系结构。
答案 2 :(得分:1)
我发现了一种比biziclop近似快约25%的替代方法。这也是一个近似值,因为它将alpha级别从0-255降低到0-31(alpha级别为32级),但据我所知,它不会截断色位。
在我的TFT显示屏上,结果看起来与biziclop算法的结果相同,但我还没有检查单个像素值,看看有什么区别(如果有的话)。
请注意,虽然fg和bg参数是32位无符号,但实际上您必须传递16位RGB565颜色。算法在函数内需要32位宽度。
/**
* Fast RGB565 pixel blending
* @param fg The foreground color in uint16_t RGB565 format
* @param bg The background color in uint16_t RGB565 format
* @param alpha The alpha in range 0-255
**/
color alphaBlendRGB565( uint32_t fg, uint32_t bg, uint8_t alpha ){
alpha = ( alpha + 4 ) >> 3;
bg = (bg | (bg << 16)) & 0b00000111111000001111100000011111;
fg = (fg | (fg << 16)) & 0b00000111111000001111100000011111;
uint32_t result = ((((fg - bg) * alpha) >> 5) + bg) & 0b00000111111000001111100000011111;
return (uint16_t)((result >> 16) | result);
}
我在Chris Chua向Adafruit Arduino帧缓冲库的拉取请求中找到了这个解决方案。这是一个扩展版本,附带注释来解释数学:
// Fast RGB565 pixel blending
// Found in a pull request for the Adafruit framebuffer library. Clever!
// https://github.com/tricorderproject/arducordermini/pull/1/files#diff-d22a481ade4dbb4e41acc4d7c77f683d
color alphaBlendRGB565( uint32_t fg, uint32_t bg, uint8_t alpha ){
// Alpha converted from [0..255] to [0..31]
alpha = ( alpha + 4 ) >> 3;
// Converts 0000000000000000rrrrrggggggbbbbb
// into 00000gggggg00000rrrrr000000bbbbb
// with mask 00000111111000001111100000011111
// This is useful because it makes space for a parallel fixed-point multiply
bg = (bg | (bg << 16)) & 0b00000111111000001111100000011111;
fg = (fg | (fg << 16)) & 0b00000111111000001111100000011111;
// This implements the linear interpolation formula: result = bg * (1.0 - alpha) + fg * alpha
// This can be factorized into: result = bg + (fg - bg) * alpha
// alpha is in Q1.5 format, so 0.0 is represented by 0, and 1.0 is represented by 32
uint32_t result = (fg - bg) * alpha; // parallel fixed-point multiply of all components
result >>= 5;
result += bg;
result &= 0b00000111111000001111100000011111; // mask out fractional parts
return (color)((result >> 16) | result); // contract result
}
答案 3 :(得分:0)
这个基于http://www-personal.umich.edu/~bazald/l/api/_s_d_l___r_l_eaccel_8c_source.html
在我的RGB565 16位设备上,它产生了最干净的结果。
COLOUR ALPHA_BLIT16_565(uint32_t fg, uint32_t bg, int8u alpha) {
// Alpha converted from [0..255] to [0..31]
uint32_t ALPHA = alpha >> 3;
fg = (fg | fg << 16) & 0x07e0f81f;
bg = (bg | bg << 16) & 0x07e0f81f;
bg += (fg - bg) * ALPHA >> 5;
bg &= 0x07e0f81f;
return (COLOUR)(bg | bg >> 16);
}