Reputation: 53
Can someone help me to fix the issue? I want to apply alpha layer mask to make some of the image transparent using canvas.
Thanks a lot.
var redImageData = redCanvas.getContext("2d").getImageData(0, 0, 200, 200); //overlay
var ImageData = imageCanvas.getContext("2d").getImageData(0, 0, 200, 200);
var px = redImageData.data;
var px2 = ImageData.data;
for(var i = 0; i < px.length; i += 4) {
if(px[i + 0] == 0 && px[i + 1] == 0 && px[i+2] == 0){
px[i + 3] = 0;
} else {
px[i + 0] = px2[i + 0];
px[i + 1] = px2[i + 1];
px[i + 2] = px2[i + 2];
px[i + 3] = px2[i + 3];
}
}
ctx.imageSmoothingEnabled = true;
ctx.putImageData(redImageData, 0, 0);
alpha mask overly https://i.sstatic.net/zCzOf.png
Upvotes: 5
Views: 6483
Reputation:
The linked image is not really an alpha mask but a matte image. The difference is that a matte image represent what would be an alpha-channel but showing it as RGB (or gray-scale) components. It doesn't actually have an alpha-channel. Matte images are common in video-compositing software but not so useful on web.
Canvas, or the Porter-Duff methods it uses, does not support matte images directly so they have to first be converted to an actual alpha-channel. To do this you have to iterate over each pixel and move one of the component values (from red, green or blue - doesn't matter which) into the alpha-channel.
When that is done you can use the canvas object that now has proper alpha-channel with composite operations which only uses the alpha information (blending modes is a different chapter).
The better approach is of course to provide the images as PNG with a proper alpha channel. But in any case, to show it's possible to also work with matte images, although not as efficient, we can do the following:
First step: this code section shows how you can efficiently do the pre-step of converting the matte image into an alpha channel. The resulting colors are not important for the main compositing step as it will only use the alpha-channel, as already mentioned.
Just make sure the image has loaded properly before trying to use the image by either using the image's onload
callback or running the script after everything has loaded.
This code will simply shift a component (blue in this case) using the full 32-bit value of the pixel (for efficiency) into the alpha-channel which leaves the image looking cyan but with proper alpha as you can see with the orange background showing through (most of the code is to handle loading and setup though).
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>
The second part then becomes about compositing using the new alpha-channel.
In this case the matte image's black becomes fully transparent while the white becomes fully opaque. You could have reversed this in the conversion step but it does not really matte as long as you're aware of the how the matte image looks.
To replace the interior content we use compositing mode source-in
. This will replace the content depending on the alpha value while keeping the alpha-channel as it is.
Dealing with the interior part first using the same mode allows us to do additional things with the content before drawing the frame (think vignette, shadows etc.).
As the final step we fill in the transparent areas, the frame itself, by using the composite mode destination-over
which replaces the more transparent ares with the content being drawn to canvas (conceptually it draws "behind" the existing content).
The code below uses simple colored boxes - just replace those with whatever you want to draw.
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>
Upvotes: 12