FabricJS - clip image to shape with background image - javascript

I would like to make a loaded image will be clipped by created shape. Here is result of my work.
https://jsfiddle.net/qqv32b5z/1/
It works well if you add an image. The loaded image is clipped by the existing shape.
The problem starts when I will add a background image.
The globalCompositeOperation with parameter source-atop, are not pointing to the shape, but to the background image.
How can I achieve a result that image will be clipped by the shape, even if background image is loaded?
Here is a version which not works as expected:
https://jsfiddle.net/undss329/
var canvas = new fabric.Canvas('canvas', {
'selection': false
});
let center = canvas.getCenter();
let currentShape = new fabric.Path('M0,0H63.73L98.64,47H34.91Z', {
fill: '#333333',
opacity: 1,
hasBorders: true,
hasControls: true,
hasRotatingPoint: true,
selectable: true,
preserveObjectStacking: true,
objectCaching: false,
top: center.top,
left: center.left,
});
canvas.setActiveObject(currentShape);
canvas.add(currentShape);
canvas.renderAll();
canvas.setBackgroundImage('http://fabricjs.com/lib/pug.jpg',
canvas.renderAll.bind(canvas), {
scaleX: 1,
scaleY: 1,
top: center.top,
left: center.left,
originX: 'center',
originY: 'center'
});
document.getElementById('image').onchange = function (e) {
var reader = new FileReader();
reader.onload = function (event) {
var img = new Image();
img.onload = function () {
var image = new fabric.Image(img, {
objectCaching: false,
hasControls: true,
selectable: true
});
image.globalCompositeOperation = 'source-atop';
image.scaleToWidth(currentShape.width);
image.top = center.top;
image.left = center.left;
canvas.add(image);
canvas.renderAll();
canvas.setActiveObject(image);
image.setCoords();
};
img.src = event.target.result;
};
reader.readAsDataURL(e.target.files[0]);
};

Related

Fabric JS How to call a function from another function

I have a function that hides and shows an image whenever it's clicked, but I want to do the same thing, by clicking another object.
Here is the code for the image
function drawCheckbox(left, top, width, height){
var imgClass = new fabric.Image.fromURL('https://image.flaticon.com/icons/svg/33/33281.svg',function(img){
img.width = width;
img.height = height;
img.left = left;
img.top = top;
img.set({
hoverCursor: 'default',
selectable: true,
opacity: 0
})
img.on('mousedown', function(e) {
if(e.target.opacity <= 0.5){
e.target.opacity = 1;
}else{
e.target.opacity = 0;
}
canvas.renderAll();
});
canvas.add(img);
canvas.renderAll();
})
}
And this is the code for the rectangle object:
function addRect(left, top, width, height, id) {
const o = new fabric.Rect({
width: width,
height: height,
fill: tableFill,
stroke: tableStroke,
strokeWidth: 2,
shadow: tableShadow,
originX: 'center',
originY: 'center',
centeredRotation: true,
snapAngle: 45,
selectable: true
})
const t = new fabric.IText(number.toString(), {
fontFamily: 'Calibri',
fontSize: 14,
fill: '#fff',
textAlign: 'center',
originX: 'center',
originY: 'center'
})
const g = new fabric.Group([o, t], {
left: left,
top: top,
centeredRotation: true,
snapAngle: 45,
selectable: true,
type: 'table',
id: id,
number: number
})
canvas.add(g)
number++
g.on('selected', function () {
// here I want to make de image dissapear, when the object is clicked
})
canvas.hoverCursor = 'pointer';
canvas.discardActiveObject();
canvas.renderAll();
return g;
}
I tried creating the image inside the rectangle, but it doesn't do anything when the rectangle it's clicked. Does anyone had a similar problem?
Here is the codepen: codepen.io/Zerro1/pen/PoZvmOE .
Here i have created a codepen for you, this is one way to do it. I tried to create a square and clicking on that square i am toggling the last checkbox visibility.
portion of code:
var square = new fabric.Rect({
width: 100,
height: 100,
fill: '#000',
left:120
});
square.on('mousedown', function(e) {
if(img.opacity <= 0.5){
img.opacity = 1;
}else{
img.opacity = 0;
}
canvas.renderAll();
});
canvas.add(img);
canvas.add(square);
canvas.renderAll();

FabricJs - Clipping area by SVG object

