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

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

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

(a ^ b) & 0x00010101是如果没有来自右边的进位,则a + 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和2中,你没有任何东西(换句话说,减去0)。 这恰好对应于减去“如果没有来自右边的那个,那么lsb在a + b是什么”。

此外,蓝色通道只能落入情况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,这样就不会丢失最重要位的执行。 掩码确保位不会移动到右侧的通道,同时确保进位不会传播出其通道。