Fabricjs mask object with transformation - 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";

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.

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>

Related

Proper way to approach loadable masks in Fabric.js

I have this bounty open Fabricjs mask object with transformation when trying to mask objects with Fabric.js.
The tool I'm developing should allow users to draw a mask over image objects, and apply transformations (skew scale rotate etc) to this object before or after the mask. I'm close to obtaining this result but objects with an angle are still not working.
I'm also trying to save this object to a database using toJSON and loadFromJSON, but after a few days trying to accomplish this I realize that this solution will not work because any references outside the ctx scope can't be accessed while loading from JSON, so they throw an error.
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);
}
Is Fabric.js the proper solution to this problem? Should I be using something else? If this can be done with Fabric.js, what is the proper approach?
I extended fabric.Image with some custom attributes.
Also I attached the mask on fabric.Image.
For fabric.Image.fromObject after the image is loaded I need it to load also the mask( which I know is a path) and attach to image.
This is a fast implementation. I'm pretty sure this code can be simplified.
Please tell me know if something is not clear enougth
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
}
});
object = canvas.getObjects()[0];
object.originalObjLeft = object.left,
object.originalObjTop = object.top,
object.originalMaskScaleX = mask.scaleX,
object.originalMaskScaleY = mask.scaleY,
object.originalObjScaleX = object.scaleX,
object.originalObjScaleY = object.scaleY;
var transformedTranslate = object.translateToGivenOrigin({
x: object.left,
y: object.top
}, object.originX, object.originY, 'center', 'center');
object.originalTransformLeft = transformedTranslate.x - object.getCenterPoint().x;
object.originalTransformTop = transformedTranslate.y - object.getCenterPoint().y;
object.originalAngle = object.angle;
object.clipMask = mask;
object.set({
clipTo: function(ctx) {
ctx.save();
ctx.rotate(-this.originalAngle * Math.PI / 180);
ctx.translate(this.originalTransformLeft / this.originalObjScaleX, this.originalTransformTop / this.originalObjScaleY)
this.clipMask.set({
left: -object.width / 2 - (this.clipMask.width / 2 * this.originalMaskScaleX) - this.originalObjLeft / this.originalObjScaleX,
top: -object.height / 2 - (this.clipMask.height / 2 * this.originalMaskScaleY) - this.originalObjTop / this.originalObjScaleY,
objectCaching: false
});
this.clipMask.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: 45,
top: 50,
left: 100
});
canvas.add(object);
};
image.src = "http://i.imgur.com/8rmMZI3.jpg";
fabric.util.object.extend(fabric.Image.prototype, {
clipMask: null,
originalObjLeft: 0,
originalObjTop: 0,
originalMaskScaleX: 1,
originalMaskScaleY: 1,
originalObjScaleX: 1,
originalObjScaleY: 1,
originalAngle:0,
originalTransformLeft:0,
originalTransformTop:0
});
fabric.Image.prototype.toObject = (function(toObject) {
return function(propertiesToInclude) {
return fabric.util.object.extend(toObject.call(this, propertiesToInclude), {
clipMask: this.clipMask ? this.clipMask.toObject(propertiesToInclude) : null,
originalObjLeft: this.originalObjLeft,
originalObjTop: this.originalObjTop,
originalMaskScaleX: this.originalMaskScaleX,
originalMaskScaleY: this.originalMaskScaleY,
originalObjScaleX: this.originalObjScaleX,
originalObjScaleY: this.originalObjScaleY,
originalAngle:this.originalAngle,
originalTransformLeft:this.originalTransformLeft,
originalTransformTop:this.originalTransformTop
});
}
})(fabric.Image.prototype.toObject);
fabric.Image.fromObject = (function(fromObject) {
return function(_object, callback) {
fromObject.call(this, _object, (function(callback, _object) {
return function(image) {
if (image.clipMask) {
fabric.Path.fromObject(image.clipMask, (function(callback) {
return function(path) {
path.pathOffset.x = 0;
path.pathOffset.y = 0;
image.clipMask = path;
callback(image);
}
})(callback))
} else {
callback(image);
}
}
})(callback, _object));
return;
}
})(fabric.Image.fromObject)
$("#button1").on('click', function() {
let dataJSON = canvas.toJSON();
canvas.clear();
canvas.loadFromJSON(
dataJSON,
canvas.renderAll.bind(canvas));
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/2.3.6/fabric.js"></script>
<script src="https://code.jquery.com/jquery-2.2.4.min.js" integrity="sha256-BbhdlvQf/xTY9gja0Dq3HiwQF8LaCRTXxZKRutelT44=" crossorigin="anonymous"></script>
<button id="button1">SAve/Load JSON</button>
<div class="canvas__wrapper">
<canvas id="canvas" width="1280" height="720"></canvas>
</div>
UPDATE
I updated the code to fix the problem with angle from here:

How to align object by bounding box in FabricJS?

Wondering if there is a way to align objects in FabricJs by their bounding box?
I'm using obj.getBoundingRect() function to determine objects bounders, then compare them with a bounding box (BB) coordinates of an Active one (that one which I move). If I see that something falls between some gap (let's say 10px) I assign an active object top to be the same top as a comparable element by using a .setTop() property.
The problem is that TOP is not a right attribute to use, since the top of the bounding box may differ between elements. For example, 2 elements with the same top but different angle will have different Bounding Box Top...
Hope you see my point...
https://jsfiddle.net/redlive/hwcu1p4f/
var canvas = this.__canvas = new fabric.Canvas('canvas');
//fabric.Object.prototype.transparentCorners = false;
var red = new fabric.Rect({
id: 1,
left: 100,
top: 50,
width: 100,
height: 100,
fill: 'red',
angle: 0,
padding: 10
});
canvas.add(red);
var green = new fabric.Rect({
id: 2,
left: 250,
top: 180,
width: 100,
height: 100,
fill: 'green',
angle: 45,
padding: 10
});
canvas.add(green);
canvas.renderAll();
canvas.on("object:moving", function(e){
const draggableObj = e.target;
const draggableObjBound = draggableObj.getBoundingRect();
canvas.forEachObject(function(obj) {
if (obj.id !== draggableObj.id) {
var bound = obj.getBoundingRect();
if (draggableObjBound.top > bound.top - 10 && draggableObjBound.top < bound.top + 10) {
draggableObj.setTop(obj.getTop());
}
}
});
});
canvas.forEachObject(function(obj) {
var setCoords = obj.setCoords.bind(obj);
obj.on({
moving: setCoords,
scaling: setCoords,
rotating: setCoords
});
});
canvas.on('after:render', function() {
canvas.contextContainer.strokeStyle = '#555';
canvas.forEachObject(function(obj) {
var bound = obj.getBoundingRect();
canvas.contextContainer.strokeRect(
bound.left,
bound.top,
bound.width,
bound.height
);
})
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/2.0.0-rc.3/fabric.js"></script>
<canvas id="canvas" width="800" height="500" style="border:1px solid #ccc"></canvas>
You should use the center to align them, that is not gonna change.
to align the bounding box at left 5 for example:
1) calculate bounding box.
2) set the position of the object to 5 + bb.width/2 considering center.
In this case the bounding rects get aligned.
var canvas = this.__canvas = new fabric.Canvas('canvas');
//fabric.Object.prototype.transparentCorners = false;
var red = new fabric.Rect({
id: 1,
left: 100,
top: 50,
width: 100,
height: 100,
fill: 'red',
angle: 0,
padding: 10
});
canvas.add(red);
var green = new fabric.Rect({
id: 2,
left: 250,
top: 180,
width: 100,
height: 100,
fill: 'green',
angle: 45,
padding: 10
});
canvas.add(green);
//ALIGN EVERYTHING TO 5
canvas.forEachObject(function(object) {
var bb = object.getBoundingRect();
object.setPositionByOrigin({ x: 5 + bb.width/2, y: bb.top }, 'center', 'center');
object.setCoords();
});
canvas.renderAll();
canvas.on("object:moving", function(e){
const draggableObj = e.target;
const draggableObjBound = draggableObj.getBoundingRect(true, true);
canvas.forEachObject(function(obj) {
if (obj.id !== draggableObj.id) {
var bound = obj.getBoundingRect(true, true);
if (draggableObjBound.top > bound.top - 10 && draggableObjBound.top < bound.top + 10) {
draggableObj.setPositionByOrigin({ x: draggableObj.left, y: bound.top + draggableObjBound.height/2 }, draggableObj.originX, 'center');
}
}
});
});
canvas.forEachObject(function(obj) {
var setCoords = obj.setCoords.bind(obj);
obj.on({
moving: setCoords,
scaling: setCoords,
rotating: setCoords
});
});
canvas.on('after:render', function() {
canvas.contextContainer.strokeStyle = '#555';
canvas.forEachObject(function(obj) {
var bound = obj.getBoundingRect(true, true);
canvas.contextContainer.strokeRect(
bound.left,
bound.top,
bound.width,
bound.height
);
})
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/2.0.0-rc.3/fabric.js"></script>
<canvas id="canvas" width="800" height="500" style="border:1px solid #ccc"></canvas>

Fabric JS set backgroundImage from fabric object

I want to create an artboard like sketch's artboard in fabric canvas elemet
like this:
let app = new Vue({
el: '#app',
computed: {
canvasSize() {
let VM = this
let el, width, height
el = VM.$refs.canvasBoxWrap
width = el.clientWidth
height = el.clientHeight
return { width, height }
}
},
data: {
dSize: ''
},
mounted() {
let VM = this
VM.dSize = VM.canvasSize
let fabricCanvasInit = () => {
let canvas = new fabric.Canvas(VM.$refs.facanvas , {
enableRetinaScaling: true
})
canvas.set({
'enableRetinaScaling': true,
'backgroundColor': '#dddddd'
})
canvas.setWidth( VM.canvasSize.width)
canvas.setHeight(VM.canvasSize.width / 16 * 9)
// canvas.set('enableRetinaScaling', true)
// canvas.set('backgroundColor' , '#dddddd')
let artBoard = new fabric.Rect({
stroke: '#000',
strokeWidth:1,
fill: 'rgba(255,255,255,1)',
width: VM.canvasSize.width - 80,
height: VM.canvasSize.width / 16 * 9 - 80
,
shadow : {
color: 'rgba(0,0,0,0.5)',
blur: 20,
offsetX: 0,
offsetY: 10,
opacity: 0.6,
fillShadow: true
}
})
canvas.add(artBoard)
canvas.artBoard = artBoard
canvas.artBoard.center()
canvas.artBoard.set({
'selectable' : false
})
canvas.renderAll()
console.log( canvas );
}
fabricCanvasInit()
}
})
but in this demo, the "artboard" was created by a fabric rect object.
When I change other object , like 'sendToBack()', I will reset the "artboard" object sendToBack()
I want add the rect with shadow like fabricCanvas.setBackgroundImage(...)
how to do that?
jsfiddle.net demo
(function() {
var canvas = this.__canvas = new fabric.Canvas('canvas');
// create a rectangle with a fill and a different color stroke
var artBoard = new fabric.Rect({
stroke: '#000',
strokeWidth:1,
fill: 'rgba(255,255,255,1)',
width: canvas.width - 40,
height: canvas.height - 40,
selectable:false,
shadow : {
color: 'rgba(0,0,0,0.5)',
blur: 20,
offsetX: 0,
offsetY: 10,
opacity: 0.6,
fillShadow: true,
}
})
canvas.centerObject(artBoard);
canvas.setBackgroundImage(artBoard);//add object as background
canvas.renderAll();
})();
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.11/fabric.min.js"></script>
<canvas id="canvas" width="400" height="400"></canvas>
You can add object as background canvas.setBackgroundImage(obj), Now this works as image and you can use sendToBack() and all . Here is your updated fiddle.

set Min and Max resize limits for fabric object

I am using fabric js for resizing object i want user to resize the object with in min&max limits. How to do this with fabric js.
I tried properties like
lockScalingX,lockScalingY,lockMomentX,lockMomentY but no luck.
Any help will be grateful.
Thanks,
There is no way to do it natively in fabric but you can hook in to the scaling event and make any modification to the object you should like. In this code I stop the scaling as well as correct fabric from shifting the top/left when I am over riding the scaling.
window.canvas = new fabric.Canvas('c');
var rect = new fabric.Rect({
left: 100,
top: 100,
width: 50,
height: 50,
fill: '#faa',
originX: 'left',
originY: 'top',
stroke: "#000",
strokeWidth: 1,
centeredRotation: true
});
canvas.add(rect);
var maxScaleX = 2;
var maxScaleY = 2;
rect.on('scaling', function() {
if(this.scaleX > maxScaleX) {
this.scaleX = maxScaleX;
this.left = this.lastGoodLeft;
this.top = this.lastGoodTop;
}
if(this.scaleY > maxScaleY) {
this.scaleY = maxScaleY;
this.left = this.lastGoodLeft;
this.top = this.lastGoodTop;
}
this.lastGoodTop = this.top;
this.lastGoodLeft = this.left;
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.6.4/fabric.min.js"></script>
<canvas id="c" width="600" height="600"></canvas>
Might come in handy for people searching for similar answers.
You can natively set the minimum limit for scaling by setting minScaleLimit. However, there isn't an out-of-the-box solution available for setting maximum limit.
i addapted the drag limit algorithm that i had. In my specific case, i have a canvas element with a background image and i had to limit the other objects resizing with the background image limits so i added extra margins to do that. But if you need to limit the objects resizing only with the canvas size you can set the extra margins to 0.
//Limit the draggable zone
this.canvas.on("object:scaling", function (e) {
let obj = e.target;
let canvas = obj.canvas;
let zoom = canvas.getZoom();
let pan_x = canvas.viewportTransform[4];
let pan_y = canvas.viewportTransform[5];
// width & height we are constraining to must be calculated by applying the inverse of the current viewportTransform
let canvas_height = canvas.height / zoom;
let canvas_width = canvas.width / zoom;
let totalWidth = obj.width * obj.scaleX;
let totalHeight = obj.height * obj.scaleY;
// if you need margins set them here
let top_margin = marginYTop;
let bottom_margin = marginYBottom;
let left_margin = marginXLeft;
let right_margin = marginXRight;
let top_bound = top_margin - pan_y;
let bottom_bound = canvas_height - bottom_margin - pan_y;
let left_bound = left_margin - pan_x;
let right_bound = canvas_width - right_margin - pan_x;
if (obj.top < top_bound || (obj.top + totalHeight) > bottom_bound) {
obj.scaleY = obj.canvas.lastScaleY;
obj.set("top", top_bound);
}
if (obj.left < left_bound || (obj.left + totalWidth) > right_bound) {
obj.scaleX = obj.canvas.lastScaleX;
obj.set("left", left_bound);
}
obj.canvas.lastScaleY = obj.scaleY;
obj.canvas.lastScaleX = obj.scaleX;
});
As this is top 1 topic in google:
Better answer (based on StefanHayden snipped ^^ and http://jsfiddle.net/fabricjs/58y8b/):
window.canvas = new fabric.Canvas('c');
var rect = new fabric.Rect({
left: 100,
top: 100,
width: 50,
height: 50,
fill: '#faa',
originX: 'left',
originY: 'top',
stroke: "#000",
strokeWidth: 1,
centeredRotation: true
});
//Gradient to see pattern on passing 0 in scaling
// horizontal linear gradient
rect.setGradient('fill', {
type: 'linear',
x1: -rect.width / 2,
y1: 50,
x2: rect.width / 2,
y2: 50,
colorStops: {
0: '#ffe47b',
1: 'rgb(111,154,211)'
}
});
canvas.add(rect);
var maxScaleX = 2;
var maxScaleY = 2;
//Set starting center point:
var centerPoint = rect.getCenterPoint();
//Save center point on center point changing events (moved, maybe some cases of: rotated (not center rotation), added and drop (just assuming for last two))
rect.on('moved rotated added drop', function() {
centerPoint = rect.getCenterPoint();
});
rect.on('scaling', function() {
if(this.scaleX > maxScaleX) {
this.scaleX = maxScaleX;
}
if(this.scaleY > maxScaleY) {
this.scaleY = maxScaleY;
}
rect.setPositionByOrigin(centerPoint, 'center', 'center');
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.6.4/fabric.min.js"></script>
<canvas id="c" width="600" height="600"></canvas>

Fabric.js: How to serialize clipTo objects since ToJSON does not work for it?

Any guidance with jsfiddle example showing ClipTo serialization will be appreciated? Current ToJSON function does not work when trying to serialize clipped objects. See the ToJSON implementation at the bottom of the code.
JSFiddle Link: http://jsfiddle.net/PromInc/ZxYCP/
var img01URL = 'https://www.google.com/images/srpr/logo4w.png';
var img02URL = 'http://fabricjs.com/lib/pug.jpg';
var canvas = new fabric.Canvas('c');
// Note the use of the `originX` and `originY` properties, which we set
// to 'left' and 'top', respectively. This makes the math in the `clipTo`
// functions a little bit more straight-forward.
var clipRect1 = new fabric.Rect({
originX: 'left',
originY: 'top',
left: 180,
top: 10,
width: 200,
height: 200,
fill: '#DDD', /* use transparent for no fill */
strokeWidth: 0,
selectable: false
});
// We give these `Rect` objects a name property so the `clipTo` functions can
// find the one by which they want to be clipped.
clipRect1.set({
clipFor: 'pug'
});
canvas.add(clipRect1);
var clipRect2 = new fabric.Rect({
originX: 'left',
originY: 'top',
left: 10,
top: 10,
width: 150,
height: 150,
fill: '#DDD', /* use transparent for no fill */
strokeWidth: 0,
selectable: false
});
// We give these `Rect` objects a name property so the `clipTo` functions can
// find the one by which they want to be clipped.
clipRect2.set({
clipFor: 'logo'
});
canvas.add(clipRect2);
function findByClipName(name) {
return _(canvas.getObjects()).where({
clipFor: name
}).first()
}
// Since the `angle` property of the Image object is stored
// in degrees, we'll use this to convert it to radians.
function degToRad(degrees) {
return degrees * (Math.PI / 180);
}
var clipByName = function (ctx) {
this.setCoords();
var clipRect = findByClipName(this.clipName);
var scaleXTo1 = (1 / this.scaleX);
var scaleYTo1 = (1 / this.scaleY);
ctx.save();
var ctxLeft = -( this.width / 2 ) + clipRect.strokeWidth;
var ctxTop = -( this.height / 2 ) + clipRect.strokeWidth;
var ctxWidth = clipRect.width - clipRect.strokeWidth;
var ctxHeight = clipRect.height - clipRect.strokeWidth;
ctx.translate( ctxLeft, ctxTop );
ctx.rotate(degToRad(this.angle * -1));
ctx.scale(scaleXTo1, scaleYTo1);
ctx.beginPath();
ctx.rect(
clipRect.left - this.oCoords.tl.x,
clipRect.top - this.oCoords.tl.y,
clipRect.width,
clipRect.height
);
ctx.closePath();
ctx.restore();
}
var pugImg = new Image();
pugImg.onload = function (img) {
var pug = new fabric.Image(pugImg, {
angle: 45,
width: 500,
height: 500,
left: 230,
top: 50,
scaleX: 0.3,
scaleY: 0.3,
clipName: 'pug',
clipTo: function(ctx) {
return _.bind(clipByName, pug)(ctx)
}
});
canvas.add(pug);
};
pugImg.src = img02URL;
var logoImg = new Image();
logoImg.onload = function (img) {
var logo = new fabric.Image(logoImg, {
angle: 0,
width: 550,
height: 190,
left: 50,
top: 50,
scaleX: 0.25,
scaleY: 0.25,
clipName: 'logo',
clipTo: function(ctx) {
return _.bind(clipByName, logo)(ctx)
}
});
canvas.add(logo);
};
logoImg.src = img01URL;
//convert to json
var serialized=JSON.stringify(canvas);
canvas.clear();
canvas.loadFromDatalessJSON(serialized);
alert(serialized);
fabricjs clipTo should be included in the json representation of the canvas by default.
So if you use toJSON you will find a clipTo field in the json representation of canvas containing the clipTo's function.
Here is a demo.

Resources