Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Merge canvas image and canvas alpha mask into dataurl generated png

given two canvas with the same pixel size, where canvas1 contains an arbitrary image (jpg, png or such) and canvas2 contains black and non-black pixels.

what i want to achive: using a third canvas3 i want to clone canvas1 and have every black canvas2 pixel (may including a threshold of blackness) be transparent in canvas3

i already have a working solution like this:

canvas3context.drawImage(canvas1,0,0);
var c3img = canvas3context.getImageData(0,0,canvas3.width,canvas3.height);
var c2img = canvas2context.getImageData(0,0,canvas2.width,canvas2.height);
loop(){
    if(c2img i-th,i+1-th,i+2-th pixel is lower than threshold)
      set c3img.data[i]=c3img.data[i+1]=c3img.data[i+2]=c3img.data[i+3]=0
}

the problem with above (pseudo) code is, that it is slow so my question is: anyone can share an idea how to speed this up significantly? i thought about webgl but i never worked with it - so i have no idea about shaders or the tools or terms needed for this. another idea was that maybe i could convert canvas2 to black&white somehow very fast (not just modifieng every pixel in a loop like above) and work with blend modes to generate the transparent pixels

any help is highly appreciated

like image 360
John Doe Avatar asked Jul 14 '14 16:07

John Doe


1 Answers

answering my own question, i provide a solution for merging an arbitrary image with a black&white image. what im still missing is how to set the alpha channel for just one color of a canvas.

I seperate the question in pieces and answer them each.

Question 1: How to convert a canvas into grayscale without iterating every pixel?

Answer: draw the image on to a white canvas with blend mode 'luminosity'

function convertCanvasToGrayscale(canvas){
    var tmp = document.createElement('canvas');
    tmp.width = canvas.width;
    tmp.height = canvas.height;
    var tmpctx = tmp.getContext('2d');

    // conversion
    tmpctx.globalCompositeOperation="source-over";  // default composite value
    tmpctx.fillStyle="#FFFFFF";
    tmpctx.fillRect(0,0,canvas.width,canvas.height);
    tmpctx.globalCompositeOperation="luminosity";
    tmpctx.drawImage(canvas,0,0);

    // write converted back to canvas
    ctx = canvas.getContext('2d');
    ctx.globalCompositeOperation="source-over";
    ctx.drawImage(tmp, 0, 0);
}

Question 2: How to convert a grayscale canvas into black&white without iterating every pixel?

Answer: two times color-dodge blend mode with color #FEFEFE will do the job

function convertGrayscaleCanvasToBlackNWhite(canvas){
    var ctx = canvas.getContext('2d');

    // in case the grayscale conversion is to bulky for ya
    // darken the canvas bevore further black'nwhite conversion
    //for(var i=0;i<3;i++){
    //    ctx.globalCompositeOperation = 'multiply';
    //    ctx.drawImage(canvas, 0, 0);
    //}

    ctx.globalCompositeOperation = 'color-dodge';
    ctx.fillStyle = "rgba(253, 253, 253, 1)";
    ctx.beginPath();
    ctx.fillRect(0, 0, canvas.width, canvas.height);
    ctx.fill();
    ctx.globalCompositeOperation = 'color-dodge';
    ctx.fillStyle = "rgba(253, 253, 253, 1)";
    ctx.beginPath();
    ctx.fillRect(0, 0, canvas.width, canvas.height);
    ctx.fill();
}

Note: this function assumes that you want black areas left black and every non-black pixel become white! thus a grayscale image which has no black pixel will become completely white the reason i choose this operation is that it worked better in my case and using only two blend operations means its pretty fast - if you want that more dark pixel be left black and more white pixel become white you can use the commented for loop to darken the image beforehand. thus dark pixel will become black and brighter pixel become darker. as you increase the amount of black pixel's using color-dodge will again do the rest of the job

Question 3: How to merge a Black&White canvas with another canvas without iterating every pixel?

Answer: use 'multiply' blend mode

function getBlendedImageWithBlackNWhite(canvasimage, canvasbw){
    var tmp = document.createElement('canvas');
    tmp.width = canvasimage.width;
    tmp.height = canvasimage.height;

    var tmpctx = tmp.getContext('2d');

    tmpctx.globalCompositeOperation = 'source-over';
    tmpctx.drawImage(canvasimage, 0, 0);

    // multiply means, that every white pixel gets replaced by canvasimage pixel
    // and every black pixel will be left black
    tmpctx.globalCompositeOperation = 'multiply';
    tmpctx.drawImage(canvasbw, 0, 0);

    return tmp;
}

Question 4: How to invert a Black&White canvas without iterating every pixel?

Answer: use 'difference' blend mode

function invertCanvas(canvas){
    var ctx = canvas.getContext("2d");

    ctx.globalCompositeOperation = 'difference';
    ctx.fillStyle = "rgba(255, 255, 255, 1)";
    ctx.beginPath();
    ctx.fillRect(0, 0, canvas.width, canvas.height);
    ctx.fill();
}

now to 'merge' an canvasimage with a canvasmask one can do

convertCanvasToGrayscale(canvasmask);
convertGrayscaleCanvasToBlackNWhite(canvasmask);
result = getBlendedImageWithBlackNWhite(canvasimage, canvasmask);

regarding performance: obviously those blend modes are much faster than modifieng every pixel and to get a bit faster one can pack all functions together as needed into one function and recycle only one tmpcanvas - but thats left to the reader ^^

as a sidenote: i tested how the size of the resulting png differs when you compare above's getBlendedImageWithBlackNWhite result with the same image but the black areas are made transparent by iterating every pixel and setting the alpha channel the difference in size is nearly nothing and thus if you dont really need the alpha-mask the information that every black pixel is meant to be transparent may be enough for futher processing

note: one can invert the meaning of black and white using the invertCanvas() function

if you want to know more of why i use those blend modes or how blend modes really work you should check the math behind them - imho you're not able to develop such functions if ya dont know how they really work: math behind blend modes canvas composition standard including a bit math

need an example - spot the difference: http://jsfiddle.net/C3fp4/1/

like image 183
John Doe Avatar answered Nov 01 '22 20:11

John Doe