Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Clip object to path drawn using free drawing brush

Tags:

fabricjs

Fabric.js 2.3.6

I'm trying to clip an object to a path drawn with the free drawing bush. The code fails to show the image inside the path and I'm not sure what I'm doing wrong.

Multiple objects could be clipped, so I can't apply the path to the canvas itself.

let image = new Image();
let object;
let canvas;

// canvas

canvas = new fabric.Canvas("canvas", {
  backgroundColor: "lightgray",
  width: 1280,
  height: 720,
  preserveObjectStacking: true,
  selection: false
});

canvas.isDrawingMode = true;
canvas.freeDrawingBrush.color = "black";
canvas.freeDrawingBrush.width = 2;

canvas.on("path:created", function(options) {
   clip(options.path);
});

// clip

function clip(path) {

  object.set({
    clipTo: function(ctx) {
      path.render(ctx);
    }
  });

  canvas.requestRenderAll();

}

// image

image.onload = function() {

  object = new fabric.Image(image, {
    width: 500,
    height: 500,
    top: 50,
    left: 50
  });

  canvas.add(object);

};

image.src = "http://i.imgur.com/8rmMZI3.jpg";

https://jsfiddle.net/o91rv38q/7/

like image 453
lolol Avatar asked Sep 13 '18 15:09

lolol


1 Answers

In order to clip on the same spot when is path has been drawn, you need to reset pathOffset for a SVG path, and setTransform to the ctx. Your clip function will look like:

function clip(path) {
    path.set({pathOffset: {x: 0, y: 0}});
  object.set({
    clipTo: function(ctx) {
        ctx.save();    
      ctx.setTransform(1,0,0,1,0,0);
        ctx.beginPath();
        path._renderPathCommands(ctx);
        ctx.restore();
    }
  });

  canvas.requestRenderAll();

}

_renderPathCommands - renders a path.

Updated fiddle

To clip multiple objects you'll need to have some sort of an array of objects and then combine then into single path:

function combinePaths (paths) {
  if (!paths.length) {
    return null;
  }
    let singlePath = paths[0].path;
  for (let i = 1; i < paths.length; i++){
        singlePath = [...singlePath, ...paths[i].path];
  } 
  return new fabric.Path(singlePath, {
    top: 0,
    left: 0,
    pathOffset: {
        x: 0,
      y: 0
    }
  });
}

Here is an example with multiple paths to clip:

// canvas

let canvas = new fabric.Canvas("canvas", {
    backgroundColor: "lightgray",
    width: 1280,
    height: 720,
    preserveObjectStacking: true,
    selection: false
});

let paths = [];

canvas.isDrawingMode = true;
canvas.freeDrawingBrush.color = "black";
canvas.freeDrawingBrush.width = 2;

canvas.on("path:created", function (options) {
    paths.push(options.path);
    clip(combinePaths(paths));
});

function combinePaths(paths) {
    if (!paths.length) {
        return null;
    }
    let singlePath = paths[0].path;
    for (let i = 1; i < paths.length; i++) {
        singlePath = [...singlePath, ...paths[i].path];
    }
    return new fabric.Path(singlePath, {
        top: 0,
        left: 0,
        pathOffset: {
            x: 0,
            y: 0
        }
    });
}

function clip(path) {
    if (!path) {
        return;
    }
    object.set({
        clipTo: function (ctx) {
            var retina = this.canvas.getRetinaScaling();
            ctx.save();
            ctx.setTransform(retina, 0, 0, retina, 0, 0);
            ctx.beginPath();
            path._renderPathCommands(ctx);
            ctx.restore();
        }
    });

    canvas.requestRenderAll();

}

// image

let image = new Image();
let object;

image.onload = function () {

    object = new fabric.Image(image, {
        width: 500,
        height: 500,
        top: 50,
        left: 50
    });
    object.globalCompositeOperation = 'source-atop';
    canvas.add(object);

};

image.src = "http://i.imgur.com/8rmMZI3.jpg";
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/2.3.6/fabric.js"></script>
<canvas id="canvas" width="1280" height="720"></canvas>
like image 165
Observer Avatar answered Nov 15 '22 05:11

Observer