使用HTML5画布在所有浏览器中无效的混合结果

Xen*_*hyl 19 javascript browser graphics html5 canvas

摘要

当反复绘制在画布上的任何东西(显然具有低阿尔法值),不管它是的drawImage()fill功能,从而导致颜色是我测试过的所有浏览器显著不准确的.以下是我通过特定混合操作得到的结果示例:

无效的混合结果

问题演示

有关示例和一些代码,请查看我编写的jsFiddle:

http://jsfiddle.net/jMjFh/2/

最顶层的数据是测试的结果,您可以通过编辑itersrgbaJS代码的顶部进行自定义.它采用您指定的任何颜色RGBA all in range [0, 255],并将其标记在完全干净,透明的画布iters上.同时,它保持运行计算标准Porter-Duff source-over-dest混合函数将为同一过程产生的内容(即浏览器应该运行和出现的内容).

底部数据集显示了一个边界情况,我发现[127, 0, 0, 2]在顶部混合[127, 0, 0, 63]会产生不准确的结果.有趣的是,混合[127, 0, 0, 2]在顶部[127, 0, 0, 62]和所有[127, 0, 0, x]颜色之间x <= 62会产生预期的结果.请注意,此边界情况仅在Windows上的Firefox和IE中有效.在我测试过的每个其他操作系统上的每个其他浏览器中,结果都差得多.

此外,如果您在Windows上的Firefox和Windows上的Chrome中运行测试,您会发现混合结果存在显着差异.不幸的是,我们并没有谈论一两个价值 - 更糟糕的是.

背景资料

我知道HTML5画布规范指出,由于舍入错误,执行drawImage()putImageData()然后getImageData()调用可能会显示稍微不同的结果.在这种情况下,基于我碰到迄今为止的一切,我们正在谈论的东西微乎其微喜欢红色通道被122代替121.

作为一些背景资料,前一段时间问过这个问题,@ NathanOstgard做了一些优秀的研究,得出的结论是alpha预乘是罪魁祸首.虽然这肯定可以在这里起作用,但我认为真正的潜在问题有点大.

问题

有没有人知道我怎么可能最终得到我看到的(非常不准确的)颜色值?此外,更重要的是,是否有人有任何想法如何规避问题并在所有浏览器中产生一致的结果?由于性能原因,手动混合像素不是一种选择.

谢谢!


编辑:我刚刚添加了一系列排序,它们输出每个中间颜色值和过程中该阶段的预期值.


编辑:在研究了一下之后,我想我已经取得了一些进展.

在Win7上推理Chrome14

在Win7上的Chrome14中,混合操作后产生的颜色为灰色,这与预期和所需的微红结果相差很多.这让我相信Chrome 没有圆角和精度的问题,因为差异太大了.相反,Chrome似乎将所有像素值存储为预乘alpha.

通过在画布上绘制单一颜色可以证明这个想法.在Chrome中,如果您将颜色[127, 0, 0, 2]一次应用到画布上,则会[127, 0, 0, 2]在您阅读时获得.但是,如果您将[127, 0, 0, 2]两次画布应用于画布,则Chrome会[85, 0, 0, 3]在您检查结果颜色时为您提供.这实际上有一定道理,当你考虑到的预乘相当于[127, 0, 0, 2][1, 0, 0, 2].

显然,当Chrome14 Win7上执行混合操作,它引用的预乘颜色值[1, 0, 0, 2]dest部件和非预乘颜色值[127, 0, 0, 2]source部件.当这两种颜色混合在一起时,我们最终会[85, 0, 0, 3]按预期使用Porter-Duff source-over-dest方法.

因此,看起来Win7上的Chrome14在使用它们时不一致地引用像素值.信息在内部以预乘状态存储,以非预乘形式呈现给您,并使用两种形式的值进行操作.

我认为可以通过做一些额外的调用getImageData()和/或来避免这种情况putImageData(),但性能影响看起来并不那么好.此外,这似乎与Win7上的Firefox7所展示的问题不同,因此在这方面需要做更多的研究.


编辑:以下是一种似乎可行的方法.

对解决方案的思考

我还没有回过头来处理这个问题,但我最近提出的一种WebGL方法是通过一个简单的像素着色器来运行所有绘图操作,该着色器执行Porter-Duff source-over-dest混合本身.似乎只有在为混合目的插值时才会出现问题.因此,如果着色器读取两个像素值,计算结果,并将值(而不是混合)写入目标,则应该缓解问题.至少,它不应该复合.但是,肯定会有一些小的舍入不准确.

我知道在我提到的最初问题中,手动混合像素是不可行的.对于需要实时反馈的应用程序的2D画布实现,我没有找到解决此问题的方法.所有混合都将在CPU上完成,并会阻止其他JS代码执行.但是,使用WebGL,您的像素着色器在GPU上运行,因此我非常确定不应该有任何性能损失.

棘手的部分是能够将dest画布作为纹理提供给着色器,因为您还需要在画布上渲染它才能查看.最终,您希望每次需要更新时都不必从可视画布生成WebGL纹理对象.维护两个数据副本,其中一个作为内存中的纹理,一个作为可查看的画布(通过根据需要通过标记纹理来更新),应该可以解决这个问题.

Sim*_*ris 3

哦天啊。我想我们刚刚踏入了暮光区。恐怕我没有答案,但我至少可以提供更多信息。你的预感是对的,至少这里有更大的东西。

我更喜欢一个比你的例子简单得多的例子(因此不太容易出现任何类型的错误 - 我担心你可能会在某个地方遇到 0-255 而不是 0-1 的问题,但看起来不彻底),所以我开始了一些很小的东西。我发现的并不令人愉快:

<canvas id="canvas1" width="128" height="128"></canvas>
<canvas id="canvas2" width="127" height="127"></canvas>
Run Code Online (Sandbox Code Playgroud)

+

var can = document.getElementById('canvas1');
var ctx = can.getContext('2d');
var can2 = document.getElementById('canvas2');
var ctx2 = can2.getContext('2d');

var alpha = 2/255.0;

ctx.fillStyle = 'rgba(127, 0, 0, ' + alpha + ')';
ctx2.fillStyle = 'rgba(127, 0, 0, ' + alpha + ')'; // this is grey
//ctx2.fillStyle = 'rgba(171, 0, 0, ' + alpha + ')'; // this works

for (var i = 0; i < 32; i++) {
      ctx.fillRect(0,0,500,500);
      ctx2.fillRect(0,0,500,500);
}
Run Code Online (Sandbox Code Playgroud)

产生这个:

在此输入图像描述

在这里亲自看看:

http://jsfiddle.net/jjAMX/

看来,如果您的表面积太小(不是 fillRect 区域,而是画布区域本身),那么绘图操作将会很麻烦。

它似乎在 IE9 和 FF7 中运行良好。我将其作为错误提交给 Chromium。(我们会看看他们怎么说,但到目前为止,他们的典型做法是忽略大多数错误报告,除非它们是安全问题)。

我强烈建议您检查一下您的示例 - 我认为您可能在某个地方遇到 0-255 到 0-1 的问题。我这里的代码似乎工作得很好,除了预期的微红色之外,但它在所有浏览器中都是一致的结果颜色,除了这个 Chrome 问题。

最后,Chrome 可能正在做一些事情来优化表面积小于 128x128 的画布,这会导致事情变得混乱。