【问题标题】:Combining two 16 bits RGB colors with alpha blending将两个 16 位 RGB 颜色与 Alpha 混合相结合
【发布时间】:2013-09-21 21:15:01
【问题描述】:

我有一个 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,但是太慢了,一定有更好的方法!

【问题讨论】:

标签: c colors alphablending


【解决方案1】:

我的(未经测试的)解决方案:我将前景色和背景色拆分为(红色 + 蓝色)和(绿色)分量,并将它们与 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
*/

【讨论】:

  • 谢谢你,很抱歉迟到了,我已经测试了你的函数,虽然它给出了不同的结果(我猜是不同的舍入误差),但与 Dietrich Epp 的结果相比,它也快了两倍多!我会做更多的测试,看看哪个颜色最准确。但我想我还是会用你的。所以我将你的答案设置为最佳,但两者都是很好的答案;)
  • 谢谢。我的版本不太精确,因为它首先从每种颜色中切掉 2 位 :) 顺便说一句,我在这里“发明”了这个 6 位解决方案:openprocessing.org/sketch/36882 这是一个模糊功能,如果你碰巧需要它一次。
  • 看起来变量a的意思是alpha,不是吗?
【解决方案2】:

正确的公式是这样的:

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;

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

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

【讨论】:

  • 非常感谢,我进行了测试,这是应该的,(至少对于具有一半不透明度的黑底白字,它会给出灰色;)。我猜它没有给出 0x8410 而是 0x7BEF 因为与 32 位颜色相比精度损失?)再次感谢,我会研究代码并尝试理解它!
  • 不,0x7BEF 完全正确,即使您使用 32 位也是如此。在二进制中它是 01111 011111 01111,如您所见,每个分量正好等于满量程的 50%,向下舍入。
  • 如果您将 alpha 设置为 132 而不是 127,那么您将得到 10000 100000 10000,比 50% 灰色略亮。您想绕哪个方向有点不确定,但它很少会产生任何视觉差异。
  • 由于缺少伽马校正,结果已经“错误”;花费更多的指令四舍五入不会使其更准确。
  • 这很奇怪,因为我尝试在 Paint.NET 中用 alpha 255 制作黑色背景 0x000000,用 alpha 128 制作白色前景 0xFFFFFF,然后使用插件导出到 rgb565,它显示 0x8410(嗯, 0x1084 由于某些原因...)。然后我尝试导出到普通的位图文件并使用我在网上找到的另一个转换器,这个也显示 0x8410。不是说您的代码不正确,但我想了解为什么存在(微小)差异,所以如果您知道,请解释;)
【解决方案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 帧缓冲库的拉取请求中找到了这个解决方案。这是一个带有 cmets 的扩展版本来解释数学:

// 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
}

【讨论】:

  • 知道为什么 alpha = ( alpha + 4 ) >> 3; uint_8 上的 +4 和 255 是非常有效的可能性?
  • +4 是更准确的范围转换所必需的。是的,255 上的 +4(移位前)是绝对可能的,但对我(gcc)来说它工作正常,所以中间结果可以存储为 16 位或 32 位整数?
【解决方案4】:

这个是基于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);
}

【讨论】:

    猜你喜欢
    • 2012-01-14
    • 1970-01-01
    • 2018-12-12
    • 1970-01-01
    • 1970-01-01
    • 2013-06-05
    • 1970-01-01
    • 2019-03-30
    • 1970-01-01
    相关资源
    最近更新 更多