将两个16位RGB颜色与alpha混合相结合

时间:2013-09-21 21:15:01

标签: c colors alphablending

我有一个可以绘制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,但这太慢了,必须有更好的方法!

4 个答案:

答案 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);
}