这种颜色混合技巧如何工作?

时间:2013-12-19 12:17:51

标签: java optimization language-agnostic bit-manipulation color-blending

我看到这个Java代码能够非常有效地在两种RGB888颜色之间实现完美的50%混合:

public static int blendRGB(int a, int b) {
    return (a + b - ((a ^ b) & 0x00010101)) >> 1;
}

这显然相当于单独提取和平均通道。像这样:

public static int blendRGB_(int a, int b) {
    int aR = a >> 16;
    int bR = b >> 16;
    int aG = (a >> 8) & 0xFF;
    int bG = (b >> 8) & 0xFF;
    int aB = a & 0xFF;
    int bB = b & 0xFF;
    int cR = (aR + bR) >> 1;
    int cG = (aG + bG) >> 1;
    int cB = (aB + bB) >> 1;
    return (cR << 16) | (cG << 8) | cB;
}

但第一种方式更有效率。我的问题是:这个神奇的工作原理如何?我还能用它做什么?还有更多类似的技巧吗?

1 个答案:

答案 0 :(得分:9)

(a ^ b) & 0x00010101a + b中频道的最低有效位,如果没有来自右侧的进位。

从总和中减去它可以保证转移到下一个通道的最高有效位的位只是来自该通道的进位,不受该通道的影响。当然,这也意味着该频道不再受下一频道的进位影响。

另一种看待这种情况的方式,不是它的方式,而是一种可以帮助你理解它的方式,是有效地改变输入,使得它们的总和甚至适用于所有通道。然后,进位很好地进入最低有效位(零,因为均匀),而不会打扰任何东西。当然它实际上做的是反过来,首先它只是对它们求和,然后才确保所有通道的总和均匀。但顺序并不重要。

更具体地说,有4种情况(在应用下一个频道的进位之前):

  1. 通道的lsb为0,下一个通道没有进位。
  2. 频道的lsb为0, 来自下一频道的进位。
  3. 频道的lsb是1,下一个频道没有进位。
  4. 频道的lsb为1,而 来自下一频道的进位。
  5. 前两个案件是微不足道的。移位将携带的位置回到它所属的通道中,它甚至不是0或1。

    案例3更有趣。如果lsb为1,则意味着移位会将该位移位到下一个通道的最高位。那很糟。这一点必须以某种方式解开 - 但你不能只是掩盖它,因为也许你是在案件4。

    案例4是最有趣的。如果lsb为1并且该位有进位,则它会翻转到0并且进位传播。这不能通过屏蔽来解除,但可以通过反转过程来完成,即从lsb中减去1(将其放回1并撤消传播进位所造成的任何损害)。

    正如你所看到的,在案例3和案例4中,治愈从lsb中减去1,那些也是lsb真正想要为1的情况(尽管可能不再是,由于来自下一个频道的进位),在情况1和2中,你没有任何东西(换句话说,减去0)。这恰好相当于减去“如果没有来自右边的遗嘱,a + b中的lsb会是什么”。

    此外,蓝色通道只能落入情况1或3(没有下一个可以携带的通道),并且移位只会丢弃该位而不是将其放入下一个通道(因为没有)。所以,你也可以写(注意面具失去了最不重要的1)

    public static int blendRGB(int a, int b) {
        return (a + b - ((a ^ b) & 0x00010100)) >> 1;
    }
    

    但是,确实没有任何区别。

    要使其适用于ARGB8888,您可以切换到旧的“SWAR平均值”:

    // channel-by-channel average, no alpha blending
    public static int blendARGB(int a, int b) {
        return (a & b) + (((a ^ b) & 0xFEFEFEFE) >>> 1);
    }
    

    这是定义加法的递归方式的变体:x + y = (x ^ y) + ((x & y) << 1)计算没有进位的和,然后分别添加进位。基本情况是其中一个操作数为零。

    两个半部分有效地向右移动1,这样就不会丢失最重要部分的执行。掩码确保位不会移动到右侧的通道,同时确保进位不会传播到其通道之外。