Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Fabricjs mask object with transformation

Tags:

fabricjs

I'm trying to mask an object using Fabric.js free drawing brush. It works fine if the object is in its default position and without any transformations. But once I add transformations to the object, the mask is placed in the wrong position. I'm not sure how to solve this. Can someone take a look?

I want to be able to apply any transformations, before or after the mask, without messing up the mask.

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

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

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

function clip(path) {
    canvas.isDrawingMode = false;
    canvas.remove(path);

    let mask = new fabric.Path(path.path, {
        top: object.top,
        left: object.left,
        objectCaching: false,
        strokeWidth: 0,
        pathOffset: {
            x: 0,
            y: 0
        }
    });

    let originalObjLeft = object.left,
        originalObjTop = object.top;

    object.set({
        clipTo: function(ctx) {
            mask.set({
                left: -object.width / 2 - mask.width / 2 - originalObjLeft,
                top: -object.height / 2 - mask.height / 2 - originalObjTop,
                objectCaching: false
            });
            mask.render(ctx);
        }
    });

    canvas.requestRenderAll();
}

// image

let image = new Image();
let object;

image.onload = function() {
    object = new fabric.Image(image, {
        width: 500,
        height: 500,
        //scaleX: 0.8,
        //scaleY: 0.8,
        //angle: 45,
        top: 50,
        left: 300
    });

    canvas.add(object);
};

image.src = "http://i.imgur.com/8rmMZI3.jpg";
like image 368
lolol Avatar asked Oct 10 '18 17:10

lolol


2 Answers

I implement an exemple with some transformations (scaleX,scaleY,left,top). I'm strugle to find a solution when the inital object have an angle different than 0. For the current solution I need it to divide the maskscale with the object scale and also adjust the positions.

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

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

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

function clip(path) {
    canvas.isDrawingMode = false;
    canvas.remove(path);

    let mask = new fabric.Path(path.path, {
        top: object.top,
        left: object.left,
        objectCaching: false,
        strokeWidth: 0,
    		scaleX : 1/object.scaleX,
        	scaleY : 1/object.scaleY,
        pathOffset: {
            x: 0,
            y: 0
        }
    });

    let originalObjLeft = object.left,
        originalObjTop = object.top,
        originalMaskScaleX = mask.scaleX,
         originalMaskScaleY = mask.scaleY,
          originalObjScaleX = object.scaleX,
         originalObjScaleY = object.scaleY;

    object.set({
        clipTo: function(ctx) {
       		
            mask.set({
                left: -object.width / 2   -( mask.width / 2  * originalMaskScaleX) - originalObjLeft/originalObjScaleX ,
                top: -object.height / 2   -( mask.height / 2 * originalMaskScaleY) - originalObjTop/originalObjScaleY ,
                objectCaching: false
            });
            mask.render(ctx);
        }
    });

    canvas.requestRenderAll();
}

// image

let image = new Image();
 

image.onload = function() {
    object = new fabric.Image(image, {
        width: 500,
        height: 500,
        scaleX: 0.8,
        scaleY: 0.8,
       // angle: 45,
        top: 50,
        left: 100
    });

    canvas.add(object);
};

image.src = "http://i.imgur.com/8rmMZI3.jpg";
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/2.3.6/fabric.js"></script>
<div class="canvas__wrapper">
  <canvas id="canvas" width="1280" height="720"></canvas>
</div>

You can check here for loadFromJSON support. The only problem remains is when the object is rotated.

like image 194
Marius Turcu Avatar answered Nov 03 '22 01:11

Marius Turcu


Basically whenever you set an angle, your context matrix has been transformed. In order to mask properly you need to return to initial state of the Transformation Matrices. Fabricjs handles first matrix with center point of an object (calculates center of an object with or without an angle). Second matrix is rotating matrix, and third - scaling. To display image with all options which are set to an object, you need to multiply all Matrices:

(First Matrix * Second Matrix) * Third Matrix

So the idea of clipping will be reverse engineering of rotating context and multiplications of matrices: difference between center points of regular object without rotation and center point of the same object but with rotation. After that take result of subtractions and divide by original object scale value.

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

const angle = 45;
let objectHasBeenRotated = false;

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

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

function clip(path) {
canvas.isDrawingMode = false;
canvas.remove(path);

let mask = new fabric.Path(path.path, {
    top: 0,
    left: 0,
    objectCaching: false,
    strokeWidth: 0,
    scaleX: 1 / object.scaleX,
    scaleY: 1 / object.scaleY,
    pathOffset: {
        x: 0,
        y: 0,
    }
});

let originalObjLeft = object.left,
    originalObjTop = object.top,
    originalMaskScaleX = mask.scaleX,
    originalMaskScaleY = mask.scaleY,
    originalObjScaleX = object.scaleX,
    originalObjScaleY = object.scaleY,
    transformedTranslate = object.translateToGivenOrigin({
        x: object.left,
        y: object.top
    }, object.originX, object.originY, 'center', 'center'),
    originalTransformLeft = transformedTranslate.x - object.getCenterPoint().x,
    originalTransformTop = transformedTranslate.y - object.getCenterPoint().y;
object.set({
    clipTo: function (ctx) {

        ctx.save();
        ctx.rotate(-angle * Math.PI / 180);

        ctx.translate(originalTransformLeft / originalObjScaleX, originalTransformTop / originalObjScaleY)

        mask.set({
            left: -object.width / 2 - (mask.width / 2 * originalMaskScaleX) - originalObjLeft / originalObjScaleX,
            top: -object.height / 2 - (mask.height / 2 * originalMaskScaleY) - originalObjTop / originalObjScaleY,

            objectCaching: false
        });
        mask.render(ctx);
        ctx.restore();
    }
});

canvas.requestRenderAll();
}


// image

let image = new Image();


image.onload = function () {
object = new fabric.Image(image, {
    width: 500,
    height: 500,
    scaleX: 0.8,
    scaleY: 0.8,
    angle: angle,
    top: 50,
    left: 300,
    id: 'pug'
});

canvas.add(object);

};

image.src = "http://i.imgur.com/8rmMZI3.jpg";
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/2.3.6/fabric.js"></script>
<div class="canvas__wrapper">
  <canvas id="canvas" width="1280" height="720"></canvas>
</div>
like image 33
Observer Avatar answered Nov 03 '22 00:11

Observer