来源过alpha混合(HTML5画布)的结果不佳 [英] Poor results with source-over alpha blending (HTML5 canvas)

查看:199
本文介绍了来源过alpha混合(HTML5画布)的结果不佳的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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



虽然这个问题是针对我有一个HTML5画布应用程序的问题,我认为问题不那么具体。



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





图片的颜色我使用作为一个邮票是 RGB(167,22,22)和我的冲压到的背景是 RGB(255,255 ,255)。这是源图片,如果有人感兴趣:





正如你所知,图像的alpha级别非常低。可能约 2/255到5/255 左右。我会期望发生的是,如果你反复应用图像戳到画布足够的时间,你会得到颜色 RGBA(167,22,22,255 )。不幸的是,我得到一个混合袋的颜色,包括一些非常奇怪的灰色区域的值 RGB(155,155,155)



我刚刚加载了Excel并插入了源代码alpha混合方程(}},并且我经过足够的迭代似乎收敛到 RGB(167,22,22)。我可能错过了一些关于alpha混合操作的基本知识,以及HTML5画布如何实现源码合成...任何人都可以帮助我弄清楚?



谢谢! p>

注意:这个问题与我的问题类似,但我不太明白为什么我会收到我在这里张贴的结果。

解决方案

画布数学内部的精度和舍入规则大多是未定义的,所以很难说明这里发生了什么。我们真正知道的像素是无符号字节,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 code> py = 100 正好在一个灰色的blob中间。在白色画布上绘制一次邮票后,日志显示为:

  [254,254 ,254,255] 

px = 120 py = 150 ,样本位于红色区域的中间。在绘制邮票一次后,日志显示为:

  [254,253,253,255 ] 

因此,看起来画布被修改了(-1,-1,-1)



使用 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] b $ b(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 code> [127,0,0,2] 。什么?!



事实证明,这是由预乘法造成的,似乎应用于加载的图像元素。请参见此jsFiddle 示例。



预乘像素存储为:

  // r,g,b为0到255 b $ b // a是0到1 
// dst是0到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);

运行此包/ unpack对 [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


Edit: I don't necessarily need a solution to this problem--rather I'd like to understand why it's occurring. I don't see why I should be getting the odd results below...

Although this question is directed towards an issue I'm having with an HTML5 canvas application, I think the problem is less specific.

I have an HTML5 canvas app that allows you to stamp images on the screen. These images are 32bit PNG's, so I'm working with transparency. If I stamp a highly transparent image in the same location many times (roughly 100), I end up with an absolutely terrible result:

The color of the image that I'm using as a stamp is RGB(167, 22, 22) and the background that I'm stamping onto is RGB(255, 255, 255). Here's the source image, if anyone's interested:

As you can tell, the image has extremely low alpha levels. Likely about 2/255 to 5/255 or so. What I would expect to happen is that if you repeatedly apply the image stamp to the canvas enough times, you'll get pixels of color RGBA(167, 22, 22, 255). Unfortunately, I'm getting a mixed bag of colors including some very odd regions of gray with a value of RGB(155, 155, 155).

I just loaded up Excel and plugged in the equation for source-over alpha blending (Wikipedia reference) and I seem to be converging to RGB(167, 22, 22) after enough iterations. I'm probably missing something fundamental about alpha blending operations and how the HTML5 canvas implements source-over compositing... can anyone help straighten me out?

Thanks!

Note: this question is similar to my issue, but I don't quite understand why I'm getting the results I've posted here.

解决方案

The precision and rounding rules of canvas math internals are mostly undefined, so it's hard to say exactly what's happening here. All we really know is that the pixels are unsigned bytes, and the alpha is premultiplied.

However, we can get some information by using getImageData to inspect the pixels as the stamp is drawn, like so:

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';

The sample at px = 75, py = 100 is right in the middle of a gray blob. After drawing the stamp once on a white canvas, the log reads:

[254, 254, 254, 255]

At px = 120, py = 150, the sample is in the middle of a red area. After drawing the stamp once, the log reads:

[254, 253, 253, 255]

So, it looks like the canvas was modified by (-1, -1, -1) for the grey pixel, and (-1, -2, -2) for the red pixel.

Sampling these same pixels in the stamp image using RMagick gives:

[167, 22, 22, 1]  // x = 75, y = 100
[167, 22, 22, 2]  // x = 120, y = 150

Working through the math, using the standard alpha blending equation, you can test each of the color values:

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...]

From this, we can guess that the canvas blending code is actually flooring the results, instead of rounding them. This would give you a result of [254, 254, 254] and [254, 253, 253], like we saw from canvas. They're likely not doing any rounding at all, and it's being floored implicitly when cast back to an unsigned byte.

This is why the other post recommends storing the image data as an array of floats, doing the math yourself, and then updating the canvas with the result. You get more precision that way, and can control things like rounding.

Edit: In fact, this blend() function isn't exactly right, even when the results are floored, as the canvas pixel values for 120, 150 stabilize at [127, 0, 0], and this function stabilizes at [167, 22, 22]. Similarly, when I drew the image just once into a transparent canvas, getImageData on the pixel at 120, 150 was [127, 0, 0, 2]. What?!

It turns out that this is caused by premultiplication, which seems to be applied to loaded Image elements. See this jsFiddle for an example.

Premultiplied pixels are stored as:

// 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;

They are unpacked later as:

inv = 1.0 / (a / 255);
r = Math.floor(dst.r * inv);
g = Math.floor(dst.g * inv);
b = Math.floor(dst.b * inv);

Running this pack/unpack against [167, 22, 22, 2] reveals:

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

这篇关于来源过alpha混合(HTML5画布)的结果不佳的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