【问题标题】:Poor results with source-over alpha blending (HTML5 canvas)source-over alpha 混合效果不佳(HTML5 画布)
【发布时间】:2011-03-19 07:01:58
【问题描述】:

编辑:我不一定需要解决这个问题——我想了解为什么它会发生。我不明白为什么我应该得到下面的奇怪结果......

虽然这个问题是针对我在使用 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 并插入了源-over alpha 混合方程 (Wikipedia reference),经过足够多的迭代后,我似乎收敛到了 RGB(167, 22, 22)。我可能遗漏了一些关于 alpha 混合操作以及 HTML5 画布如何实现 source-over 合成的基本知识......有人可以帮我理顺吗?

谢谢!

注意:this question 与我的问题类似,但我不太明白为什么会得到我在此处发布的结果。

【问题讨论】:

  • 您是否尝试过您所说的与您的问题接近的问题的公认答案?看来该解决方案也可能对您有用。
  • @James Black 我没有。我真的不明白为什么缺乏精确度,这反过来又让我看不到如何解决问题。如果我使用 source-over alpha 混合方程,最终我会得到我想要的颜色——但这不是 HTML5 画布发生的事情......?
  • 你能发布另一个图片链接吗? imgur 不再提供它
  • @sinelaw 当然,我刚刚为你重新上传了。
  • 谢谢。顺便说一句,你最后做了什么?

标签: html graphics canvas pixel blend


【解决方案1】:

画布数学内部的精度和舍入规则大多是未定义的,因此很难准确地说出这里发生了什么。我们真正知道的是像素是无符号字节,而 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 = 75py = 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 的画布像素值稳定在[127, 0, 0],而这个函数稳定在[167, 22, 22]。同样,当我在透明画布中仅绘制一次图像时,120, 150 像素上的getImageData[127, 0, 0, 2]。什么?!

事实证明,这是由预乘引起的,它似乎应用于加载的 Image 元素。示例见this jsFiddle

预乘像素存储为:

// 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] 运行此打包/解包显示:

a = 2 / 255;                                // 0.00784
inv = 1.0 / (2 / 255);                      // 127.5
r = Math.floor(Math.floor(167 * a) * inv);  // 127
g = Math.floor(Math.floor(22 * a) * inv);   // 0
b = Math.floor(Math.floor(22 * a) * inv);   // 0

【讨论】:

  • 非常非常感谢您的详尽回复。我明白你在这里说什么,现在看看另一个问题指的是什么,失去了精确度。不幸的是,我认为这不是我的问题。如果我们继续按照您在此处概述的过程,我们最终不会为 (120, 150) 获得 [167, 22, 22, 255] 吗?目标图像从 [255, 255, 255, 0] 开始,但不知何故,由于浏览器使用的 alpha 混合方程,我最终得到了非纯红色和奇怪的灰色阴影......
  • @Xenethyl 好点,我没注意到。我做了一些更多的挖掘工作,看起来 dst 和 src 在添加之前都被铺平了。我已经用一个例子更新了答案。
  • @Xenethyl Gah!在尝试用正常的颜色填充在 jsFiddle 中重现这个之后,我看到了更多的怪异。我想我已经把这个范围缩小到了预乘......
  • 再次感谢您的积极响应。实际上,当我试图研究这个问题时,我遇到了预乘的 alpha 值,但当时并没有想到它。我还没有时间尝试使用邮票图像来恢复预乘,但我认为根据你得出的结果,它确实是罪魁祸首。我真的很感谢你在这里的时间——这会让我发疯很长一段时间。 :)
猜你喜欢
  • 1970-01-01
  • 2016-10-11
  • 1970-01-01
  • 1970-01-01
  • 2011-10-27
  • 1970-01-01
  • 2023-03-09
  • 1970-01-01
  • 2014-11-29
相关资源
最近更新 更多