Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

FabricJS Setting background image size and position

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();
    }
};
like image 936
Daniel Bonnell Avatar asked Nov 09 '16 13:11

Daniel Bonnell


2 Answers

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.

like image 153
John M Avatar answered Nov 06 '22 06:11

John M


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

like image 44
Jay Kukadiya Avatar answered Nov 06 '22 06:11

Jay Kukadiya