I am using this function to define my fabricJS canvas background image:
$scope.bgImage = function () {
const {
ipcRenderer
} = require('electron')
ipcRenderer.send('openFile', () => {
})
ipcRenderer.once('fileData', (event, filepath) => {
fabric.Image.fromURL(filepath, function (image) {
var hCent = canvas.getHeight / 2;
var wCent = canvas.getWidth / 2;
canvas.setBackgroundColor({
source: filepath,
top: hCent,
left: wCent,
originX: 'center',
originY: 'middle',
repeat: 'no-repeat',
scaleX: 10,
scaleY: 10
}, canvas.renderAll.bind(canvas));
})
})
};
All the parameters shoulkd be change on the fly with some html inputs. Setting the repeat is working fine and i can change it dynamically.
But I am simply not able to change the scale of my background image. Using the above function I would expect that my background image would be:
would be not repeated (ok. is working)
positioned in the middle/center of my canvas (it is not)
would be scaled by a factor of 10 (it is not)
I am making again a newbish mistake? Or ist this realted to some changes in image handling in FabricJS v.2
canvas.setBackgroundImage() Use this function to add Image as a background.
To get Height/Width of canvas use canvas.getHeight()/canvas.getWidth(),these are functions. Or else you can use the properties > canvas.height/canvas.width respectively.
DEMO
// initialize fabric canvas and assign to global windows object for debug
var canvas = window._canvas = new fabric.Canvas('c');
var hCent = canvas.getHeight() / 2;
var wCent = canvas.getWidth() / 2;
canvas.setBackgroundImage('http://fabricjs.com/assets/jail_cell_bars.png', canvas.renderAll.bind(canvas), {
originX: 'center',
originY: 'center',
left: wCent,
top: hCent,
scaleX: 1,
scaleY: 1
});
fabric.util.addListener(document.getElementById('toggle-scaleX'), 'click', function () {
if (!canvas.backgroundImage) return;
canvas.backgroundImage.scaleX = canvas.backgroundImage.scaleX < 1 ? 1 : fabric.util.toFixed(Math.random(), 2);
canvas.renderAll();
});
fabric.util.addListener(document.getElementById('toggle-scaleY'), 'click', function () {
if (!canvas.backgroundImage) return;
canvas.backgroundImage.scaleY = canvas.backgroundImage.scaleY < 1 ? 1 : fabric.util.toFixed(Math.random(), 2);
canvas.renderAll();
});
canvas {
border: 1px solid #999;
}
button {
margin-top: 20px;
}
<script src="https://rawgithub.com/kangax/fabric.js/master/dist/fabric.js"></script>
<canvas id="c" width="600" height="600"></canvas>
<button id="toggle-scaleX">Toggle scaleX</button>
<button id="toggle-scaleY">Toggle scaleY</button>
Related
I'm experiencing an issue when scaling an image inside a group the offset of the image changes. The group consists of a background (Rect) and an image. When scaling the group the offset if the image becomes different. Expected is that the offset remains the same as the background.
I'm using Fabric 4.0.0-beta.6
const canvas = new fabric.Canvas('canvas');
canvas.setWidth(document.body.clientWidth);
canvas.setHeight(document.body.clientHeight);
const bg = new fabric.Rect({
width: 100,
height: 100,
fill: 'red',
});
fabric.Image.fromURL("https://dummyimage.com/300x150/000/fff", function(img) {
img.scaleToWidth(bg.width)
const post = new fabric.Group([bg, img], {
left: 100,
top: 100,
});
post.scaleToWidth(400);
canvas.add(post);
canvas.renderAll();
});
https://jsbin.com/migogekoxu/1/edit?html,css,js,output
Setting the strokeWidth property of the background element to 0 fixed the offset for me.
const canvas = new fabric.Canvas('canvas');
canvas.setWidth(document.body.clientWidth);
canvas.setHeight(document.body.clientHeight);
const bg = new fabric.Rect({
width: 100,
height: 100,
fill: 'red',
strokeWidth: 0, //<---
});
fabric.Image.fromURL("https://dummyimage.com/300x150/000/fff", function(img) {
img.scaleToWidth(bg.width)
const post = new fabric.Group([bg, img], {
left: 100,
top: 100,
});
post.scaleToWidth(400);
canvas.add(post);
canvas.renderAll();
});
<canvas id="canvas"></canvas>
<script src="https://cdn.jsdelivr.net/npm/fabric#4.0.0-beta.6-browser/dist/fabric.min.js"></script>
Obviously that's a bug with fabricJS. If you don't want to fix it on your own in it's source code you can workaround it however.
You can manually correct the position by adding an offset of 0.5 to the image's top and left properties.
For example:
const canvas = new fabric.Canvas('canvas');
const bg = new fabric.Rect({
width: 100,
height: 100,
fill: 'red'
});
fabric.Image.fromURL("https://dummyimage.com/300x150/000/fff", function(img) {
img.scaleToWidth(bg.width);
img.left += 0.5;
img.top += 0.5;
const post = new fabric.Group([bg, img], {
left: 100,
top: 100
});
post.scaleToWidth(400);
canvas.add(post);
canvas.renderAll();
});
<script src="https://cdn.jsdelivr.net/npm/fabric#4.0.0-beta.6-browser/dist/fabric.min.js"></script>
<canvas id="canvas" width="600" height="400"></canvas>
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 .
I am having issues getting drag, drop and delete to work on a FabricJS using JQuery.
Select an object and click delete is working with no issues.
Here's a demo of my code Fiddle
I can get drag, drop and delete to work outside of FabricJS (Just dragging unrelated elements to delete) but I'm having difficulty combing the two.
Maybe I need to incorporate the delete button inside the canvas in order for the 'drop' to work? When I try this the #delete element disappears.
JS
var canvas = new fabric.Canvas('canvas');
$("#delete").click(function() {
deleteObjects();
});
//Test objects on the canvas
//Circle
var circle = new fabric.Circle({
left: 200,
top: 150,
radius: 30,
fill: "#ff0072"
});
circle.hasRotatingPoint = true;
canvas.add(circle);
// adding triangle
var triangle = new fabric.Triangle({
left: 130,
top: 150,
strokeWidth: 1,
width: 70,
height: 60,
fill: '#00b4ff',
selectable: true,
originX: 'center'
});
triangle.hasRotatingPoint = true;
canvas.add(triangle);
//Select all objects
function deleteObjects() {
var activeObject = canvas.getActiveObject(),
activeGroup = canvas.getActiveGroup();
if (activeObject) {
canvas.remove(activeObject);
}
}
//Drag and drop delete
$(function() {
$('#delete').droppable({
drop: function(event, ui) {
deleteObjects();
}
});
});
HTML
<section class="canvas__container">
<canvas id="canvas" width="400" height="400"></canvas>
<div id="delete">♻</div>
</section>
(I didn't paste my CSS as that's not particularly relevant)
This should get you close:
var bin;
var selectedObject;
fabric.Image.fromURL('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADUAAAA1CAIAAABuhDQnAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAPPSURBVGhDzZd9U+IwEIfv+3+2Gxln9PAEcYRBURQVkVeFe9rdC2laIFsK+Pzh2Cab/ZF9Sfpr9bMpr+/r6+v5+fn+/v5vyuXl5e8U/uGx2Wwy9Pr6yjQ1KIVZ32w2e3x8xL2oiYHJmGCoS1gw6MPB7e2t+iwF5laVUfqIEcGq1WrqZw9YhKXig75bHzlUiTIfFmRZdbCVHfrIG13yALC4utnMRn2EYM9siwEX22NdrA8b1y8ODY62SCzWd4Sd88GdOs5RoO+gObeJTbkY6qOs1OLoFFZ0Rh95UHkriQfX+UTM6Ot2uzr3RCBApfxnrY+T54SbJyAgOADX+krX7NnZGTeAm5ub8/NzfbUHQS2rPlTruJFOp7NYLGQR+P7+5vHz8/Pl5eX6+lonGfG3UPWV6ykPDw/L5VJWyIPQcjHxe43qM93nBEz8nSvk4+OD6KtBNKys9qKPqtYRC3d3d/P5XFYRptMpBei/ZGXyUg0suEaT6OOarq/toHI8HrPIZDLhWn91dTUajdKVE4g+tz2dasH16kSfte1Rp29vb71e7+LiQt78SeFRtPqQ7ExmVGZG4hphoo8fra+jQRx7A5QqJeySjJoNgs5jueQW8zL6Wq0WqSb2wnA41LG0gbm6YRqKUW+9rSFJVkj0mYxJsqDFs4s0Gh1OISlJcGJdr9cRR3ytvQZJsniiT9/FQeMVSwe+86240WiQjuDKxXozEiuzPupULB00OR3Lgmg/DTA09UKxMsfX10fuE8cguA6izHGnU9PJ5IaO7SITX1N9uPjijx1CXKFXCpDR4MfEO8rUh6n+3a5QFqQUGSbvaYrtdnswGBBThqghioO6Tr0kUCUyM4ZMfzH1Z3IoSEGksFVPT0/6nELcqQz2TB4R7X5JDJn+bD3fAilsJx0xyDYftDKqxnFkzjfs9XUcHPmYiD0QTQ5ZttDtVkC/31fLaNz6iT6wHkFsuRgKoiB/+ML7+7uprYBLPlB91vspLjFxv1J6L3/JRU5kV0C0RlPaCQX3U9bVQSOcEySftBguCpSwvN8H//xUfVDuLl45yFBBKWt9qP4J35d+5cFaH1gvqpXj2p4jow/tJ9zC/OZBRh9Yb0EV4nqyT6gPyn0L74nfU3wK9MGRazmoWZ9ifeSB9YuhNDjKp52jWB9gc4RdxMUWcbBRn3DQXNyUcz479AFlVXnTYcHCas2zWx8QAjpnJSpZhKW2x9QnSp/AAbhnRmLun/0xGPQJOCBvTPdFJmNiVSaY9TmIETlEsHDP55brR/zDIy8ZYkJ8KAspr+84/Gx9q9U/+FfCZwDz5ogAAAAASUVORK5CYII=', function(img) {
img.set({
left: 174,
top: 329,
selectable: false
});
bin = img;
canvas.add(img, circle, triangle);
});
canvas.on('object:selected', function(evn) {
selectedObject = evn.target;
})
canvas.on('mouse:up', function(evn) {
var x = evn.e.offsetX;
var y = evn.e.offsetY;
if (x > bin.left && x < (bin.left + bin.width) &&
y > bin.top && y < (bin.top + bin.height)) {
canvas.remove(selectedObject);
}
});
Here's your JSFiddle updated, https://jsfiddle.net/rekrah/jgruwse0/.
Let me know if you have any further questions.
Say I have a HTML5 canvas (In this case using fabric.js), and I want to change the cursor over the canvas to represent whatever brush size and colour has been selected. I'm thinking there should be a way to do this by changing an SVG's properties (size & colour) dynamically with JS so we don't have to use multiple images. Any ideas on if this is possible?
var canvas = new fabric.Canvas(c, {
isDrawingMode: true,
freeDrawingCursor: 'url("img/cursor.svg"), auto'
});
I think freeDrawingCursor is just looking for normal css property names. Here is an example of how to have a fabric object represent the cursor size and color:
var canvas = new fabric.Canvas('c', {
isDrawingMode: true,
freeDrawingCursor: 'none'
});
canvas.freeDrawingBrush.width = 10;
canvas.freeDrawingBrush.color = '#9f9';
var mousecursor = new fabric.Circle({
left: 0,
top: 0,
radius: canvas.freeDrawingBrush.width / 2,
fill: canvas.freeDrawingBrush.color,
originX: 'right',
originY: 'bottom',
})
canvas.add(mousecursor);
canvas.on('mouse:move', function(obj) {
mousecursor.top = obj.e.y - mousecursor.radius;
mousecursor.left = obj.e.x - mousecursor.radius;
canvas.renderAll()
})
canvas.on('mouse:out', function(obj) {
// put circle off screen
mousecursor.top = -100;
mousecursor.left = -100;
canvas.renderAll()
})
canvas {
border: 1px solid #ccc;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.6.3/fabric.js"></script>
<canvas id="c" width="600" height="600"></canvas>
Existing answers didn't work for me in 2020. Try the following:
this.canvas.isDrawingMode = true;
this.canvas.freeDrawingCursor = 'none';
const mousecursor = new fabric.Circle({
left: 0,
top: 0,
radius: this.canvas.freeDrawingBrush.width / 2,
fill: this.canvas.freeDrawingBrush.color,
originX: 'right',
originY: 'bottom',
});
this.canvas.add(mousecursor);
// Cursor in canvas
this.canvas.on('mouse:move', event => {
mousecursor.top = event.e.layerY + mousecursor.radius;
mousecursor.left = event.e.layerX + mousecursor.radius;
this.canvas.renderAll();
});
// Cursor out of canvas
this.canvas.on('mouse:out', event => {
mousecursor.top = -100;
mousecursor.left = -100;
this.canvas.renderAll();
});
Worked fine with Angular 7.
I was looking for the same and found my way to this question. Unfortunately the solution by STHayden is not working for me. So I've modified it a bit and came up with the code below. It uses two canvas layers, the bottom for drawing, the top for the "cursor". Works pretty well for me now, maybe someone will find it helpful :)
//drawing layer
var canvas = new fabric.Canvas("draw", {
isDrawingMode: true,
freeDrawingCursor: 'none'
});
//mouse cursor layer
var cursor = new fabric.StaticCanvas("cursor");
canvas.freeDrawingBrush.width = 20;
canvas.freeDrawingBrush.color = '#ff0000';
var cursorOpacity = .5;
//create cursor and place it off screen
var mousecursor = new fabric.Circle({
left: -100,
top: -100,
radius: canvas.freeDrawingBrush.width / 2,
fill: "rgba(255,0,0," + cursorOpacity + ")",
stroke: "black",
originX: 'center',
originY: 'center'
});
cursor.add(mousecursor);
//redraw cursor on new mouse position when moved
canvas.on('mouse:move', function (evt) {
var mouse = this.getPointer(evt.e);
mousecursor
.set({
top: mouse.y,
left: mouse.x
})
.setCoords()
.canvas.renderAll();
});
//put cursor off screen again when mouse is leaving
canvas.on('mouse:out', function () {
mousecursor
.set({
top: mousecursor.originalState.top,
left: mousecursor.originalState.left
})
.setCoords()
.canvas.renderAll();
});
//while brush size is changed show cursor in center of canvas
document.getElementById("size").oninput = function () {
var size = parseInt(this.value, 10);
mousecursor
.center()
.set({
radius: size/2
})
.setCoords()
.canvas.renderAll();
};
//after brush size has been changed move offscreen, update brush size
document.getElementById("size").onchange = function () {
var size = parseInt(this.value, 10);
canvas.freeDrawingBrush.width = size;
mousecursor
.set({
left: mousecursor.originalState.left,
top: mousecursor.originalState.top,
radius: size/2
})
.setCoords()
.canvas.renderAll();
};
//change mousecursor opacity
document.getElementById("opacity").onchange = function () {
cursorOpacity = this.value;
var fill = mousecursor.fill.split(",");
fill[fill.length-1] = cursorOpacity + ")";
mousecursor.fill = fill.join(",");
};
//change drawing color
document.getElementById("color").onchange = function () {
canvas.freeDrawingBrush.color = this.value;
var bigint = parseInt(this.value.replace("#", ""), 16);
var r = (bigint >> 16) & 255;
var g = (bigint >> 8) & 255;
var b = bigint & 255;
mousecursor.fill = "rgba(" + [r,g,b,cursorOpacity].join(",") + ")";
};
#cont {
position: relative;
width: 500px;
height: 500px;
}
canvas {
border: 1px solid;
}
#cont canvas, .canvas-container {
position: absolute!important;
left: 0!important;
top: 0!important;
width: 100%!important;
height: 100%!important;
}
#cursor {
pointer-events: none!important;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.6.4/fabric.min.js"></script>
Color: <input id="color" type="color" value="#ff0000"><br/>
Brush size: <input id="size" type="range" min="1" max="100" step="1" value="20"><br/>
Brush opacity: <input id="opacity" type="number" min="0" max="1" step="0.1" value="0.5"><br/>
<div id="cont">
<canvas id="draw" width="500" height="500"></canvas>
<canvas id="cursor" width="500" height="500"></canvas>
</div>
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/