Fabric.js eraser issue canvas

I just wrote my eraser in Fabric, and I hope to be able answer also the questions made by @kangax.

First, if you want to have a handwriting eraser, you should build an object like this:

canvas.freeDrawingBrush = new fabric.PencilBrush(canvas);

Then I slightly hacked the actual fabric library (v. 2.4.3) here:

createPath: function(pathData) {
      var path = new fabric.Path(pathData, {
        fill: null,
        stroke: this.color,
        strokeWidth: this.width,
        strokeLineCap: this.strokeLineCap,
        strokeMiterLimit: this.strokeMiterLimit,
        strokeLineJoin: this.strokeLineJoin,
        strokeDashArray: this.strokeDashArray,
        // ADDED BY AZ (24 Nov 2018)
        globalCompositeOperation: this.globalCompositeOperation,
        id: this.id
      });

Using globalCompositeOperation you can manage your canvas.freeDrawingBrush to draw a colored path (the color you wish, I choose red, but you can also choose the background color of your canvas) as this:

canvas.isDrawingMode = 1;
canvas.freeDrawingBrush.color = "red";
canvas.freeDrawingBrush.width = 10;
canvas.freeDrawingBrush.globalCompositeOperation = 'destination-out';
canvas.freeDrawingBrush.id = 'erasure';
ctx.beginPath(); // the context of canvas
canvas.renderAll();

So, when you move your mouse onto the canvas, you'll see a red path. When you move up the mouse, the path is finally created and the gCO is applied, erasing everything down the red path.

Well, if you want to save the canvas, I prefer to use the canvas.toSVG() function (it's great for retina screens if you're able to manage it). So, to save the canvas as SVG you just need this line canvas.toSVG() and you can store the result somewhere. When you want to restore the canvas, you should manage also the 'erasure' id, so you can use my restore function:

function restoreSketch(imageSVG) {
  fabric.loadSVGFromString(imageSVG, function (objects, options) {
    $.each(objects, function (index, value) {
      if (value.id && value.id == 'erasure') {
        value.set({
         globalCompositeOperation: 'destination-out'
        }); //set gCO for value
      }
   });                          
   var obj = fabric.util.groupSVGElements(objects, options);
   canvas.add(obj).renderAll();
});

I hope to be useful for everybody having headaches with Fabric.js

EDIT: as suggested by @Benni the lines related to the erasure can be displaced. If you want to fix them onto the canvas, you might slightly change the code using lockMovementX and lockMovementY. So, in the fabric.js lib, after

globalCompositeOperation: this.globalCompositeOperation,

add:

lockMovementX: this.lockMovementX,
lockMovementY: this.lockMovementY,

Then, in your code, after canvas.freeDrawingBrush.id = 'erasure'; add:

canvas.freeDrawingBrush.lockMovementX = true;
canvas.freeDrawingBrush.lockMovementY = true;

There's no built-in eraser in Fabric and implementing is a bit difficult.

The thing about Fabric is that everything is object-based and most of the things are also vector-based.

Unlike with native canvas, we can't just erase some pixels on a global bitmap. We have entire object model underneath, and canvas output is a simple loop of all those objects rendered onto canvas.

One way we could emulate eraser is perhaps by having some kind of overlay on top of canvas. And sort-of draw "erased" lines on it, giving illusion of underlying objects being wiped out.

But there are still complications with this:

  • How would we serialize this layer (to JSON or to SVG)?
  • What if you erase half of previously-drawn path and then want to work with already erased shape? The shape itself needs to be modified; overlay wouldn't work.
  • Would eraser affect only shapes or also background color? What about background image?

There's likely more issues that I didn't think of at the moment.