HTML5 Canvas png 的透明度怪异

HTML5 Canvas transparency weirdness with pngs

我正在尝试对从 canvas 检索到的一些 ImageData 应用透明度。简单吧?只需为每四个数据点设置一个适当的值……只是,它似乎只适用于没有预先存在的 alpa 值的像素。

//create 2 canvases
var canvas = document.createElement("canvas");
document.body.appendChild(canvas);
var ctx = canvas.getContext("2d");
canvas.id = "tempCanvas";
var canvas2 = document.createElement("canvas");
document.body.appendChild(canvas2);
var ctx2 = canvas2.getContext("2d");
canvas2.id = "tempCanvas2";

//draw the png in the first canvas
var testimg = document.getElementById("testimg");
ctx.drawImage(testimg, 0, 0, 200, 200);

//helper function to opacity to the pixels
opacity = function(pixels, value){
    var d = pixels.data;
    for(var i=0;i<d.length;i+=4){
        d[i+3] = value*255; //scale by 255
        if(d[i+3] > 255) d[i+3] = 255; //clamp
    }
    return pixels;
}

//make the first canvas' image data opaque
var data = ctx.getImageData(0,0,200,200);
data = opacity(data, 0.5);
//draw to second canvas
ctx2.fillStyle="#900";
ctx2.fillRect(0,0,200,200);

//should get a jsfiddle logo overlayed on red background
//instead, get yucky grey background
ctx2.putImageData(data, 0, 0);

(由于同源政策,复制有点困难,但这里有一个使用 png jsfiddle 标志的 fiddle:http://jsfiddle.net/97c0eetr/

为什么此不透明度算法不适用于 jsfiddle 徽标(以及我正在 http://localhost 上测试的其他透明 png)?

编辑:FF42 Ubuntu,如果有所不同的话。

您遇到了大端/小端问题:在今天的大多数 computers/devices 中,字节序是小端,这意味着对于 32 位值,字节顺序是 3-2-1- 0。

所以不透明度,即第 4 个字节(索引 3)将在 pixelIndex * 4 + 0 找到 ... == pixelIndex * 4.

只需在您的代码中将不透明度函数更改为:

opacity = function(pixels, value){
    var d = pixels.data;
    for(var i=0;i<d.length;i+=4){
        d[i] = value*255; //scale by 255
        if(d[i] > 255) d[i] = 255; //clamp
    }
    return pixels;
}

更新 fiddle 此处: http://jsfiddle.net/97c0eetr/5/

最好的 100% 解决方案当然是有两个不透明度函数,您可以根据浏览器的终端选择。但是通过押注小端,你可以覆盖 99% 的设备。

如果你想知道字节顺序,我曾经做了一个小要点来得到它,在这里找到它: https://gist.github.com/TooTallNate/4750953

代码如下:

function endianness () {
  var b = new ArrayBuffer(4);
  var a = new Uint32Array(b);
  var c = new Uint8Array(b);
  a[0] = 0xdeadbeef;
  if (c[0] == 0xef) return 'LE';
  if (c[0] == 0xde) return 'BE';
  throw new Error('unknown endianness');
}

顺便说一下,您的函数可以简化为

opacity = function(pixels, value){
    value = Math.floor(value * 255);
    if (value>255) value = 255;
    var d = pixels.data;
    for(var i=0;i<d.length;i+=4){
        d[i] = value;
    }
    return pixels;
}

编辑:那么为什么背景不是红色的? -->> 当使用 putImageData 时,您使用的是原始 1 像素复制 1 像素的方法。因此,所有内容都会被替换,例如 globalCompositeOperation 或任何设置。所以红色背景只是被新数据覆盖了。

解决方案是 putImageData 在一个临时的 canvas - 它将以正确的不透明度写入 - 然后 drawImage 在你的目标 canvas 上,所以平局考虑了不透明度。

因为您已经有两个 canvases,所以更改很容易,请在此处查看更新后的 fiddle: http://jsfiddle.net/97c0eetr/4/