Resizing Objects in Fabric.js - javascript

I'm planning on using Frabic.js to build a room layout. It's based around a grid layout (grid size: 25) with the objects snapping into position.
I'm trying to override resize events to make the width a multiple of the grid size (25). However it seems to be randomly resizing the the element.
Code
var canvas = new fabric.Canvas('section', { selection: true }); fabric.Object.prototype.transparentCorners = false;
var canvasWidth = 600;
var canvasGrid = 25;
canvas.on('object:modified', function(options) {
options.target.set({opacity: 1}); // Return the opacity back to 1 after moving
var newWidth = (Math.round(options.target.getWidth() / canvasGrid)) * canvasGrid;
console.log("Width:" + options.target.getWidth());
console.log("Desired width: " + newWidth);
if(options.target.getWidth() !== newWidth) {
console.log("alter the width");
options.target.set({ width: newWidth });
console.log("Width changed to: " + options.target.getWidth());
}
console.log("Final width: " + options.target.getWidth());
});
The objects are added on the fly by the end user, the code for it is below.
$("#addBlock").click(function(e){
e.preventDefault();
var block = new fabric.Rect({
left: canvasGrid,
top: canvasGrid,
width: canvasGrid,
height: canvasGrid,
fill: 'rgb(127, 140, 141)',
originX: 'left',
originY: 'top',
centeredRotation: false,
lockScalingX: false,
lockScalingY: false,
lockRotation: false,
hasControls: true,
cornerSize: 8,
hasBorders: false,
padding: 0
});
canvas.add(block);
});
Console Output
Width:170
Desired width: 175
alter the width
Width changed to: 238.00000000000003
Final width: 238.00000000000003
The desired width is what I would like the shapes/blocks to be resized to rather than it's final width.

