Resizing images (in SVG format) after applying "rotate" transformation - javascript

I've been working on an application that allows users to add images to a viewport, then resize them, crop them and rotate them. For the rotation to work, the images are added as SVG's, using the Raphael.js library.
All images have an outline (called wrapper). When an image is resized, so is its wrapper. When the image is rotated, the wrapper expands too, so that it will contain the rotated image (see image bellow).
So far, so good. Except that when I attempt to resize an image object AFTER it has been rotated, it won't be contained within its outline anymore (see image bellow).
When resizing an image, the svg oject's "x" and "y" attributes also need to be adjusted. Both the new size (width + height) of the image and the "x" and "y" attributes are computed as seen bellow:
var ratio_width = w / init.wrapper.width, // w = the new width of the wrapper (computed at each resize step)
ratio_height = h / init.wrapper.height, // h = the new height of the wrapper
svg_width = init.svg.width * ratio_width, // svg_width = the new width of the image
svg_height = init.svg.height * ratio_height, // svg_height = the new height of the image
x = init.svg.x*ratio_width,
y = init.svg.y*ratio_height;
this.svg.attr({
"x": x,
"y": y,
"width": svg_width,
"height": svg_height
});
The "init" JavaScript object contains the size (width & height) of the wrapper AND the size of the image (after the image is rotated, their dimensions will differ), but also the values of the x and y attributes BEFORE the resize operation.
The thing is that all the values computed above are correct. I know because I've also tried destroying the svg at each resize step and re-appending it (but it's not an option, there's too much of a flicker), with those same values for size, x and y, like so:
// "this.angle" on the second line is the current rotation angle
this.svg.remove();
this.svg = this.canvas.image(this.src,x,y,svg_width,svg_height).transform("r"+this.angle);
So if I simply remove the object and then create it again with the values above, why won't simply changing its attributes do the job as it should? I've been pulling my hair out for 3 days now, so I'd appreciate some help!

Related

Fabric js transform and zoom canvas to fit all objects in viewport