I have a problem with FabricJS objects. I would like to create a clipping area (svg object), which will clips loaded images.
I'm using clipTo method on loaded image passing it into clipping area shape object (SVG), but it does not work, because the SVG is not shape object in sense of FabricJS. Is there any way to convert the loaded SVG into FabricJS shape like Polygon or Rect?
It almost works good if I will use:
globalCompositeOperation = 'source-atop'
on the loaded image (and remove clipTo function), but if I will move/resize/rotate the image it overlaps border of the main clipping area (the biggest rect). Image should not interact with other objects outside the grey rect, especially with main clipping area.
Maybe is there a way to achieve this by playing with globalCompositeOperation?
I will be very grateful for any tips.
Here is JSFiddle of my problem:
https://jsfiddle.net/6nfdr1ng/
and here almost working version (with source-atop):
https://jsfiddle.net/ud9nev1g/
<input type="file" id="image"><br>
<canvas id="canvas" width="530" height="600"></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.21/fabric.min.js"></script>
<script type="text/javascript">
var canvas = new fabric.Canvas('canvas', {
'selection': false
});
/**
* loading man clipping rect
*/
var clippingRect = new fabric.Rect({
width: 185,
height: 400,
fill: 'transparent',
stroke: 1,
opacity: 1,
hasBorders: false,
hasControls: false,
hasRotatingPoint: false,
selectable: false,
preserveObjectStacking: true,
objectCaching: false
});
canvas.add(clippingRect);
canvas.renderAll();
/**
* loading shape which should be another clipping area for loaded image
*/
var svgString = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 63.7 63.7"><defs><style>.cls-1{fill:#79797a;fill-rule:evenodd;}</style></defs><title>Asset 4</title><g id="Layer_2" data-name="Layer 2"><g id="kwadrat"><path class="cls-1" d="M0,0H63.7V63.7H0Z"/></g></g></svg>';
var retinaScalling = canvas.getRetinaScaling();
fabric.loadSVGFromString(svgString, function (objects, options) {
var shapeObject = fabric.util.groupSVGElements(objects, options);
shapeObject.scaleX = 0.5;
shapeObject.scaleY = 0.5;
shapeObject.set('id', 'shape');
shapeObject.scaleToWidth(clippingRect.getWidth());
shapeObject.setCoords();
shapeObject.clipTo = function (ctx) {
ctx.save();
ctx.setTransform(retinaScalling, 0, 0, retinaScalling, 0, 0);
clippingRect.render(ctx);
ctx.restore();
};
shapeObject.center();
shapeObject.setCoords();
canvas.setActiveObject(shapeObject);
canvas.add(shapeObject);
canvas.renderAll();
});
/**
* loading image
*/
document.getElementById('image').onchange = function (e) {
var reader = new FileReader();
var clippingAreaShape = canvas.getActiveObject();
reader.onload = function (event) {
var img = new Image();
img.onload = function () {
var shapeImageObject = new fabric.Image(img, {
objectCaching: false,
hasControls: true,
selectable: true
});
shapeImageObject.scaleToHeight(clippingAreaShape.getWidth());
shapeImageObject.set('id', 'shape-image');
shapeImageObject.set({
left: clippingAreaShape.left,
top: clippingAreaShape.top
});
shapeImageObject.clipTo = function (ctx) {
ctx.save();
ctx.setTransform(retinaScalling, 0, 0, retinaScalling, 0, 0);
clippingAreaShape.render(ctx);
ctx.restore();
};
// shapeImageObject.globalCompositeOperation = 'source-atop';
canvas.add(shapeImageObject);
canvas.renderAll();
canvas.setActiveObject(shapeImageObject);
shapeImageObject.setCoords();
};
img.src = event.target.result;
};
reader.readAsDataURL(e.target.files[0]);
}
</script>
I resolved my problem by combining the SVG shape into one path (without any polygons any other svg objects), then I pass the svg coords into fabircJS path. The valid SVG coords looks like that "M0,0H63.7V63.7H0Z"
fabric.loadSVGFromURL('object.svg', function (objects, options) {
let img1 = new fabric.Path(objects[0].d, {
fill: '#333',
opacity: 1,
hasBorders: true,
hasControls: true,
hasRotatingPoint: true,
selectable: true,
preserveObjectStacking: true,
objectCaching: false,
});
}
I hope it will help someone else :)

Change path of object and wrong bounding rectangles

