Search code examples
htmlcanvasclipping

Using globalCompositeOperation in few phases


I'm drawing many things on my context. Shapes, texts, images..

I want to use achieve the same effect i'm getting using the clip method on the context with globalCompositeOperation (Using the clip is harder for me to perform and i don't know if possible for texts)

The user can draw few shapes. and then start a mask phase. to draw some more shapes, texts.. which would draw into the mask and then the next draw will be clipped in the masked phase. and then continue to regular drawing...

For ex.

The user draw this drawing

enter image description here

Then started masked mode and drew this 2 red lines

enter image description here

Then he stopped drawing into the mask, and start drawing rectangle to consider the mask

enter image description here

And finally applied the mask clipping and the result should look like this

enter image description here

I've managed to clip the rectangle with the lines if there were no earlier drawings.

// Starting the mask phase
ctx.strokeStyle = 'red';
ctx.lineWidth = 5;
ctx.beginPath();
ctx.moveTo(20, 20);
ctx.lineTo(240, 140);
ctx.moveTo(80, 20);
ctx.lineTo(300, 140);
ctx.stroke();

ctx.globalCompositeOperation = 'source-out';

ctx.fillStyle = 'cyan';
ctx.fillRect(50, 70, 250, 20);

// Setting the composition back
ctx.globalCompositeOperation = 'source-over';

but when i'm adding my drawings in the beginning of the code, the composition considering it as well.

ctx.fillStyle = 'brown';
ctx.beginPath();
ctx.arc(80, 80, 50, 50, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = 'yellow';
ctx.fillRect(80, 60, 150, 40);
ctx.fillStyle = 'black';
ctx.font = '40pt arial';
ctx.fillText('Hello', 130, 110);


// How to tell the context to start from here the compisition ???

How to tell the context to start composition from a certain point, if possible ?

I could create another canvas and draw the mask there.. and then draw the new canvas on the main canvas. But there is better solution ?


Solution

  • You can change the compositing mode at any point in the drawing flow by changing .globalCompositeOperation. But, as you've discovered, any new compositing mode will also affect existing canvas content.

    Your intuition is correct about using a second "staging canvas" to do compositing that won't destroy your existing content.

    You can use an in-memory canvas to do compositing and create your rect-with-erased-lines. Then you can drawImage this in-memory canvas to your main canvas. Since the compositing was done on your in-memory canvas, your existing circle-hello content is not undesirably affected by compositing.

    enter image description here

    Here's example code and a Demo:

    var canvas=document.getElementById("canvas");
    var ctx=canvas.getContext("2d");
    var cw=canvas.width;
    var ch=canvas.height;
    
    // create an in-memory canvas to do compositing
    var c=document.createElement('canvas');
    var cctx=c.getContext('2d');
    
    var img=new Image();
    img.onload=start;
    img.src="https://dl.dropboxusercontent.com/u/139992952/multple/temp6a.png";
    function start(){
      ctx.drawImage(img,0,0);
      addCompositedRectangle();
      ctx.drawImage(c,0,0);
    }
    
    
    function addCompositedRectangle(){
    
      // resize the in-memory canvas
      // Note: this automatically clears any previous content
      //       and also resets any previous compositing modes
      c.width=300; // largest width;
      c.height=140; // largest height;
    
      // Starting the mask phase
      cctx.strokeStyle = 'red';
      cctx.lineWidth = 5;
      cctx.beginPath();
      cctx.moveTo(20, 20);
      cctx.lineTo(240, 140);
      cctx.moveTo(80, 20);
      cctx.lineTo(300, 140);
      cctx.stroke();
    
      cctx.globalCompositeOperation = 'source-out';
    
      cctx.fillStyle = 'cyan';
      cctx.fillRect(50, 70, 250, 20);
    
      // Setting the composition back
      cctx.globalCompositeOperation = 'source-over';
    
    }
    body{ background-color: ivory; padding:10px; }
    #canvas{border:1px solid red;}
    <canvas id="canvas" width=300 height=300></canvas>