Reputation: 1001
I have two canvases - both of the same size. The MainCanvas is completely filled with a background image. On SecondaryCanvas I have a small image - a company logo that has some transparent areas (basically where all of the white pixels have had their alpha reduced to 0).
I want to apply some sort of bevel or innershadow to the SecondaryCanvas, but only where there is an actual image. Typically I see people stroke a path to do this, but it's impossible for me to trace the edges of the non-transparent area.
How can I do this? The thought just occurred to me that I could scan the entire SecondaryCanvas pixel-by-pixel and check to see if it's neighboring pixels are transparent or not (an attempt to find the 'edges')... and apply the appropriate colors to the pixel if it is... but that seems very CPU intensive.
Upvotes: 0
Views: 405
Reputation: 54059
Here is the quickest I know of using convolution filters.
Copys from source image to destination. I have added some flags as well for this solution.
The function (sorry about the spelling).
embose(imageDataSource, imageDataDestination, method, emboseImageEdge, perceptualCorrect, edgeOnly)
Function returns true if processed or false if not. If false no pixels are changed.
Todo.
I forgot the add Invert but that is easy to do just invert val = -val
all the vals in the embos arrays then set the center value to 1 (must be one or will not look good).
You can also rotate the direction of the light by rotating the negative/positive line in the embos array, but that is a little more complex.
For softer embos create larger convolution embos
arrays. Set size
and halfSize
to match. EG 3 pixel embos requires a 7 by 7 array with size=7
and halfSize = 3
with a similar set of values. The bigger the embos
array the slower the function.
For very large images this will block the page. If using this function for large images move this function to a web worker.
As it uses a convolution filter it can be adapted to do a host of other filters. Though this one only uses pixel luminance, it is easy to modify to a per colour channel filter. So filter type this can be adapted to. Gaussian blur, blur, sharpen, edge detect, and many more.
See bottom of code example how it is used.
Hope this does what you need or can be adapted to do so. Any question feel free to ask. Sorry the commenting is currently sparse but I am short on time. Will return to fix that soon.
var canvas = document.getElementById("canV");
var ctx = canvas.getContext("2d");
// Groover API log dependency replacement
function log(data){
// console.log(data); // just suck it up
}
// Groover Bitmaps API dependency replacement
// Extracted from Groover.Bitmaps
var createImage= function(w,h){ // create a image of requier size
var image = document.createElement("canvas");
image.width = w;
image.height =h;
image.ctx = image.getContext("2d"); // tack the context onto the image
return image;
}
function embose(imageDataSource,imageDataDestination, method, emboseImageEdge, perceptualCorrect,edgeOnly){
"use strict";
var dataS = imageDataSource.data;
var dataD = imageDataDestination.data;
var w = imageDataSource.width;
var h = imageDataSource.height;
if(dataS.length !== dataD.length){
return false; // failed due to size mismatch
}
var embos,size,halfSize;
var lMethod = method.toLowerCase(); // some JS engines flag reasignment of
// arguments as unoptimised as this
// is computationally intensive create a
// new var for lowercase
if(lMethod === "2 pixel soft" ){
embos = [
-0.25 , -0.5 , -1, -1 , 0.5,
-0.5 , -1 , -1, 1 , 1,
-1 , -1 , 1, 1 , 1,
-1 , -1 , 1, 1 , 0.5,
-0.5 , 1, 1, 0.5 , 0.25
];
size = 5;
halfSize = 2;
}else
if(lMethod === "2 pixel" ){
embos = [
-1 , -1 , -1, -1 , 1,
-1 , -1 , -1, 1 , 1,
-1 , -1 , 1, 1 , 1,
-1 , -1 , 1, 1 , 1,
-1 , 1, 1, 1 , 1
];
size = 5;
halfSize = 2;
}else
if(lMethod === "1 pixel" ){
embos = [
-1 , -1, -1,
-1, 1, 1,
1 , 1, 1
];
size = 3;
halfSize = 1;
}
var deb = 0
var x,y,l,pl,g,b,a,ind,scx,scy,cx,cy,cInd,nearEdge,pc;
pc = perceptualCorrect; // just for readability
for(y = 0; y < h; y++){
for(x = 0; x < w; x++){
ind = y*4*w+x*4;
l = 0;
nearEdge = false;
if(dataS[ind+3] !== 0){ // ignor transparent pixels
for (cy=0; cy<size; cy++) {
for (cx=0; cx<size; cx++) {
scy = y + cy - halfSize;
scx = x + cx - halfSize;
if (scy >= 0 && scy < h && scx >= 0 && scx < w) {
cInd = (scy*w+scx)*4;
if(dataS[cInd+3] === 0){
nearEdge = true;
}
l += pc?(embos[cy*size+cx] *
((Math.pow(dataS[cInd++]*0.2126,2)+
Math.pow(dataS[cInd++]*0.7152,2)+
Math.pow(dataS[cInd++]*0.0722,2)
)/3)):
(embos[cy*size+cx] *
((dataS[cInd++]+
dataS[cInd++]+
dataS[cInd++]
)/3));
}else
if(emboseImageEdge){
nearEdge = true;
}
}
}
if((nearEdge && edgeOnly) || ! edgeOnly){
if(pc){
pl = Math.sqrt((Math.pow(dataS[ind]*0.2126,2) + Math.pow(dataS[ind+1]*0.7152,2) + Math.pow(dataS[ind+2]*0.0722,2))/3);
l = Math.sqrt(Math.max(0,l));
if(pl > 0){
pl = l/pl;
dataD[ind] = Math.sqrt(dataS[ind]*dataS[ind++]*pl);
dataD[ind] = Math.sqrt(dataS[ind]*dataS[ind++]*pl);
dataD[ind] = Math.sqrt(dataS[ind]*dataS[ind++]*pl);
dataD[ind] = dataS[ind]; // alpha not effected
}else{ // black pixel
dataD[ind++] = 0;
dataD[ind++] = 0;
dataD[ind++] = 0;
dataD[ind] = dataS[ind];
}
}else{
l = Math.max(0,l);
pl = (dataS[ind]+dataS[ind+1]+dataS[ind+2])/3;
if(pl > 0){
pl = l/pl;
dataD[ind] = dataS[ind++]*pl;
dataD[ind] = dataS[ind++]*pl;
dataD[ind] = dataS[ind++]*pl;
dataD[ind] = dataS[ind]; // alpha not effected
}else{ // black pixel
dataD[ind++] = 0;
dataD[ind++] = 0;
dataD[ind++] = 0;
dataD[ind] = dataS[ind];
}
}
}else{ // if not edge then just copy image pixel to dest
dataD[ind] = dataS[ind++];
dataD[ind] = dataS[ind++];
dataD[ind] = dataS[ind++];
dataD[ind] = dataS[ind];
}
}else{ // set transparent pixel to zero
dataD[ind+3] = 0;
}
}
}
// all done
return true; // return success
}
var img = createImage(128,128);
img.ctx.font = "32px arial black";
img.ctx.textAlign = "center";
img.ctx.textBaseline = "middle";
img.ctx.lineCap = "round";
img.ctx.lineJoin = "round";
img.ctx.lineWidth = 4
img.ctx.strokeStyle = "#3AD";
img.ctx.fillStyle = "#334";
img.ctx.strokeText("LOGO!",64,64);
img.ctx.fillText("LOGO!",64,64);
ctx.drawImage(img,0,0);
var img1 = createImage(128,128);
var imgData = img.ctx.getImageData(0,0,128,128);
var imgData1 = img1.ctx.getImageData(0,0,128,128);
if(embose(imgData,imgData1,"2 pixel soft",false, true, false)){
img1.ctx.putImageData(imgData1,0,0);
log("ONe")
ctx.drawImage(img1,128,0);
}
img.ctx.fillStyle = "#DA3"; // make is look better for the sell ;)
img.ctx.fillText("LOGO!",64,64);
var imgData = img.ctx.getImageData(0,0,128,128);
var img1 = createImage(128,128);
var imgData1 = img1.ctx.getImageData(0,0,128,128);
if(embose(imgData,imgData1,"2 pixel",false, true, true)){
img1.ctx.putImageData(imgData1,0,0);
ctx.drawImage(img1,0,128);
}
var img1 = createImage(128,128);
var imgData1 = img1.ctx.getImageData(0,0,128,128);
if(embose(imgData,imgData1,"1 pixel",false, false, false)){
img1.ctx.putImageData(imgData1,0,0);
ctx.drawImage(img1,128,128);
}
.canC {
width:256px;
height:256px;
}
<canvas class="canC" id="canV" width=256 height=256></canvas>
Upvotes: 3