I'm using FabricJS for a project in which I need to be able to set the background image of the canvas. The canvas can be any size and can be resized at any time. Lastly, the image should always fill the canvas regardless of its size, but never be distorted in any way.
So for example, if I have an 800x600 (WxH) canvas and a 1200x700 background image, the image should be scaled down to 1029x600 so that it covers the entire canvas without being distorted.
I've written a function that is supposed to calculate the dimensions of the canvas and image and set the size and position of the image accordingly, but I'm having trouble getting it to work correctly. It works most of the time, but is not "bullet proof". Sometimes the image gets distorted and other times it doesn't fill the entire canvas.
What I would like help with is refactoring this into a bullet-proof solution that will always meet my sizing criteria no matter what size the canvas or image is.
I've created a fiddle with the code. the fiddle loads an 800x600 canvas and then sets first a landscape background image to demonstrate how the code isn't sizing the image to cover the canvas, and then a portrait image to show how it sometimes distorts images.
Here is the code itself:
var canvas = window._canvas = new fabric.Canvas('c'),
canvasOriginalWidth = 800,
canvasOriginalHeight = 600,
canvasWidth = 800,
canvasHeight = 600,
canvasScale = .5,
photoUrlLandscape = 'https://images8.alphacoders.com/292/292379.jpg',
photoUrlPortrait = 'https://presspack.rte.ie/wp-content/blogs.dir/2/files/2015/04/AMC_TWD_Maggie_Portraits_4817_V1.jpg';
setCanvasSize({height: canvasHeight, width: canvasWidth});
setTimeout(function() {
setCanvasBackgroundImageUrl(photoUrlLandscape, 0, 0, 1)
}, 50)
setTimeout(function() {
setCanvasBackgroundImageUrl(photoUrlPortrait, 0, 0, 1)
}, 4000)
function setCanvasSize(canvasSizeObject) {
canvas.setWidth(canvasSizeObject.width);
canvas.setHeight(canvasSizeObject.height);
setZoom();
}
function setZoom() {
setCanvasZoom();
canvas.renderAll();
}
function setCanvasZoom() {
var width = canvasOriginalWidth;
var height = canvasOriginalHeight;
var tempWidth = width * canvasScale;
var tempHeight = height * canvasScale;
canvas.setWidth(tempWidth);
canvas.setHeight(tempHeight);
}
function setCanvasBackgroundImageUrl(url, top, left, opacity) {
if(url && url.length > 0) {
fabric.Image.fromURL(url, function(img) {
var aspect, scale;
if(parseInt(canvasWidth) > parseInt(canvasHeight)) {
if(img.width >= img.height) {
// Landscape canvas, landscape source photo
aspect = img.width / img.height;
if(img.width >= parseInt(canvasWidth)) {
scale = img.width / parseInt(canvasWidth);
} else {
scale = parseInt(canvasWidth) / img.width;
}
img.width = parseInt(canvasWidth)
img.height = img.height / scale;
} else {
// Landscape canvas, portrait source photo
aspect = img.height / img.width;
if(img.width >= parseInt(canvasWidth)) {
scale = img.width / parseInt(canvasWidth);
} else {
scale = parseInt(canvasWidth) / img.width;
}
img.width = parseInt(canvasWidth);
img.height = img.height * scale;
}
} else {
if(img.width >= img.height) {
// Portrait canvas, landscape source photo
aspect = img.width / img.height;
if(img.height >= parseInt(canvasHeight)) {
scale = img.width / parseInt(canvasHeight);
} else {
scale = parseInt(canvasHeight) / img.height;
}
img.width = img.width * scale;
img.height = parseInt(canvasHeight)
} else {
// Portrait canvas, portrait source photo
aspect = img.height / img.width;
if(img.height >= parseInt(canvasHeight)) {
scale = img.height / parseInt(canvasHeight);
} else {
scale = parseInt(canvasHeight) / img.height;
}
img.width = img.width * scale;
img.height = parseInt(canvasHeight);
}
}
canvas.setBackgroundImage(img, canvas.renderAll.bind(canvas), {
top: parseInt(top) || 0,
left: parseInt(left) || 0,
originX: 'left',
originY: 'top',
opacity: opacity ? opacity : 1,
scaleX: canvasScale,
scaleY: canvasScale
});
canvas.renderAll();
setZoom();
});
} else {
canvas.backgroundImage = 0;
canvas.setBackgroundImage('', canvas.renderAll.bind(canvas));
canvas.renderAll();
setZoom();
}
};
Here is a fiddle that does what (I think) you want to achieve:
https://jsfiddle.net/whippet71/7s5obuk2/
The code for scaling the image is fairly straightforward:
function scaleAndPositionImage() {
setCanvasZoom();
var canvasAspect = canvasWidth / canvasHeight;
var imgAspect = bgImage.width / bgImage.height;
var left, top, scaleFactor;
if (canvasAspect >= imgAspect) {
var scaleFactor = canvasWidth / bgImage.width;
left = 0;
top = -((bgImage.height * scaleFactor) - canvasHeight) / 2;
} else {
var scaleFactor = canvasHeight / bgImage.height;
top = 0;
left = -((bgImage.width * scaleFactor) - canvasWidth) / 2;
}
canvas.setBackgroundImage(bgImage, canvas.renderAll.bind(canvas), {
top: top,
left: left,
originX: 'left',
originY: 'top',
scaleX: scaleFactor,
scaleY: scaleFactor
});
canvas.renderAll();
}
Basically you just want to know if the aspect ratio of the image is greater or less than that of the canvas. Once you know that you can work out the scale factor, the final step is to work out how to offset the image such that it's centered on the canvas.
fabric.Image.fromURL("image.jpg", function (img) {
canvas.setBackgroundImage(img, canvas.renderAll.bind(canvas), {
scaleX: canvas.width / img.width,
scaleY: canvas.height / img.height
});
});
It set image to full canvas
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With