rescale images in canvas after resetting it's ratio - javascript

I have a simple canvas on my page that looks like this:
The canvas is responsive so the size can increase and decrease depending on the screen you are looking at. What remains the same is the resolution of the result. This will always be scaled to a pre-set default of 1000x1000px.
This works with a scaling ratio.
Example: the canvas is 500x500 px on your screen, this means the scaling ratio is 0.5, if your screen is big enough to fit the 1000x1000 canvas on it. The ratio will be 1.0 and so on.
The canvas also has the option to add an image to it. Those images have to size accordingly to your screen size as well and just like the complete canvas, it will be resized to it's proper size before saving(1000x1000).
When you upload an image it is usually quite big on the canvas like so:
Right now the size of the image is too big to completely show on the canvas and at this point it's individual scaling ratio is 1.0 while the scale ratio of the canvas remains at 0.5.
If i want to resize the image so it fits nicely within the canvas I get the following :
To make sure the size of the image is not returned to it's default scale of 1.0 like when I uploaded it I added the following code :
canvas.on('object:modified', function (options) {
options.target.set({
width: options.target.getWidth(),
height: options.target.getHeight(),
scaleX: 1, scaleY: 1,
});
});
This will set the X and Y scale to 1 after resizing/modifying. This way every time you change it's size, that point will be the new 1.0 scale.
To scale all my objects i wrote the following function :
function scaleObjects()
{
// page has been resized therefore we recalculate the aspect ratio for the canvas.
$windowWidth = $(window).width();
$windowHeight = $(window).height();
//currentCanvasWidth = $windowWidth;
var PrevHeightRatio = canvas.getHeight() / canvasHeightDefault;
var PrevWidthRatio = canvas.getWidth() / canvasWidthDefault;
resizeCanvas();
var HeightRatio = canvas.getHeight() / canvasHeightDefault;
var WidthRatio = canvas.getWidth() / canvasWidthDefault;
var objects = canvas.getObjects();
for (var i = 0; i < objects.length; i++) {
objects[i].scaleX = WidthRatio;
objects[i].scaleY = HeightRatio;
var multiplierW = 1 / PrevWidthRatio;
var multiplierH = 1 / PrevHeightRatio;
var originalStartX = objects[i].left * multiplierW;
var originalStartY = objects[i].top * multiplierH;
objects[i].left = originalStartX * WidthRatio;
objects[i].top = originalStartY * HeightRatio;
objects[i].setCoords();
}
canvas.renderAll();
}
This will run every time a resize or a canvas save occurs.
The problem is that whenever I save or resize my canvas the current scale of the canvas gets applied to my images/objects. In this case my image is at a scale of 1.0 and my canvas at 0.5, saving or resizing will apply the 0.5 scale to the object and decrease its size. I would however like it to scale exactly like the rest of my canvas so nothing gets out of proportion.
How can i achieve this?
Here is an image of the canvas "After" resizing the page once :
Here is a jsfiddle : https://jsfiddle.net/L6hob1e4/2/

After some research and some testing i found the following solution.
I changed the for loop that scales the objects to this:
for (var i = 0; i < objects.length; i++) {
var previousH = (((objects[i].getHeight() / canvas.getHeight()) * canvasHeightDefault) * PrevHeightRatio);
var previousW = (((objects[i].getWidth() / canvas.getWidth()) * canvasWidthDefault) * PrevWidthRatio);
console.log(previousH + " - " + previousW);
var multiplierSizeH = 1 / PrevHeightRatio;
var multiplierSizeW = 1 / PrevWidthRatio;
var onMaxH = objects[i].getHeight() * multiplierSizeH;
var onMaxW = objects[i].getWidth() * multiplierSizeW;
objects[i].height = onMaxH * HeightRatio;
objects[i].width = onMaxW * WidthRatio;
var multiplierW = 1 / PrevWidthRatio;
var multiplierH = 1 / PrevHeightRatio;
var originalStartX = objects[i].left * multiplierW;
var originalStartY = objects[i].top * multiplierH;
objects[i].left = originalStartX * WidthRatio;
objects[i].top = originalStartY * HeightRatio;
objects[i].setCoords();
}
This will keep track of the size before and after the scaling which fixes the issue.

Related

scaling fabric js canvas with all its objects

