8

我看到这个 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;
}

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

4

1 回答 1

9

(a ^ b) & 0x00010101a + b如果没有进位来自右侧,则通道的最低有效位将是什么。

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

另一种看待这个的方式,不是它的方式,而是一种可以帮助你理解它的方式,是有效地改变了输入,以便它们的总和对于所有通道都是偶数。然后进位很好地进入最低有效位(为零,因为偶数),而不会干扰任何东西。当然,它实际上做的是另一种方式,首先它只是对它们求和,然后才确保所有通道的总和都是偶数。但顺序无关紧要。

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

  1. 一个通道的 lsb 为 0,并且没有来自下一个通道的进位。
  2. 一个通道的 lsb 为 0,并且有来自一个通道的进位。
  3. 一个通道的 lsb 为 1,并且没有来自下一个通道的进位。
  4. 一个通道的 lsb 为 1,并且有来自一个通道的进位。

前两种情况是微不足道的。移位将携带位放回它所属的通道中,它是 0 还是 1 都无关紧要。

案例 3 更有趣。如果 lsb 为 1,则意味着移位将把该位移到下一个通道的最高有效位。那很糟。必须以某种方式取消设置该位-但您不能只是将其掩盖,因为也许您是第 4 种情况。

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

如您所见,在案例 3 和案例 4 中,解决方法都是从 lsb 中减去 1,而这些也是 lsb 真正想要为 1 的情况(尽管可能不再是 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,这样最高有效位的进位就不会丢失。掩码确保位不会移动到右侧的通道,同时确保进位不会传播到其通道之外。

于 2013-12-19T12:48:09.387 回答