我需要将一些旧的与图形相关的C/C++代码移植到Java和JavaScript上,我发现其中有这样一段:
b = (b+1 + (b >> 8)) >> 8; // very fast
其中b
代表short int
类型的蓝色,同样的代码也用于r
和b
(红色和蓝色)。这个注释并没有什么用。
除了明显的移位和加法操作,我无法理解它的作用。我可以在不理解的情况下进行移植,只是出于好奇想要问一下。
我需要将一些旧的与图形相关的C/C++代码移植到Java和JavaScript上,我发现其中有这样一段:
b = (b+1 + (b >> 8)) >> 8; // very fast
其中b
代表short int
类型的蓝色,同样的代码也用于r
和b
(红色和蓝色)。这个注释并没有什么用。
除了明显的移位和加法操作,我无法理解它的作用。我可以在不理解的情况下进行移植,只是出于好奇想要问一下。
y = ( x + 1 + (x>>8) ) >> 8 // very fast
这是一种关于除以255的固定点近似方法。从概念上讲,它对基于像素值的计算进行规范化非常有用,使得255(通常是最大像素值)恰好等于1。
它被描述为非常快速,因为完全通用的整数除法在许多CPU上是相对缓慢的操作 - 尽管如果编译器能够推断出输入约束条件,它可能会为您进行类似的优化。
其原理是基于这样一个想法:257/(256*256)
是1/255
的一个非常接近的近似值,且x*257/256
可以表示为x+(x>>8)
。+1是四舍五入支持,允许该公式在所有x
值[0..65534]的情况下准确匹配整数除法x/255
。
对内部部分进行一些代数运算可能会使事情更加清晰...
x*257/256
= (x*256+x)/256
= x + x/256
= x + (x>>8)
这里有更多讨论:如何快速进行alpha混合?和通过乘法进行除法计算
顺便说一句,如果你想要四舍五入并且你的CPU可以进行快速乘法运算,以下方法对于所有uint16_t被除数值都是准确的--实际上是[0..(2^16)+126]。
y = ((x+128)*257)>>16 // divide by 255 with round-to-nearest for x in [0..65662]
当您必须为每个像素组合许多alpha值时,您需要使用比257/256更精确的公式。举个例子,当进行图像缩小操作时,您需要将每个源像素的4个alpha值合并到目标像素中,并将所有源像素贡献到目标像素。
我发布了一个无限精度的二进制操作版本的 /255,但没有理由被拒绝。所以我会补充说明,我实现了用于生活的 alpha 混合硬件,我编写实时图形代码和游戏引擎,我在 MICRO 等会议上发表过关于这个主题的文章,所以我真的知道自己在说什么。而且对人们来说,了解比 1/255 更精确的公式可能是有用的,或者至少是娱乐的:
版本1:x = (x + (x>>8))>>8 - 不添加常量,不能满足(x*255)/255=x,但在大多数情况下看起来很好。 版本2:x = (x + (x>>8) + 1)>>8 - 对于整数将满足(x*255)/255=x,但不会针对所有 alpha 命中正确的整数值
版本3:(简单整数舍入):(x + (x>>8) + 128)>>8 - 不会对所有alpha命中正确的整数值,但在成本相同的情况下平均会更接近版本2。
版本4:无限准确度版本,用于任意数量的复合 alpha 的任意精度(用于图像调整大小、旋转等):
[(x + (x>>8))>>8] + [((x&255) + (x>>8))>>8]
为什么版本4无限准确? 因为1/255 = 1/256 + 1/65536 + 1/256^3 + 1/256^4 +…
以上最简表达式(版本1)不处理舍入,但也不处理从无限数量的相同和列中发生的进位。上面添加的新术语确定了从这无限数量的基数256位数中溢出的位(0或1)。通过添加它,您得到了与添加所有无限加数相同的结果。此时,您可以通过在任何准确度点上添加半个比特来四舍五入。
OP可能不需要,但人们应该知道您根本不需要近似。上面的公式实际上比双精度浮点精度更高。
关于速度:在硬件方面,此方法比单个(全宽)加法更快。在软件方面,您必须考虑吞吐量与延迟之间的关系。在延迟方面,它可能仍然比窄乘法快(绝对比全宽乘法快),但在OP上下文中,您可以一次展开许多像素,并且由于现代乘法单元是流水线处理的,所以仍然可以正常工作。翻译为Java时,您可能没有窄乘法,因此这仍然可能更快,但需要检查。看起来这个代码用于检查蓝色(或红色或绿色)是否完全使用。当b
等于255
时,它的值为1
,对于所有更低的值则为0
。
b
在 1 到 255 之间,那么这是有意义的。但是 (b >> 8)
总是为零... 写成 (b+1) >> 8
就足够了。 - ArnonZ计算b+1 + b/256
的值,并将结果除以256
。
这样,使用位移操作,编译器会使用CPU级别的移位指令来进行翻译,而不是使用FPU或库函数的除法操作。
>> 8
就是/ 256
。他们所问的是,为什么要执行这个表达式,也就是从语义上讲,它代表什么意思。 - lurkerboolean isBFullyOn = false;
if (b == 0xff) {
isBFullyOn = true;
}
bool isFullyOn = b == 0xFF
仍然会更快(假设没有编译器优化的情况下)。 - Dale Wilsonb = (b + (b >> 8)) >> 8;
的基本意思是 b = b *257/256
。
我认为+1
是对内部 >>8
导致的 -0.5
平均值减少的丑陋黑客。
我会将其写为 b = (b + 128 + ((b +128)>> 8)) >> 8;
。
b*257/256/256
),但看到你的答案让我认识到了这个公式。谢谢。 - Brent BradburnRunning this test code:
public void test() {
Set<Integer> results = new HashSet<Integer>();
// short int ranges between -32767 and 32767
for (int i = -32767; i <= 32767; i++) {
int b = (i + 1 + (i >> 8)) >> 8;
if (!results.contains(b)) {
System.out.println(i + " -> " + b);
results.add(b);
}
}
}
-129
和 128
之间的所有可能值。然而,如果您使用的是 8 位颜色 (0 - 255
),则唯一可能的输出是 0
(对于 0 - 254
) 和 1
(对于 255
),因此很可能它正在尝试执行函数 @kaykay posted。