I'm trying to made a collaboration tool based on fabricJs. Because I want canvas to be responsive I get some problems with pan/zoom functionality
Canvas view on large monitor
Problem is when someone on large monitor put some objects on corners for example and another user open this canvas on laptop or small monitor. In this case there is objects outside of current user viewport
Canvas view on small monitor
My idea is to add a button and functionality to fit all objects inside view with zoom and viewportTransform (I don't want to move objects in center of canvas because after save I will get different results from origin).
For this first I create a group with
let group = new fabric.Group(_this.canvas.getObjects()); and then I calculate zoom with
canvas.height / group.height and I look zoom to be calculated properly.
After that I don't know how to calculate where I need to zoom canvas. I try with getBoundingRect, calcViewportBoundaries and other function but can`t get correct zoom point.
JSFiddle - https://jsfiddle.net/znru0yx6/4/
I know this is 2.5 years after you asked. But this might help others who stumble across this (as i did) and give you some closure as well.
I've tried to include as many comments as possible as i always prefer to see the explanation alongside each line, rather than separately.
window.zoomToContents = function(canvas){
//first check if there are any elemnts to zoom to
if (canvas.getObjects().length < 1) {
return;
}
// reset zoom so pan actions work as expected
canvas.setZoom(1);
//group all the objects
const group = new fabric.Group(canvas.getObjects());
//find the centre of the group on the canvas
const x = (group.left + (group.width / 2)) - (canvas.width / 2);
const y = (group.top + (group.height / 2)) - (canvas.height / 2);
//and pan to it
canvas.absolutePan({x:x, y:y});
//now we need to decide whether width or height should determine the scaling
//e.g. a portrait box in a landscape canvas (height) needs scaling differently to a portrait box in a portrait canvas (could be height or width)
//or a landscape box in a portrait canvas (width)
//work out the distance between the edges of the group and the canvas
const heightDist = canvas.getHeight() - group.height;
const widthDist = canvas.getWidth() - group.width;
let groupDimension = 0;
let canvasDimension = 0;
//the smaller the number then that's the side we need to use as a reference to scale
//either group is inside the canvas (positive number) so the edge closest to the limits of the canvas will be used as the reference scale (smallest positive difference)
//or the group extends outside the canvas so the edge that extends further will be the reference (large negative number)
//either way, we want the smallest number
if (heightDist < widthDist) {
//height is the reference so need the height to scale to be nearly the height of the canvas
groupDimension = group.height;
canvasDimension = canvas.getHeight();
} else {
//width is the reference so need the width to scale to be nearly the width of the canvas
groupDimension = group.width;
canvasDimension = canvas.getWidth();
}
//work out how to scale the group to match the canvas size (then only make it zoom 80% of the way)
const zoom = (canvasDimension / groupDimension) * 0.8;
//we've already panned the canvas to the centre of the group, so now zomm using teh centre of teh canvas as teh reference point
canvas.zoomToPoint({ x: canvas.width / 2, y: canvas.height / 2 }, zoom);
}

Re-scale svg element

I have different rect elements that are drawn inside an svg layer which is overlapped over a png image.
In another view I can see the elements drawn but the image below is bigger and it hasn't same scale factor, if I divide the width for height for both the images, the results aren't the same.
How can I re-scale an svg element inside an image with two different scale factors?
These images could explain what I mean to do
Image with rect drawn:
Image that I should re-scale:
I have solved the problem with a simple function.
Ours unknowns are the rect's coordinates that we want to re-scale and its size, so if we denote with oldRectX and oldRectY, newRectX and newRectY rispectively as the old and the new rect's coordinates and with oldSVGWidth and oldSVGHeight, newSVGWidth and newSVGHeight rispectively as the old and the new SVG's dimensions that contains the rects, applying this ratio we can calculate the new positionament and the new size:
oldRectX : oldSVGWidth = newRectX : newSVGWidth
So I can calculate newRectX:
newRectX = (oldRectX * newSVGWidth)/oldSVGWith
The same reasoning it's apply for calculate newRectY with the difference that I have to replace the width with the height and X with Y:
newRectY = (oldRectY * newSVGHeight)/oldSVGHeight
Finally the new sizes:
newRectWidth = oldRectWidth * (newSVGWidth/oldSVGWidth)
newRectHeight = oldRectWidth * (newSVGHeight/oldSVGHeight)

Resize rectangle "onResize" in paper.js

Ok, I feel incredibly stupid, but after trying for half an hour or so, I give up.
How do I resize a rectangle via the onResize() event in paper.js?
I am trying with this example, which comes with a onResize() function already, but I am having no success (you can edit the sample by clicking on the Source button on the top right).
The current resize function looks like this:
function onResize() {
text.position = view.center + [0, 200];
square.position = view.center;
}
Now, I tried to make the square 80 % of the viewport height on resize by adding:
square.size = [view.size.height / 100 * 80, view.size.height / 100 * 80];
(I tried the same with static numbers, just to be sure).
I tried
square.size = new Size(width, height);
square.set(new Point(view.center), new Size(width, height)
square.height = height;
square.width = width;
and probably 20 more version that I cannot remember now.
When I console.logged the square.size it did show me the newly assigned values (sometimes?), but it still left the size of the rectangle unchanged.
What can I do to actually change the size of rectangle?
Just to make sure, the onResize() function gets called whenever the window's dimensions change. That's not the only place you can change the rectangle's size / it has nothing to do with the rectangle per se.
To change the rectangle's sizes, you have to check the documentation. There's a scale(amount) and an expand(amount) method attached to the Rectangle object. size is just a property, and it doesn't seem to come with a setter.
Hence, if you want to keep a ratio in between the square and the view, I guess you could save the previous view width, see what the difference is and scale the rectangle accordingly – see how in this this answer to a previous question.
Alternatively, you could just reinitialise the square and set the size property to 80% of the view's width on each view resize:
square = new Path.Rectangle({
position: view.center,
size: view.bounds.width * 0.8,
parent: originals,
fillColor: 'white'
});

How to make a canvas animation scale (HTML5)

I have an animation/canvas drawing where the pixels are hard coded and not relative. How do I make it so that the canvas and the drawing/animation scales with the browser size?
I thought of making the pixel values relative to the width and height of either the browser or the canvas but I don't know if it's a good idea.
Any tips?
P.S: I didn't post the code because it is almost 1000 lines long but if required I could post part of it.
Scale to fit, fill, or stretch
Almost every device has a different display aspect ratio, and then each setup will use only part, or all of the available screen to display the window.
The only real choice you have is how to adapt to fit the available space.
As the previous answer points out you can get the available size with innerWidth, innerHeight.
So lets assume you have that as width and height.
var width = innerWidth; // or canvas width
var height = innerHeight; //
Default resolution
I am assuming you app has a fixed aspect and an optimal resolution. To make it simple to adapt your app you need to keep the optimal resolution stored in code.
var defaultWidth = ?
var defaultHeight = ?
To adapt to displays you will need to get a scale that you set at the start of the app and adjust when the display size changes. You will also need the origin. the point where coordinate 0,0 is as for some solutions that will move.
These values are all relative to the current display that app is rendering on. They will take you native coordinates and make them conform to the device pixels.
var displaySetting = {
scaleX : ?,
scaleY : ?,
originX : ?,
originY : ?,
}
Fit, fill, or stretch
The 3 most basic options.
Stretch to fit. You stretch the rendering to fit the display but lose the aspect.
Scale to fit. You scale the rendering so that you maintain the aspect and all of the display is visible. Thought depending on the device you may have empty space on the sides or top and bottom.
Scale to fill. You scale the rendering so that it fills the device screen but you may end up clipping some or the rendering on the sides or top and bottom.
.
function getDisplayTransform(type, displaySetting){
displaySetting = displaySetting || {};
// first get scales
if(type === "stretch"){
displaySetting.scaleX = width / defaultWidth;
displaySetting.scaleY = height / defaultHeight;
} else if (type === "fit") {
displaySetting.scaleX = Math.min(width / defaultWidth, height / defaultHeight);
displaySetting.scaleY = displaySetting.scaleX;
} else { // type to fill
displaySetting.scaleX = Math.max(width / defaultWidth, height / defaultHeight);
displaySetting.scaleY = displaySetting.scaleX;
}
// now that the scale is set get the location of the origin. which is
displaySetting.originX = width / 2 - defaultWidth * 0.5 * displaySetting.scaleX;
displaySetting.originY = height / 2 - defaultHeight * 0.5 * displaySetting.scaleY;
// Note that for scale to fill the origin may be of the display or canvas
return displaySetting;
}
So now you just have to set the new coordinate system
ctx.setTransform(
displaySetting.scaleX,0,
0,displaySetting.scaleY,
displaySetting.originX,displaySetting.originY
);
So to fit with space to spare use getDisplayTransform("fit",displaySetting); or to fill use getDisplayTransform("fill",displaySetting); which will have some clipping top & bottom or left & right.
Once the transform is set you render as normal. You don't have to change the coordinates in your code, Line widths, font sizes, shadow offsets all remain the same as far as you code is concerned.
Set the canvas width and height to window.innerWidth and window.innerHeight respectively. So, the code would be like (in JS):
var canvas = document.getElementById('my_canvas');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
Above code will only change the canvas size according to window width and height. To set the drawing/animation scale, you need to set its position/size dynamically (respect to the window width/height), not hard coded.

Get pixel color from resized canvas, on mouseover

I have checked this question which provides the perfect answer. But my problem is slightly different. I have a canvas of 300 x 300 and i am re-sizing the canvas using css to 200 x 60. If i re-size the canvas using css i am not able to get the color value onmouseover.
In the re-sized fiddle if you mouse over right below the red or blue rectangles you will notice it still says #FF0000 & #0000FF respectively while it should be #000000. So how to make it work even with re-sized canvas?
Fiddle: Re-sized with css.
Fiddle: Non re-sized.
You need to apply a scale factor inside the mouse handler method. The scale factor is the relationship between your canvas's bitmap (actual size) and the element size (CSS size).
For example:
// find scale:
var sx = example.width / parseInt(example.style.width, 10);
var sy = example.height / parseInt(example.style.height, 10);
// apply to x/y
x = (x * sx)|0; // scale and cut any fraction to get integer value
y = (y * sy)|0;
Updated fiddle
In addition the code need to have some boundary check of the coordinates so getImageData() won't fail (not shown here).

Categories