var canvas = new fabric.Canvas('canvas');
canvas.setWidth(500);
canvas.setHeight(500);
canvas.setBackgroundColor('#ccc');
canvas.allowTouchScrolling = true;
var line = new fabric.Path('M100,350 Q200,100 300,350', {
fill: '',
stroke: 'red',
strokeWidth: 5,
objectCaching: false
});
var circle = new fabric.Circle({
radius: 15,
fill: 'blue',
lockScalingY: true,
lockScalingX: true,
left: 300,
top: 350
});
canvas.add(line, circle);
canvas.on('object:moving', function(event) {
var path = canvas.item(0);
path.path[1][3] = event.target.left;
path.path[1][4] = event.target.top;
path.setCoords();
canvas.calcOffset();
canvas.renderAll();
});
<script src="//cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.20/fabric.js"></script>
<canvas id="canvas"></canvas>
show demo on fiddle
When I move circle there is a change path of line. After change path of line I see wrong bounding rectangles.
How I can fix that?
I can just remove line and create again, but maybe there is an easier way to do it.
You can actually recalculate the bounding box, but you have to do by yourself.
So is actually tricky, you need to remove the current pathOffset from path, and then call the internal method to recalculate the bbox.
It works, but being an internal method and a non supported feature, it could be changed in the future.
var canvas = new fabric.Canvas('canvas');
canvas.setWidth(500);
canvas.setHeight(500);
canvas.setBackgroundColor('#ccc');
canvas.allowTouchScrolling = true;
var line = new fabric.Path('M100,350 Q200,100 300,350', {
fill: '',
stroke: 'red',
strokeWidth: 5,
objectCaching: false
});
var circle = new fabric.Circle({
radius: 15,
fill: 'blue',
lockScalingY: true,
lockScalingX: true,
left: 300,
top: 350
});
canvas.add(line, circle);
canvas.on('object:moving', function(event) {
var path = canvas.item(0);
path.path[1][3] = event.target.left;
path.path[1][4] = event.target.top;
path.pathOffset = null;
path._setPositionDimensions({});
path.setCoords();
canvas.calcOffset();
canvas.renderAll();
});
<script src="//cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.20/fabric.js"></script>
<canvas id="canvas"></canvas>

Why objects which placed close without any indent, have a space between each other in FabricJS

