2

I have an HTML5 Canvas. I am using the KineticJS(KonvaJS) canvas library. On a blank canvas I dram an image as shown in the figure below. Now I want to create a circle Shape which can be used to erase parts of the image. The red circle in the image is the eraser.

enter image description here

How can I erase parts of an Image on HTML5 Canvas?

Michael
  • 32,527
  • 49
  • 210
  • 370
  • Please don't "chain" additional follow-on questions to your original question. That often changes the basis of the answer. It certainly does on this question you ask. Thanks! ;-) – markE Feb 10 '15 at 20:20

3 Answers3

3

You can use Compositing to "erase" pixels.

Specifically you use destination-out compositing.

KineticJS does not support compositing, but you still have a couple of options:

(Note: KineticJS has become KonvaJS and I haven't checked whether KonvaJs supports compositing. If it now does, just use destination-out compositing inside KonvaJS)

Option#1: Use a native canvas element as your Kinetic.Image source

  • Create an in-memory html5 canvas using var c=document.createElement,

  • Resize the canvas to image size,

  • drawImage your image onto the canvas,

  • Create a Kinetic.Image and set its image property to a reference to the native canvas. The Kinetic.Image will display whatever is drawn onto the native canvas.

    var kImage=new Kinetic.Image({
    ...
    image:c,
    ...
    
  • Set the canvas Compositing to cause new drawings to "erase" existing pixels:

    c.globalCompositeOperation='destination-out';
    
  • Listen for drag events on your circle-eraser. Use those events to draw a circle on the canvas that move just like the Kinetic circle-eraser moves. Since the canvas's compositing is set to "erase", new drawings of the circle on the canvas will erase the image on the canvas.

Your Kinetic.Image exactly reflects its canvas source (var c), so your Kinetic.Image will also display the image being erased in response to the Kinetic circle-eraser movements.

Option#2: Use a Kinetic.Shape

You can do the same operation as Option#1 by creating a Kinetic.Shape on a separate layer and getting a reference to the native canvas context using:

var ctx = myShapeLayer.getContext()._context;

This is a weaker option because KineticJS will redraw the shape--causing your erasing to be undone. Therefore you must do the additional step of saving all your circle-eraser's movements and replaying those movements (in drawFunc) to redo your erasing.

markE
  • 102,905
  • 11
  • 164
  • 176
  • Is there a way to vary the strength of the eraser? Something like a streng value which defines 0 meaning no erasing of pixel and 1 meaning pixel will be erased. While all the values between 0 and 1 define how strong the eraser is on the pixel it erases. – Michael Feb 10 '15 at 20:09
  • I'm guessing your asking about combining alpha with compositing. You can do that, but you must ask about that in a new question since your original question says nothing about alpha blending. ;-) – markE Feb 10 '15 at 20:19
  • Here is the question: http://stackoverflow.com/questions/28441093/how-can-i-combine-alpha-with-compositing-in-images – Michael Feb 10 '15 at 20:39
  • Isn't there a jsfiddle or jsbin to these steps of Option#1? – Mahdi Alkhatib Apr 13 '16 at 13:24
  • 1
    @MahdiAlkhatib. Here's an [example](https://jsfiddle.net/m1erickson/jL0zz58h/) using native canvas. I leave it to you to adapt it to Kinetic, or Konva, or whatever library you're using. Cheers! – markE Apr 13 '16 at 16:38
3

Thanks for markE for his detailed answer,

I have tried to get the context from the Konva.Layer() and it worked.

    var freeHandDrawingImage = new Image();
    freeHandDrawingImage.onload = function() {
        var context = freeHandDrawingLayer.getContext('2d');
        context.drawImage(this, 0,0);
        context.globalCompositeOperation='destination-out';
        freeHandDrawingLayer.draw();
    };
    freeHandDrawingImage.src = "image.png";

and I have used the Konva.Shape to erase by "destination-out" and draw free draw by custom "source-over":

freeDrawingType = 'brush';
isFreeDrawingMode = false;
isPaint = false;
lastPointerPosition = {};

drawFreeDrawings = function(){

    var freeDraw = new Konva.Shape({
        name: "freeDraw",
        stroke: 'black',
        strokeWidth: 5,
        closed : false,
        sceneFunc: function(context){
            // free draw quad
            debugger;
            if(isPaint){
                if (freeDrawingType === 'brush') {
                    context.globalCompositeOperation = 'source-over';
                }
                if (freeDrawingType === 'eraser') {
                    context.globalCompositeOperation = 'destination-out';
                }
                context.beginPath();
                context.moveTo(lastPointerPosition.x, lastPointerPosition.y);
                var newPosition = stage.getPointerPosition();

                context.lineTo(newPosition.x, newPosition.y);
                context.stroke();
                debugger;
                lastPointerPosition = newPosition;
                context.strokeShape(this);

            }
        }
    });
    freeHandDrawingLayer.add(freeDraw);
    // now we need to bind some events
    // we need to start drawing on mousedown
    // and stop drawing on mouseup
    selectionBoxBackground.on('mousedown', function() {
        if(isFreeDrawingMode){
            isPaint = true;
            lastPointerPosition = stage.getPointerPosition();
            stage.draw();
        }
    });

    selectionBoxBackground.on('mouseup', function() {
        if(isFreeDrawingMode){
            isPaint = false;
        }
    });

    // and core function - drawing
    selectionBoxBackground.on('mousemove', function() {
            if (!isPaint) {
                return;
            }      
            freeHandDrawingLayer.draw();
    });
}
Mahdi Alkhatib
  • 1,954
  • 1
  • 29
  • 43
0

In plain JavaScript this is pretty straight forward.

First get your canvas and drawing context ready:

var context=document.getElementById("your_canvas_id").getContext("2d");
var image=document.getElementById("your_image_id");

Now you want to draw the image to the context:

context.drawImage(image,0,0,image.width,image.height,0,0,image.width,image.height);

Now, when you want to erase part of your image, just draw over the canvas:

var x=y=radius=10;// Circle coordinates and radius.

context.fillStyle="#ffffff";// Your eraser color (not transparent)
context.beginPath();
context.arc(x,y,radius,0,Math.PI*2);
context.fill();

This only simulates erasing, however. If you want what you erase to be transparent afterwards, you might look into context.clearRect, but I'm not sure how you would do that with a circle.

Frank
  • 2,050
  • 6
  • 22
  • 40
  • [link](http://stackoverflow.com/questions/10396991/clearing-circular-regions-from-html5-canvas) This link describes how to actually erase a circular region of your canvas. – Frank Feb 10 '15 at 18:11