链接的图像实际上并不是 alpha mask,而是matte图像。不同之处在于哑光图像代表什么would是一个 alpha 通道,但将其显示为 RGB(或灰度)分量。它实际上没有 alpha 通道。遮罩图像在视频合成软件中很常见,但在网络上不太有用。
Canvas 或其使用的 Porter-Duff 方法不直接支持遮罩图像,因此必须首先将它们转换为实际的 Alpha 通道。为此,您必须迭代每个像素并将其中一个分量值(从红色、绿色或蓝色 - 无论是哪一个)移动到 alpha 通道中。
完成后,您可以使用画布对象,该对象现在具有正确的 alpha 通道和仅使用 alpha 信息的复合操作(混合模式是不同的章节)。
更好的方法当然是提供具有适当 Alpha 通道的 PNG 图像。但无论如何,为了表明也可以使用遮罩图像,尽管效率不高,我们可以执行以下操作:
将遮罩图像转换为 Alpha 通道
第一步:此代码部分展示了如何有效地执行将遮罩图像转换为 Alpha 通道的预处理步骤。正如已经提到的,最终的颜色对于主要合成步骤并不重要,因为它只会使用 alpha 通道。
只需在尝试使用图像之前确保图像已正确加载,方法是使用图像的onload
回调或在所有内容加载后运行脚本。
此代码将简单地将使用像素的完整 32 位值(为了提高效率)的组件(在本例中为蓝色)转移到 alpha 通道中,这使得图像看起来青色,但具有适当的 alpha,正如您在橙色背景中看到的那样显示出来(不过大部分代码是用来处理加载和设置的)。
window.onload = function() {
// at this stage the image has loaded:
var img = document.getElementById("img");
var canvas = document.getElementById("c");
var ctx = canvas.getContext("2d");
// - setup canvas to match image
canvas.width = img.naturalWidth;
canvas.height = img.naturalHeight;
// - draw image
ctx.drawImage(img, 0, 0);
// CONV. STEP: move a component channel to alpha-channel
var idata = ctx.getImageData(0, 0, canvas.width, canvas.height);
var data32 = new Uint32Array(idata.data.buffer);
var i = 0, len = data32.length;
while(i < len) {
data32[i] = data32[i++] << 8; // shift blue channel into alpha (little-endian)
}
// update canvas
ctx.putImageData(idata, 0, 0);
};
body {background: #f72; font-size:44px; color:rgba(0,0,0,0.5)}
<img id="img" crossorigin="anonymous" src="https://i.imgur.com/QRGYuWg.png"> ►
<canvas id="c"></canvas>
合成
第二部分接下来将涉及使用新的 Alpha 通道进行合成。
在这种情况下,哑光图像的黑色变得完全透明,而白色变得完全不透明。您可以在转换步骤中反转此操作,但只要您知道遮罩图像的外观,它就不会真正遮罩。
为了替换内部内容,我们使用合成模式source-in
。这将根据 alpha 值替换内容,同时保持 alpha 通道不变。
首先使用相同的模式处理内部部分允许我们在绘制框架之前对内容做其他事情(想想小插图、阴影等)。
作为最后一步,我们使用合成模式填充透明区域,即框架本身destination-over
它用绘制到画布的内容替换了更透明的区域(从概念上讲,它绘制在现有内容的“后面”)。
下面的代码使用简单的彩色框 - 只需将它们替换为您想要绘制的任何内容即可。
window.onload = function() {
var img = document.getElementById("img");
var canvas = document.getElementById("c");
var ctx = canvas.getContext("2d");
canvas.width = img.naturalWidth;
canvas.height = img.naturalHeight;
ctx.drawImage(img, 0, 0);
var idata = ctx.getImageData(0, 0, canvas.width, canvas.height);
var data32 = new Uint32Array(idata.data.buffer);
var i = 0, len = data32.length;
while(i < len) data32[i] = data32[i++] << 8;
ctx.putImageData(idata, 0, 0);
// COMP. STEPS: use the mask with composite operation. Since our frame
// is black (= transparent as alpha) we can use the following mode:
ctx.globalCompositeOperation = "source-in";
// draw something, here a blue box, replace with whatever you want
ctx.fillStyle = "blue";
ctx.fillRect(0, 0, canvas.width, canvas.height);
// to fill the frame area, still transparent, use this mode:
ctx.globalCompositeOperation = "destination-over";
ctx.fillStyle = "yellow";
ctx.fillRect(0, 0, canvas.width, canvas.height);
};
body {background: #f72; font-size:44px; color:rgba(0,0,0,0.5)}
<img id="img" crossorigin="anonymous" src="https://i.imgur.com/QRGYuWg.png"> ►
<canvas id="c"></canvas>