Probably you can solve resetting the scale factor of your modified object:
options.target.set({ width: newWidth, height: newHeight, scaleX: 1, scaleY: 1, });
try yourself in the snippet below if can fit for your needs:
$(function () {
var canvas = new fabric.Canvas('canvas', { selection: true }); fabric.Object.prototype.transparentCorners = false;
var canvasWidth = 600;
var canvasGrid = 50;
canvas.on('object:modified', function (options) {
options.target.set({ opacity: 1 });
var newWidth = (Math.round(options.target.getWidth() / canvasGrid)) * canvasGrid;
var newHeight = (Math.round(options.target.getHeight() / canvasGrid)) * canvasGrid;
if (options.target.getWidth() !== newWidth) {
options.target.set({ width: newWidth, height: newHeight, scaleX: 1, scaleY: 1, });
}
});
$("#addBlock").click(function (e) {
e.preventDefault();
var block = new fabric.Rect({
left: canvasGrid,
top: canvasGrid,
width: canvasGrid,
height: canvasGrid,
fill: 'rgb(127, 140, 141)',
originX: 'left',
originY: 'top',
centeredRotation: false,
lockScalingX: false,
lockScalingY: false,
lockRotation: false,
hasControls: true,
cornerSize: 8,
hasBorders: false,
padding: 0
});
canvas.add(block);
});
});
canvas {
border: 1px dashed #333;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.5.0/fabric.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<canvas id="canvas" width="500" height="500"></canvas>
<input type="button" id="addBlock" value="add" />

Related

Can't get overlay to resize fabric.js?

I've been able to get the canvas to resize, and also include touch events. But for some reason, I can't get the overlay image to fill the canvas.
I'm using the recommended code from fabric.js
canvas.setOverlayImage('http://fabricjs.com/assets/jail_cell_bars.png', canvas.renderAll.bind(canvas), {
width: width,
height: height,
// Needed to position overlayImage at 0/0
originX: 'left',
originY: 'top'
});
My Fiddle has a fair bit of code, as I'm not sure if it's the resizer or touch events that might be causing this issue.
The problem in your fiddle stems from the fact that in Fabric.js, setting width and height don't scale an image to the given values but rather set the dimensions of the fabric.Image container. What you need to do is call canvas.overlayImage.scaleToWidth(width) (or .scaleToHeight(height)) in the setOverlayImage()'s callback, so that the image would actually be scaled to cover the whole canvas:
//Image Edit
var canvas = new fabric.Canvas('c', {
preserveObjectStacking: true
});
canvas.setWidth(window.innerWidth);
canvas.setHeight(window.innerWidth);
fabric.Image.fromURL("https://4.img-dpreview.com/files/p/E~TS590x0~articles/3925134721/0266554465.jpeg", function(img) {
img.scale(0.5).set({
left: 150,
top: 150,
angle: 0
});
canvas.add(img).setActiveObject(img);
});
var info = document.getElementById('info');
function resize() {
var canvasSizer = document.getElementById("imageEditor");
var canvasScaleFactor = canvasSizer.offsetWidth / 525;
var width = canvasSizer.offsetWidth;
var height = canvasSizer.offsetHeight;
var ratio = canvas.getWidth() / canvas.getHeight();
if ((width / height) > ratio) {
width = height * ratio;
} else {
height = width / ratio;
}
var scale = width / canvas.getWidth();
var zoom = canvas.getZoom();
zoom *= scale;
canvas.setDimensions({
width: width,
height: height
});
canvas.setViewportTransform([zoom, 0, 0, zoom, 0, 0])
canvas.setOverlayImage('https://i.imgur.com/1CURvP5.png', function() {
canvas.overlayImage && canvas.overlayImage.scaleToWidth(width)
canvas.renderAll()
}, {
// Needed to position overlayImage at 0/0
originX: 'left',
originY: 'top'
});
}
window.addEventListener('load', resize, false);
window.addEventListener('resize', resize, false);
var textObj = new fabric.IText("Test Text", {
fontSize: 22,
top: 362.5,
left: 262.5,
hasControls: true,
fontWeight: 'bold',
fontFamily: 'Montserrat',
fontStyle: 'normal',
centeredrotation: true,
originX: 'center',
originY: 'center'
});
canvas.add(textObj);
canvas.renderAll();
canvas.on({
'touch:gesture': function() {
var text = document.createTextNode(' Gesture ');
info.insertBefore(text, info.firstChild);
},
'touch:drag': function() {
var text = document.createTextNode(' Dragging ');
info.insertBefore(text, info.firstChild);
},
'touch:orientation': function() {
var text = document.createTextNode(' Orientation ');
info.insertBefore(text, info.firstChild);
},
'touch:shake': function() {
var text = document.createTextNode(' Shaking ');
info.insertBefore(text, info.firstChild);
},
'touch:longpress': function() {
var text = document.createTextNode(' Longpress ');
info.insertBefore(text, info.firstChild);
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/2.4.4/fabric.js"></script>
<div id="imageEditor">
<div class="canvas-container">
<canvas id="c"></canvas>
</div>
</div>
(I had to re-upload the jail_cell_bars.png image elsewhere because the original hosting was not cooperating)

How can i able to move image beneath rectangle in canvas?

I could select the background image from inside rectangle, but not able to move.
To move the image, i should go outside of the rectangle.
I know that the rectangle is created over the image, I'm looking for any option to move the image as it is selectable from inside rectangle.
My canvas screenshot
This is how i set the image and rectangle.
changeImage(innercanvasHeight, innercanvasWidth) {
const base_image = new Image();
base_image.crossOrigin = 'Anonymous';
base_image.src = 'assets/images/1wal.jpg';
fabric.Image.fromURL(base_image.src, (myImg) => {
const img1 = myImg.set({left: 160, top: 80, width: 600, height:
400, id: 'wallpaper'});
this.FabriCanvas.add(img1).setActiveObject(img1);
const hiddenImg = document.createElement('img');
hiddenImg.src = this.FabriCanvas.getActiveObject().toDataURL();
hiddenImg.id = 'target';
hiddenImg.style.display = 'none';
document.body.appendChild(hiddenImg);
this.innerCanvas(innercanvasHeight, innercanvasWidth);
});
innerCanvas(height, width) {
this.innercanvas = this.FabriCanvas.add(new fabric.Rect({
left: 160,
top: 80,
id: 'innerCan',
fill: 'transparent',
stroke: '#fff',
strokeWidth: 1,
width: width,
height: height,
selectable: false
}));
this.FabriCanvas.renderAll();
}
Use preserveObjectStacking so it wont come up while dragging, and use perPixelTargetFind to click through the object if it is transparent.
DEMO
var canvas = new fabric.Canvas('canvas',{
preserveObjectStacking: true
});
var image = new fabric.Image('');
var rect = new fabric.Rect({
left: 160,
top: 80,
id: 'innerCan',
fill: 'transparent',
stroke: '#fff',
strokeWidth: 1,
width: 100,
height: 100,
selectable: false,
perPixelTargetFind : true
});
canvas.add(image,rect);
image.setSrc('//fabricjs.com/assets/pug.jpg',function(img){
img.set({ scaleX:canvas.width/img.width,scaleY: canvas.height/img.height});
img.setCoords();
canvas.renderAll();
})
canvas{
border: 2px solid #000;
}
<script src="https://rawgit.com/kangax/fabric.js/master/dist/fabric.js"></script>
<canvas id='canvas' width=300 height=300></canvas>

FabricJS FlipX object

I am having trouble flipping or mirroring the object horizontally when the object itself is clicked on the FabricJS canvas.
I came close but it was mirroring the object when it was being resized too, which I didn't want.
I would guess I need to add the 'flipX: true' attribute to object on the first click and on the next click remove that attribute and so on with each click. Or maybe that is over complicating it and it can be done much easier with a flipX function I do not know.
I did find a Fiddle that flipped the object, but it was onclick of a button not the object itself.
I am struggling to solve this :\
My Fiddle
HTML:
<canvas id="canvas" width="400" height="300"></canvas>
JS:
var canvas = this.__canvas = new fabric.Canvas('canvas');
canvas.on('object:selected', function() {
toggle('flipX');
});
// create a rectangle
var rect = new fabric.Rect({
left: 50,
top: 50,
width: 100,
height: 50,
angle: 20,
fill: 'red'
});
canvas.add(rect);
canvas.renderAll();
You could accomplish that in the following way ...
var canvas = this.__canvas = new fabric.Canvas('canvas');
// mouse event
canvas.on('mouse:down', function(e) {
if (e.target) {
if (!e.target.__corner) {
e.target.toggle('flipX');
canvas.renderAll();
}
e.target.__corner = null;
}
});
// create a rectangle
var rect = new fabric.Rect({
left: 50,
top: 50,
width: 100,
height: 50,
angle: 20,
});
// set gradient (for demonstration)
rect.setGradient('fill', {
type: 'linear',
x1: -rect.width / 2,
y1: 0,
x2: rect.width / 2,
y2: 0,
colorStops: {
0: '#ffe47b',
1: 'rgb(111,154,211)'
}
});
canvas.add(rect);
rect.set('flipX', true);
canvas.renderAll();
body{margin:10px 0 0 0;overflow:hidden}canvas{border:1px solid #ccc}
<script src="http://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.4.0/fabric.min.js"></script>
<canvas id="canvas" width="208" height="208"></canvas>
Same from above, different case.
Apply flip to background image
fabric.Image.fromURL('../' +ImageUrl, function (img02) {
Backcanvas.setBackgroundImage(img02, Backcanvas.renderAll.bind(Backcanvas), {
backgroundImageStretch: false,
top: 0,
left: 0,
originX: 'left',
originY: 'top',
flipY:'true'
});
Backcanvas.renderAll();
Backcanvas.backgroundImage.setCoords();
canvas.renderAll();
Backcanvas.renderAll();
}, { crossOrigin: 'anonymous' });

Sending multiple objects forward changes their order (z-index)

The following snippet has a green square above a red square
Select both squares by dragging over them.
Click the bring forward button
After clicking bring forward the squares have switched order. It is my understanding that the items should stay in the same order, but be moved increasingly above other non-selected items as the button is further clicked.
If you deselect, and repeat the experiment you will see that they switch again.
Any ideas?
var canvas = new fabric.Canvas('c',
{
preserveObjectStacking : true
});
var rect = new fabric.Rect({
left: 10, top: 10,
fill: 'red',
width: 100, height: 100,
hasControls: true
});
canvas.add(rect);
var rect2 = new fabric.Rect({
left: 40, top: 40,
fill: 'green',
width: 100, height: 100,
hasControls: true
});
canvas.add(rect2);
$("#bringForward").click(function()
{
var items = canvas.getActiveObject() || canvas.getActiveGroup();
if(items)
items.bringForward();
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.6/fabric.min.js"></script>
<button id="bringForward">Bring Forward</button>
<canvas id="c" width="640" height="480"></canvas>
This can be considered a bug or not, depending on what do you expect the function to do.
The documentation for the feature says:
Moves an object or a selection up in stack of drawn objects
And is actually doing so.
The object on top cannot go more on top, the one under can and goes.
Still for a dev this can look like a weird behaviour, to me not really. But guess is personal.
Here is your widget with a modified snippet to try a better solution.
var removeFromArray = fabric.util.removeFromArray;
// modified function to avoid snapping
fabric.StaticCanvas.prototype.bringForward = function (object, intersecting) {
if (!object) {
return this;
}
var activeGroup = this._activeGroup,
i, obj, idx, newIdx, objs, latestIndex;
if (object === activeGroup) {
objs = activeGroup._objects;
latestIndex = this._objects.length;
for (i = objs.length; i--;) {
obj = objs[i];
idx = this._objects.indexOf(obj);
if (idx !== this._objects.length - 1 && idx < latestIndex - 1) {
newIdx = idx + 1;
latestIndex = newIdx;
removeFromArray(this._objects, obj);
this._objects.splice(newIdx, 0, obj);
} else {
latestIndex = idx;
}
}
}
else {
idx = this._objects.indexOf(object);
if (idx !== this._objects.length - 1) {
// if object is not on top of stack (last item in an array)
newIdx = this._findNewUpperIndex(object, idx, intersecting);
removeFromArray(this._objects, object);
this._objects.splice(newIdx, 0, object);
}
}
this.renderAll && this.renderAll();
return this;
};
var canvas = new fabric.Canvas('c',
{
preserveObjectStacking : true
});
var rect = new fabric.Rect({
left: 10, top: 10,
fill: 'red',
width: 100, height: 100,
hasControls: true
});
canvas.add(rect);
var rect2 = new fabric.Rect({
left: 40, top: 40,
fill: 'green',
width: 100, height: 100,
hasControls: true
});
canvas.add(rect2);
var rect3 = new fabric.Rect({
left: 70, top: 70,
fill: 'blue',
width: 100, height: 100,
hasControls: true
});
canvas.add(rect3);
var rect4 = new fabric.Rect({
left: 100, top: 100,
fill: 'orange',
width: 100, height: 100,
hasControls: true
});
canvas.add(rect4);
$("#bringForward").click(function()
{
var items = canvas.getActiveObject() || canvas.getActiveGroup();
if(items)
items.bringForward();
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.6/fabric.min.js"></script>
<button id="bringForward">Bring Forward</button>
<canvas id="c" width="640" height="480"></canvas>

Pixelated image in Fabric.js with sliceHack Resize filter

I can't find a way to use the Resize filter (slideHack) without getting a pixelated image sometimes.
I use fabric.js 1.7.2
I just added the image in the canvas
fabric.Image.fromURL(url, function(oImg)
{
var scaling = 0.2;
var rFilter = new fabric.Image.filters.Resize({
resizeType: 'sliceHack'
});
oImg.resizeFilters.push(rFilter);
oImg.applyFilters();
oImg.set({
left: 300,
top: 300,
scaleX: scaling,
scaleY: scaling
});
canvas.add(oImg);
canvas.renderAll();
});
When I click on the image or resize it manually, the edges get smooth.
When I apply a Tint Filter, it is pixelated again
I can't find the function triggered to smooth the edges...
Thanks for your help.
The document Introduction to Fabric.js, Part 2 suggests the following syntax, which seems to solve the issue:
fabric.Image.fromURL('pug.jpg', function(img) {
// add filter
img.filters.push(filter);
// apply filters and re-render canvas when done
img.applyFilters(canvas.renderAll.bind(canvas));
// add image onto canvas
canvas.add(img);
});
The result is compared to your original method in the code snippet below. In order to make it work, I also added { crossOrigin: 'Anonymous' } (see the comments to this answer by AndreaBogazzi).
var canvas = new fabric.Canvas('c');
var ctx = canvas.getContext("2d");
var url = 'http://i.imgur.com/a47Yxsb.png';
var imgWidth = 770;
function performScaling() {
// Get scaling factor
var scaling = parseFloat(document.getElementById("txtScaling").value);
canvas.clear();
// With original method
fabric.Image.fromURL(url, function(oImg)
{
var rFilter = new fabric.Image.filters.Resize({
resizeType: 'sliceHack'
});
oImg.resizeFilters.push(rFilter);
oImg.applyFilters();
oImg.set({
left: 0,
top: 16,
scaleX: scaling,
scaleY: scaling
});
canvas.add(oImg);
canvas.renderAll();
});
// As suggested in Fabric.js introduction
fabric.Image.fromURL(url, function (oImg) {
oImg.filters.push(new fabric.Image.filters.Resize({
resizeType: 'sliceHack', scaleX: scaling , scaleY: scaling
}));
oImg.set({
left: imgWidth * 1.1 * scaling,
top: 16
});
oImg.applyFilters(canvas.renderAll.bind(canvas));
canvas.add(oImg);
},{ crossOrigin: 'Anonymous' });
// Add labels
canvas.add(new fabric.Text('Pixelated', {
fontFamily: 'Arial',
fontSize: 12,
left: 0.48 * imgWidth * scaling,
top: 0,
fill: 'black',
originX: 'center'
}));
canvas.add(new fabric.Text('Not pixelated', {
fontFamily: "Arial",
fontSize: 12,
left: 1.58 * imgWidth * scaling,
top: 0,
fill: 'black',
originX: 'center'
}));
}
#divScaling
{
display: inline-block;
vertical-align: middle;
margin: 6px 32px 16px 0px;
vertical-align: top;
}
#txtScaling
{
width: 70px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.2/fabric.min.js"></script>
<div id="divScaling">
<label for="txtScaling">Scaling factor: </label>
<input id="txtScaling" type="text" value="0.2" />
<button onclick="performScaling()">
Scale
</button>
</div>
<img src="http://i.imgur.com/a47Yxsb.png" style="width: 30px;height: 30px;"/>
<canvas id="c" width="600" height="400"></canvas>
I think that pixelated problem was fixed in version 2. Edge was smoother than version 1.72
Notice breaking changes in v2:
Another breaking change is that the .resizeFilters is no more an array of resize filter. Is a single resizeFilter that you can use when the object is scaled on the canvas.
image.resizeFilter = new fabric.Image.filters.ResizeFilter({type: 'hermite'});
var canvas = new fabric.Canvas('c');
var ctx = canvas.getContext("2d");
var url = 'http://i.imgur.com/a47Yxsb.png';
var imgWidth = 1024;
function performScaling() {
// Get scaling factor
var scaling = parseFloat(document.getElementById("txtScaling").value);
canvas.clear();
// As suggested in Fabric.js introduction
fabric.Image.fromURL(url, function (oImg) {
oImg.set({
left: imgWidth * 1.1 * scaling,
top: 16
});
oImg.scale(scaling);
oImg.resizeFilter = new fabric.Image.filters.Resize({
resizeType: 'sliceHack'
});
oImg.applyResizeFilters();
canvas.add(oImg);
canvas.renderAll();
// canvas.setBackgroundImage(
// oImg,
// () => {
// canvas.renderAll();
// },
// );
},{ crossOrigin: 'Anonymous' });
// Add labels
canvas.add(new fabric.Text('Not pixelated', {
fontFamily: "Arial",
fontSize: 12,
left: 1.58 * imgWidth * scaling,
top: 0,
fill: 'black',
originX: 'center'
}));
}
#divScaling
{
display: inline-block;
vertical-align: middle;
margin: 6px 32px 16px 0px;
vertical-align: top;
}
#txtScaling
{
width: 70px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/2.4.4/fabric.min.js"></script>
<div id="divScaling">
<label for="txtScaling">Scaling factor: </label>
<input id="txtScaling" type="text" value="0.2" />
<button onclick="performScaling()">
Scale
</button>
</div>
<img src="http://i.imgur.com/a47Yxsb.png" style="width: 30px;height: 30px;"/>
<canvas id="c" width="600" height="400"></canvas>

Categories