编辑:我不一定需要这个问题的解决方案 - 而是我想了解为什么正在发生。我不明白为什么我应该得到以下奇怪的结果......
虽然这个问题是针对我在HTML5画布应用程序中遇到的问题,但我认为问题不太具体。
我有一个HTML5画布应用程序,允许您在屏幕上标记图像。这些图像是32位PNG,所以我正在使用透明度。如果我在同一位置多次(大约100)标记高度透明的图像,我最终会得到一个绝对可怕的结果:
我用作图章的图像的颜色为RGB(167, 22, 22)
,我加盖的背景为RGB(255, 255, 255)
。这是源图像,如果有人感兴趣的话:
正如您所知,图像的alpha级别极低。可能约为2/255 to 5/255
左右。我想要发生的事情是,如果你反复将图像标记重复应用到画布上,你将得到颜色为RGBA(167, 22, 22, 255)
的像素。不幸的是,我得到了一个混合颜色包,包括一些非常奇怪的灰色区域,值为RGB(155, 155, 155)
。
我刚加载了Excel并插入了源代码alpha混合(Wikipedia reference)的等式,经过足够的迭代后我似乎正在收敛到RGB(167, 22, 22)
。我可能遗漏了一些关于alpha混合操作的基本知识以及HTML5 canvas如何实现源代码合成......任何人都可以帮助理顺我吗?
谢谢!
注意:this question与我的问题相似,但我不太明白为什么我会收到我在此处发布的结果。
答案 0 :(得分:5)
画布数学内部的精确度和舍入规则大多是未定义的,因此很难确切地说出这里发生了什么。我们真正知道的是像素是无符号字节,alpha是预乘的。
但是,我们可以通过使用getImageData
在绘制图章时检查像素来获取一些信息,如下所示:
var px = 75;
var py = 100;
var stamp = new Image;
stamp.onload = function() {
for (var i = 0; i < 100; ++i) {
imageData = context.getImageData(px, py, 1, 1);
console.log(Array.prototype.slice.call(imageData.data, 0, 4));
context.drawImage(stamp, 0, 0);
}
};
stamp.src = 'stamp.png';
px = 75
处的样本,py = 100
正好位于灰色斑点的中间。在白色画布上绘制一次印章后,日志显示为:
[254, 254, 254, 255]
在px = 120
,py = 150
,样本位于红色区域的中间。在绘制一次印章后,日志显示为:
[254, 253, 253, 255]
因此,看起来画布被灰色像素修改为(-1,-1,-1),红色像素修改为(-1,-2,-2)。
使用RMagick对图章图像中的这些相同像素进行采样,得到:
[167, 22, 22, 1] // x = 75, y = 100
[167, 22, 22, 2] // x = 120, y = 150
使用标准的alpha混合方程式,您可以测试每个颜色值:
function blend(dst, src) {
var a = src[3] / 255.0
return [
(1.0 - a) * dst[0] + a * src[0],
(1.0 - a) * dst[1] + a * src[1],
(1.0 - a) * dst[2] + a * src[2]
];
}
console.log(blend([255, 255, 255], [167, 22, 22, 1]));
// output: [254.6549..., 254.0862..., 254.0862...]
console.log(blend([255, 255, 255], [167, 22, 22, 2]));
// output: [254.3098..., 253.1725..., 253.1725...]
从这里,我们可以猜测画布混合代码实际上是对结果进行布局,而不是对它们进行舍入。这会给你一个[254, 254, 254]
和[254, 253, 253]
的结果,就像我们从画布上看到的那样。它们可能根本不会进行任何舍入,并且当被转换为无符号字节时它会被隐式覆盖。
这就是为什么其他帖子建议将图像数据存储为浮点数组,自己做数学,然后用结果更新画布。你可以通过这种方式获得更高的精度,并且可以控制诸如舍入之类的事情。
编辑:事实上,即使结果被覆盖,此blend()
函数也不完全正确,因为120, 150
的画布像素值稳定在{{ 1}},此函数稳定在[127, 0, 0]
。同样,当我仅将图像绘制到透明画布中时,[167, 22, 22]
处像素上的getImageData
为120, 150
。什么?!
事实证明,这是由预乘引起的,它似乎应用于加载的Image元素。有关示例,请参阅this jsFiddle。
预乘像素存储为:
[127, 0, 0, 2]
他们将在以后解压缩:
// r, g, b are 0 to 255
// a is 0 to 1
// dst is all 0 to 255
dst.r = Math.floor(r * a);
dst.g = Math.floor(g * a);
dst.b = Math.floor(b * a);
dst.a = a * 255;
针对inv = 1.0 / (a / 255);
r = Math.floor(dst.r * inv);
g = Math.floor(dst.g * inv);
b = Math.floor(dst.b * inv);
运行此打包/解包,显示:
[167, 22, 22, 2]