如何使用画布 imageData 停止 alpha 预乘?

IT技术 javascript canvas html5-canvas rgba
2021-03-16 19:12:41

有没有办法停止对画布数据的 alpha 通道进行预乘,或者有一种解决方法?

我想生成一个图像(在这种情况下是一些随机的 rgba 值)并将画布另存为图像。

在第二步中,我想使用 imageData 将原始图像与生成的图像进行比较,但是由于生成图像中我的 rgba 像素的 alpha 通道的预乘,这将不起作用。

这个例子

function drawImage(ctx) {
    var img = ctx.createImageData(canvas.width,canvas.height);

        for (var i=img.data.length;i-=4;) {     
                img.data[i] = Math.floor(Math.random() * 255);
                img.data[i+1] = Math.floor(Math.random() * 255);
                img.data[i+2] = Math.floor(Math.random() * 255);
                img.data[i+3] = Math.floor(Math.random() * 255);
        }

        ctx.putImageData(img, 0, 0);
            // our image data we just set
        console.log(img.data);
            // the image data we just placed onto the canvas
        console.log(ctx.getImageData(0,0,canvas.width, canvas.height).data);
}   

在控制台中,您会发现两个 console.log 输出。第一个在预乘之前,第二个在预乘之后。这两个输出是不同的,有些值相差 3 或更多。这仅在涉及部分透明度时发生(alpha 设置为 255 以外的任何值)。

有没有办法获得相同的输出?关于这个问题的任何想法?任何想法如何为这个问题创建类似解决方法的东西?

先感谢您!

3个回答

Bleh,就画布规范而言,这是一个公认的问题。它指出:

由于与预乘 alpha 颜色值相互转换的有损性质,刚刚使用 putImageData() 设置的像素可能会作为不同的值返回到等效的 getImageData()。

所以这:

var can = document.createElement('canvas');
var ctx = can.getContext('2d');
can.width = 1;
can.height = 1;
var img = ctx.createImageData(1, 1);
img.data[0] = 40;
img.data[1] = 90;
img.data[2] = 200;
var ALPHAVALUE = 5;
img.data[3] = ALPHAVALUE;
console.log(img.data); 
ctx.putImageData(img, 0, 0);
console.log(ctx.getImageData(0, 0, 1, 1).data); 

输出:

[40, 90, 200, 5]
[51, 102, 204, 5]

在所有浏览器中。

所以这是一个有损操作,除非他们更改规范以提供不使用预乘的选项,否则没有解决方法。早在 2008 年的 WHATWG 邮件列表中就讨论过这个问题,他们认为“往返”/放置/获取图像数据的身份不是规范愿意要求的Promise。

如果您需要“保存”图像数据,则无法使用 putImageData 保存它并保持相同的保真度。通过将完整 alpha 数据绘制到临时画布并使用较小的画布重新绘制到主画布的解决方法globalAlpha也不起作用。

所以你运气不好。对不起。


直到今天(2014 年 5 月 12 日)这仍然在 WHATWG 列表中讨论:http : //lists.whatwg.org/htdig.cgi/whatwg-whatwg.org/2014-May/296792.html

很高兴你的注意力被吸引到了这一点,我知道如果有人能猜到,那就是你。
2021-04-22 19:12:41
2021-04-22 19:12:41
如果我们在获取画布上下文时使用 getContext("2d", {alpha:false}) 会怎样?这会否定 alpha 预计算吗?这对需要 alpha 的人没有帮助,但如果它有效,它会为其他人加快速度。
2021-04-22 19:12:41
不幸的是,lists.whatwg.org 链接已失效,我找不到任何存档副本。我找到了这个档案,但没有主题标题我不确定你指的是哪条消息-lists.w3.org/Archives/Public/public-whatwg-archive/2014May/...
2021-04-29 19:12:41

我找到了一种方法来读取使用WebGL的2.这是有关我的问题的图像的精确字节值这里看看下面的代码,它比较getImageData了预期输出和 WebGL 的输出:

let image = new Image();

image.addEventListener('load', function () {
  let canvas = document.createElement('canvas');
  let ctx = canvas.getContext("2d");
  canvas.width = this.width;
  canvas.height = this.height;
  ctx.drawImage(this, 0, 0);
  let data = ctx.getImageData(0, 0, this.width, this.height).data;
  document.getElementById('imageData').innerHTML = data;
});

image.addEventListener('load', function () {
  let canvas = document.createElement('canvas');
  let gl = canvas.getContext("webgl2");
  gl.activeTexture(gl.TEXTURE0);
  let texture = gl.createTexture();
  gl.bindTexture(gl.TEXTURE_2D, texture);
  const framebuffer = gl.createFramebuffer();
  gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
  gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
  gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, this);
  gl.drawBuffers([gl.COLOR_ATTACHMENT0]);
  let data = new Uint8Array(this.width * this.height * 4);
  gl.readPixels(0, 0, this.width, this.height, gl.RGBA, gl.UNSIGNED_BYTE, data);
  document.getElementById('webGl').innerHTML = data;
});

image.src = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR4nGPg2T3JDgADyAGYmiSbAQAAAABJRU5ErkJggg==";
<pre>Expected:  12,187,146,62<br>
ImageData: <span id="imageData"></span><br>
WebGL:     <span id="webGl"></span><br></pre>

哦...我实际上找到了一种更好的方法来做我想做的事。不管怎么说,还是要谢谢你!
2021-04-19 19:12:41
谢谢,我发现您的代码段非常有帮助。问题的一些相关延续:是否有一种简单的方法可以扩展此代码段以将这些像素显示在画布上而不是临时帧缓冲区中?或者这是否需要编写着色器?
2021-04-20 19:12:41
@RReverser 有什么更好的方法?
2021-04-27 19:12:41
我对 WebGL 2 的经验并不丰富,但可以通过不创建新的帧缓冲区(因此内容被渲染到画布,这是默认设置)并使用gl.drawBuffers([gl.NONE]). 如果这不起作用,您可能必须使用着色器以通常的方式进行操作。
2021-05-06 19:12:41

嗯,这仍然是一个无赖...

我想如果你在 webgl 的纹理中使用它,你可以将它作为一个统一的字节数组传递