使用source-over alpha混合效果不佳(HTML5画布)

时间:2011-03-19 07:01:59

标签: html5 graphics canvas pixel blend

编辑:我不一定需要这个问题的解决方案 - 而是我想了解为什么正在发生。我不明白为什么我应该得到以下奇怪的结果......

虽然这个问题是针对我在HTML5画布应用程序中遇到的问题,但我认为问题不太具体。

我有一个HTML5画布应用程序,允许您在屏幕上标记图像。这些图像是32位PNG,所以我正在使用透明度。如果我在同一位置多次(大约100)标记高度透明的图像,我最终会得到一个绝对可怕的结果:

http://i.imgur.com/qYY5n.png

我用作图章的图像的颜色为RGB(167, 22, 22),我加盖的背景为RGB(255, 255, 255)。这是源图像,如果有人感兴趣的话:

http://i.stack.imgur.com/Wm9Dx.png

正如您所知,图像的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与我的问题相似,但我不太明白为什么我会收到我在此处发布的结果。

1 个答案:

答案 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 = 120py = 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]处像素上的getImageData120, 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]
相关问题