i am using a function to scale entire fabric.js canvas, this function takes value in percentage like zoomCanvas(2.2); where 2.2 means 220% but instead of percentage i need to scale canvas by pixels to fit canvas in container for example when canvas is loaded on a page from json data its initial size is like 1200px x 700px and my container size is 500px. now i need to find a way by which can convert this 500px to a percentage value so that entire canvas and all its object fits in 500px.
This is scaling function where factor is percent value
function zoomCanvas(factor) {
canvas.setHeight(canvas.getHeight() * factor);
canvas.setWidth(canvas.getWidth() * factor);
if (canvas.backgroundImage) {
// Need to scale background images as well
var bi = canvas.backgroundImage;
bi.width = bi.width * factor; bi.height = bi.height * factor;
}
var objects = canvas.getObjects();
var tcounter = 0;
for (var i in objects) {
tcounter++;
//alert(tcounter);
var scaleX = objects[i].scaleX;
var scaleY = objects[i].scaleY;
var left = objects[i].left;
var top = objects[i].top;
var tempScaleX = scaleX * factor;
var tempScaleY = scaleY * factor;
var tempLeft = left * factor;
var tempTop = top * factor;
objects[i].scaleX = tempScaleX;
objects[i].scaleY = tempScaleY;
objects[i].left = tempLeft;
objects[i].top = tempTop;
objects[i].setCoords();
}
canvas.renderAll();
canvas.calcOffset();
}
The scale factor you need is:
scaleRatio = Math.min(containerWidth/canvasWidth, containerHeight/canvasHeight);
To zoom your canvas you should really just use:
canvas.setDimensions({ width: canvas.getWidth() * scaleRatio, height: canvas.getHeight() * scaleRatio });
then
canvas.setZoom(scaleRatio)
A custom zoom function should not be needed
You should have an original canvas with some dimensions. From that canvas you should just find yours factorX and factorY values. For example, your original canvas is 1280x1020 px and you need to convert to the dimension 500x500px.
var factorX = 500 / 1280;
var factorY = 500 / 1020;
Than, modify your function from
function zoomCanvas(factor) {...}
to
function zoomCanvas(factorX, factorY) {...}
Use factorX for width values, and factorY for height values.

Fabric js How to set percentage object's position

I'm using Fabric.js and i changing canvas size dynamically, so i need scale all canvas objects with appropriate objects indents (in percent depending on canvas size) top and left. How i can set top and left objects position in percent?
like in css:
top: 32%
left: 10%
// set canvas size dynamically by resize event
this.canvas.setWidth(size.width);
this.canvas.setHeight(size.height);
In my opinion you should have two values in order to calculate resize ratio.
initialCanvasWidth and finalCanvasWidth.
By assuming that you have initialCanvasWidth value in page load. You can calculate resize ratio on every resize by a function like this:
window.onresize = utility.debounce(() => {
const finalCanvasWidth = canvasWrapper.clientWidth
const resizeRatio = (finalCanvasWidth / initialCanvasWidth).toFixed(3)
setCanvasSizeAndPosition()
scaleObjectsOnCanvas(resizeRatio)
}, 250)
By wrapping your canvas with a div and by using its new width after resize, you can set your new canvas width and height (in setCanvasSizeAndPosition function)
this.canvas.setWidth(newWidth).setHeight(newHeight)
I think you will also need to store your aspect ratio of your canvas at the beginning in order to calculate newHeight value
After setting your new canvas size, you can continue with scaling and positioning objects on canvas (scaleObjectsOnCanvas function)
scaleObjectsOnCanvas(resizeRatio) {
const objects = canvas.getObjects()
for (let i = 0; i < objects.length; i++) {
objects[i].left = objects[i].left * resizeRatio
objects[i].top = objects[i].top * resizeRatio
objects[i].scaleX = objects[i].scaleX * resizeRatio
objects[i].scaleY = objects[i].scaleY * resizeRatio
}
this.canvas.renderAll()
}
Hope that helps.
Here's a real world working example of responsive canvas/fabric.js usage

How to position Objects relative to canvas size javascript?

