5

我有一个 TFT 显示器,它可以绘制 16 位颜色,格式为 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

4 回答 4

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
*/
于 2013-09-28T14:31:53.507 回答
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;

注意函数中的大量变量......这没关系。如果您尝试通过重写方程式来“优化”代码,从而创建更少的临时变量,那么您所做的只是编译器已经为您完成的工作。除非你有一个非常糟糕的编译器。

如果这还不够快,有几个选项可供选择。但是,选择正确的选项取决于分析的结果、代码的使用方式以及目标架构。

于 2013-09-27T21:20:28.237 回答
3

我找到了一种替代方法,它比 biziclop 的近似值快约 25%。这也是一个近似值,因为它将 alpha 的级别从 0-255 降低到 0-31(32 个 alpha 级别),但据我所知,它不会截断颜色位。

在我的 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
}
于 2018-04-25T00:26:31.237 回答
1

这个是基于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);
}
于 2020-04-01T16:40:06.383 回答