I need to build a frame for my image in Fabric JS. This frame should be built dynamically to fit any size of the image.
To do this I use two images:
http://i.imgur.com/3VqKv1O.png - image for side
http://i.imgur.com/w41HYN3.png - image for corner
I've developed an algorithm, which does it and it works! That's great.
But I have a problem with the positioning of pieces of the frame.
As you can see in my example, there is strange space between pieces and they placed not on the same level by Y axis. I'm very surprised because these pieces have the same height and they have correct values for left and top.
Anybody knows what can be a reason of this problem?
Thanks in advance
JS Fiddle https://jsfiddle.net/Eugene_Ilyin/gzjxhLtm/9/
var sideObj = new fabric.Rect(),
frameSideTopImage = new fabric.Image(new Image(), {type: 'image'}),
frameCornerTLImage = new fabric.Image(new Image(), {type: 'image'}),
imageSize = 172,// Size of the image (width = height).
leftOffset = 100,
topOffset = 100;
// Load image for corner.
fabric.util.loadImage('http://i.imgur.com/w41HYN3.png', function (img) {
// Set image and options for top left corner.
frameCornerTLImage.setElement(img);
frameCornerTLImage.setOptions({
strokeWidth: 0,
left: leftOffset,
top: topOffset
});
// Load image for side.
fabric.util.loadImage('http://i.imgur.com/3VqKv1O.png', function (img) {
frameSideTopImage.setElement(img);
// Create canvas for pattern.
var canvasForFill = new fabric.StaticCanvas(fabric.util.createCanvasElement(), {enableRetinaScaling: false});
canvasForFill.add(frameSideTopImage);
// Configure top side for the frame.
sideObj.setOptions({
strokeWidth: 0,
objectCaching: false,
originX: 'left',
originY: 'top',
left: imageSize + 100,
top: 100,
width: 800,
height: imageSize,
fill: new fabric.Pattern({
source: function () {
canvasForFill.setDimensions({
width: imageSize,
height: imageSize
});
return canvasForFill.getElement();
},
repeat: 'repeat-x'
})
});
var canvas = new fabric.Canvas('c');
var frame = new fabric.Group([sideObj, frameCornerTLImage], {
strokeWidth: 0,
width: 800 * 3,
height: imageSize * 3,
scaleX: 1/3,
scaleY: 1/3,
}
);
canvas.add(frame);
canvas.renderAll();
});
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.7/fabric.js"></script>
<body>
<canvas id="c" width="1000" height="1000"></canvas>
</body>
var sideObj = new fabric.Rect(),
frameSideTopImage = new fabric.Image(new Image(), {type: 'image'}),
frameCornerTLImage = new fabric.Image(new Image(), {type: 'image'}),
imageSize = 172,// Size of the image (width = height).
leftOffset = 100,
topOffset = 100;
// Load image for corner.
fabric.util.loadImage('http://i.imgur.com/w41HYN3.png', function (img) {
// Set image and options for top left corner.
frameCornerTLImage.setElement(img);
frameCornerTLImage.setOptions({
left: leftOffset,
top: topOffset
});
// Load image for side.
fabric.util.loadImage('http://i.imgur.com/3VqKv1O.png', function (img) {
frameSideTopImage.setElement(img);
// Create canvas for pattern.
var canvasForFill = new fabric.StaticCanvas(fabric.util.createCanvasElement(), {enableRetinaScaling: false});
canvasForFill.add(frameSideTopImage);
// Configure top side for the frame.
sideObj.setOptions({
objectCaching: false,
originX: 'left',
originY: 'top',
left: imageSize + 100,
top: 100,
width: 800,
strokeWidth:0,
height: imageSize,
fill: new fabric.Pattern({
source: function () {
canvasForFill.setDimensions({
width: imageSize,
height: imageSize
});
return canvasForFill.getElement();
},
repeat: 'repeat-x'
})
});
var canvas = new fabric.Canvas('c');
canvas.add(sideObj);
canvas.add(frameCornerTLImage);
canvas.renderAll();
});
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.7/fabric.js"></script>
<body>
<canvas id="c" width="1000" height="1000"></canvas>
</body>
set strokeWidth:0 , strokeWidth is by default 1, so it creates a border of width 1 .

Mask two images using fabric js

Is it possible to mask two images using fabric js?
For example: I have a colored shape(heart). I have another images and I want to mask it over heart such that that new image is only displayed as heart. I can move the second uploaded image around and resize to get the desired area in that heart.
Both images are user uploaded images and not one of those rectangles or square which are created using fabric and drag and resize image like in the jsfiddle example here.
http://jsfiddle.net/Jagi/efmbrm4v/1/
var canvas = new fabric.Canvas('myCanvas');
var clippingRect = new fabric.Rect({
left: 0,
top: 0,
width: 100,
height: 100,
fill: 'transparent',
opacity: 1
});
canvas.add(clippingRect);
function handleImage(e) {
var reader = new FileReader();
reader.onload = function (event) {
var img = new Image();
img.onload = function () {
var instanceWidth, instanceHeight;
instanceWidth = img.width;
instanceHeight = img.height;
var imgInstance = new fabric.Image(img, {
width: instanceWidth,
height: instanceHeight,
top: (canvas.getHeight() / 2 - instanceHeight / 2),
left: (canvas.getWidth() / 2 - instanceWidth / 2),
originX: 'left',
originY: 'top'
});
canvas.add(imgInstance);
imgInstance.clipTo = function (ctx) {
ctx.save();
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.rect(
100, 100,
200, 200
);
ctx.restore();
};
canvas.renderAll();
};
img.src = event.target.result;
};
reader.readAsDataURL(e.target.files[0]);
}
I need exactly this but with two custom images(user uploaded images).
Maybe this pseudo example will give you pointer how to add mask with a path, not just a rectangle.
//adding main image and mask
fabric.Image.fromURL(IMAGE_URL_HERE, function(pic) {
canvas.add(pic);
//positioning image, if needed
pic.centerH().setCoords();
//adding path, may be a heart or any other in your case
var path = new fabric.Path('M1.398,84.129c3.305,20.461,9.73,50.635,13.591,67.385c2.354,10.212,12.549,23.734,18.51,30.02c4.923,5.191,27.513,23.343,38.34,27.589c18.604,7.295,33.984,4.187,46.012-8.306c12.028-12.493,25.595-34.78,27.954-43.994c2.587-10.105,5.065-26.842,4.313-37.243c-1.036-14.316-14.224-69.332-16.806-79.55c-4.48-17.735-26.246-48.821-80.609-37.668C-1.66,13.514-1.879,63.844,1.398,84.129z');
//positioning path, if needed
path.set({
originX: 'center',
originY: 'center',
});
//masking img with path
pic.set({
clipTo: function(ctx) {
path.render(ctx);
}
});
canvas.renderAll();
});
Full demo (for slightly another case though) here: http://jsfiddle.net/mqL55m0f/2/

Categories

Resources