I use this code to resize my canvas width and height to the viewport of the browser
function scaleCanvas(){
c.width = Math.max(document.documentElement.clientWidth, window.innerWidth || 0);
c.height = Math.max(document.documentElement.clientHeight, window.innerHeight || 0);
drawMenu();
}
It works really great but now I want to put the Coordinates of my objects text etc in relations to the size of the canvas I tried this
// Canvas grösse
c.width = 1280;
c.height = 720;
// Text Schwer
var schwerx = 890;
var schwery = 52;
var schwerw = 100;
var schwerh = 30;
var schwerf = 22;
// Basis Höhe und Breite
var basex = 1280;
var basey = 720;
// Function Schwer
function schwer(){
var rx = schwerx / basex;
var x = rx * c.width;
var ry = schwery / basey;
var y = ry * c.height;
var rw = schwerw / basex;
var w = rw * c.width;
var rh = schwerh / basey;
var h = rh * c.height;
ctx.save();
ctx.rotate(16.3*Math.PI/180);
ctx.font = getFont();
ctx.fillStyle = "#feec47";
ctx.fillText('SCHWER', x, y, w, h);
ctx.restore();
function getFont() {
var ratio = schwerf / basex;
var size = c.width * ratio;
return (size|0) + 'px Pokemon';
}
}
This works great for the font size and on some Width and Heights of the canvas but not on all scales.
You need to start with a base size which is used for calculations, say 1280x720 as in your code.
From there you need two factors, one for horizontal scaling and one for vertical.
Normalize current size using your base size - this will be the scale factors you use to scale the two dimensions:
var factorX = newWidth / baseWidth;
var factorY = newHeight / baseHeight;
You can now scale using one of two approaches: either scale transforming the scale of the context:
ctx.setTransform(factorX, 0, 0, factorY, 0, 0);
or generate new temporary points of the existing points:
var schwerx = 890;
var schwery = 52;
var nSchwerx *= factorX;
var nSchwery *= factorY;
etc...
If can be convenient to store these coordinates in an object or array so they can be processed using a for-loop.
In the case of fonts you will probably use transformation - it's possible to approximate the size without - here is one method which can be used with a bounding box to define the area the text should fit (predefined using the base size and the text/base font. A method to get the height of the font is shown in the same link).
Pros and cons
There are pros and cons with both: transformation matrix makes it easy to scale to any size without doing much with existing coordinates. However, it will also interpolate/resample the graphics so with large factors things will start to look blurry, text may look squeezed etc.
Scaling coordinates (path) will obtain full quality but requires a few more steps as well as being to maintain the original coordinates. It's also more complicated to get text to fit as its size is based on height and width will follow. Width may also be non-linear relative to height depending on the typeface and how it is optimized at the height it's being used, and will therefor require special treatment.

Auto scalling of canvas dynamically using fabric.js for export on SVG

I'm working on simple editor that can add/edit text and image on a canvas. My default editor size is 480x317 but I want it to export on SVG using .toSVG() with a larger size 1075x709. The problem is when I tried to resize the canvas and export it. All object are not scale to my canvas size. It seem like it just resize the actual canvas but not the objects. Is there a function on fabric.js that can resize also the objects after the canvas resize? Here is the sample code on jsfiddle
try to zoom canvas
var originWidth = canvas.getWidth();
function zoom (width, height)
{
var scale = width / canvas.getWidth();
height = scale * canvas.getHeight();
canvas.setDimensions({
"width": width,
"height": height
});
canvas.calcOffset();
var objects = canvas.getObjects();
for (var i in objects) {
var scaleX = objects[i].scaleX;
var scaleY = objects[i].scaleY;
var left = objects[i].left;
var top = objects[i].top;
objects[i].scaleX = scaleX * scale;
objects[i].scaleY = scaleY * scale;
objects[i].left = left * scale;
objects[i].top = top * scale;
objects[i].setCoords();
}
canvas.renderAll();
}
zoom (5000);
once you save revert back to normal with zoom
fabric has built in method to do that.
var zoom = 4;
canvas.setZoom(zoom);
/* need to enlarge canvas otherwise the svg will be clipped */
canvas.setWidth(canvas.getWidth() * zoom).setHeight(canvas.getHeight() * zoom);
var svg = canvas.toSVG();
/* need to set back canvas dimensions */
canvas.setWidth(canvas.getWidth() / zoom).setHeight(canvas.getHeight() / zoom);
an option to handle this automatically trough svg viewport is in the works.

Change generated image resolution Fabric.js

I'm creating a Fabric.js based image editor, but I have a problem with final image resolution. I need generated image in high resolution but dimension for my editor are in pixel in low resolution.
For example: canvas has 800px x 600px and I need an final image with 100 cm x 400 cm, in other words, in real size.
Let me put some idea from my experience here -
if the final resolution is large, but not extreme large, you can do zoom canvas to its size before generate image data (toDataURL, for instance)
if the final resolution is extreme large, I suggest you can deal with it from PHP directly
For the first one -
var originWidth = canvas.getWidth();
function zoom (width)
{
var scale = width / canvas.getWidth();
height = scale * canvas.getHeight();
canvas.setDimensions({
"width": width,
"height": height
});
canvas.calcOffset();
var objects = canvas.getObjects();
for (var i in objects) {
var scaleX = objects[i].scaleX;
var scaleY = objects[i].scaleY;
var left = objects[i].left;
var top = objects[i].top;
objects[i].scaleX = scaleX * scale;
objects[i].scaleY = scaleY * scale;
objects[i].left = left * scale;
objects[i].top = top * scale;
objects[i].setCoords();
}
canvas.renderAll();
}
zoom (2000);
// here you got width = 2000 image
var imageData = canvas.toDataURL({
format: 'jpeg',
quality: 1
});
zoom (originWidth);
I never tried it on 100cm x 400cm, because it's really large, so if you can't do it from fabric.js, do it in PHP or else, this link may be help Convert SVG image to PNG with PHP
100cm = 39.3inch and 400cm = 39.3 * 4inch, if you have 300dpi image for final output. You will need width = 39.3 * 300 and height 39.3 * 4 * 300 size, if browser can handle it or not, I am not sure.

Categories

Resources