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.
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.
I'm placing text in an HTML5 canvas. I'm setting the context textAlign value to center and the textBaseline to "hanging"
I'm confused, because safari and chrome and both webkit and the text placement is diffrent even when told to be in the same spot.
Chrome looks correct:
as does firefox
however safari places the text lower (same code)
any idea how safari calculates the position differently so I can build an exception for it?
Text to fit is a pain. Here is a none standard way to make text fit almost pixel perfect. (width may come inside requested a little and some fonts are just way to out there to measure).
This method renders the text at a large size "100px" and then measures its various parts. You can the fit to a rectangle, with four options that you can see in the demo. T, H and letters like that tend to be slightly shorter than O, G and rounded letters, you can opt to keep the round bits inside or not, you can ignore the tails 'y,j,g' or keep them inside.
The measuring function renders text several times to find the various parts, you can overlap several letters if you want a better sample or a specific font is a pain.
This solution will fit all browsers that have good canvas support.
Sorry about the naming but this particular issue does me in, we get great text in every other browser API so why not canvas????? (secret conspiracy of the SVG camp LOL ;) me thinks
Two functions. First measures the text, and returns an object containing the relevant info, and the second renders text to fit a rectangle. See the code at the bottom how to use it and set the two flags that control the hanging tail and roundy bits.
var canvas = document.createElement("canvas");
canvas.width = 800;
canvas.height = 800;
var ctx = canvas.getContext("2d");
document.body.appendChild(canvas);
function superWTFCanvasTextMessure(font){ // just the font name please.....
var c = document.createElement("canvas"); // make canvas
var ctx1 = c.getContext("2d"); // get the thing that draw the stuff
ctx1.font = "100px "+font; // big font
ctx1.textAlign = "center"; // put it in the middle
ctx1.textBaseline = "middle";
// draw text nice and solid...
ctx1.fillText("lp",Math.floor(c.width/2),Math.floor(c.height/2));
ctx1.fillText("lp",Math.floor(c.width/2),Math.floor(c.height/2));
ctx1.fillText("lp",Math.floor(c.width/2),Math.floor(c.height/2));
ctx1.fillText("lq",Math.floor(c.width/2),Math.floor(c.height/2));
ctx1.fillText("lg",Math.floor(c.width/2),Math.floor(c.height/2));
ctx1.fillText("lj",Math.floor(c.width/2),Math.floor(c.height/2));
// get the pixels as words
var data= new Uint32Array(ctx1.getImageData(0,0,canvas.width,canvas.height).data.buffer);
var top = 0;
while(data[top++] === 0); // find the first pixel on from the top;
var tail = data.length - 1;
while(data[tail--] === 0); // find the first pixel on from the bottom;
ctx1.clearRect(0,0,canvas.width,canvas.height); // clear up the mess
// and now for the Base draw text nice and solid...
ctx1.fillText("T",Math.floor(c.width/2),Math.floor(c.height/2));
ctx1.fillText("T",Math.floor(c.width/2),Math.floor(c.height/2));
ctx1.fillText("T",Math.floor(c.width/2),Math.floor(c.height/2));
ctx1.fillText("T",Math.floor(c.width/2),Math.floor(c.height/2));
ctx1.fillText("T",Math.floor(c.width/2),Math.floor(c.height/2));
// get the pixels as words
data= new Uint32Array(ctx1.getImageData(0,0,canvas.width,canvas.height).data.buffer);
var bum = data.length - 1;
while(data[bum--] === 0); // find the first pixel on from the bottom;
// and the round bits the poke out in all the wrong places.
ctx1.clearRect(0,0,canvas.width,canvas.height); // clear up the mess
// and now for the Base draw text nice and solid...
ctx1.fillText("O",Math.floor(c.width/2),Math.floor(c.height/2));
ctx1.fillText("J",Math.floor(c.width/2),Math.floor(c.height/2));
ctx1.fillText("?",Math.floor(c.width/2),Math.floor(c.height/2));
ctx1.fillText("G",Math.floor(c.width/2),Math.floor(c.height/2));
ctx1.fillText("O",Math.floor(c.width/2),Math.floor(c.height/2));
// get the pixels as words
data= new Uint32Array(ctx1.getImageData(0,0,canvas.width,canvas.height).data.buffer);
var head = 0;
while(data[head++] === 0); // find the first pixel on from the bottom;
var theOtherBit = data.length - 1;
while(data[theOtherBit--] === 0); // find the first pixel on from the bottom;
return {
body: Math.floor(bum / canvas.width) - Math.floor(top / canvas.width)+1,
all : Math.floor(tail / canvas.width) - Math.floor(top / canvas.width)+1,
offset : Math.floor(c.height/2) - Math.floor(top / canvas.width),
t2A : Math.floor(theOtherBit / canvas.width) - Math.floor(head / canvas.width)+1,
t2t : Math.floor(tail / canvas.width) - Math.floor(head / canvas.width)+1,
offsetHead : Math.floor(c.height/2) - Math.floor(head / canvas.width),
font : ctx1.font,
};
}
function drawPixelPerfectTextTheHardWay(text,left,top,width,height,sWTFDesc){
var sy,offy;
ctx.font = sWTFDesc.font; // set up the same font as measured. (dont worry it will be scaled and can be any size but if not measure at 100 px this will not work as well)
ctx.textAlign = "center";
ctx.textBaseline = "middle";
var w = ctx.measureText(text).width; // get the width
if(sWTFDesc.bumsDown){
if(sWTFDesc.extrasIn){
sy = height/ sWTFDesc.t2A; // get the height scale
}else{
sy = height/ sWTFDesc.body; // get the height scale
}
}else{
if(sWTFDesc.extrasIn){
sy = height/ sWTFDesc.t2t; // get the height scale
}else{
sy = height/ sWTFDesc.all; // get the height scale
}
}
var sx = width / w; // get the x scale
if(sWTFDesc.extrasIn){
offy = sWTFDesc.offset * sy; // get top offset
}else{
offy = sWTFDesc.offset * sy; // get the correct offset
}
// set up the tranform
ctx.setTransform(sx,0,0,sy,left + width / 2, top + offy);
ctx.fillText(text,0,0);
ctx.setTransform(1,0,0,1,0,0); // reset the tranform to the default..
// all diddly done..
}
ctx.clearRect(0,0,canvas.width,canvas.height)
var superFontDesc = superWTFCanvasTextMessure("arial");
ctx.strokeStyle = "black";
ctx.fillStyle = "red";
ctx.strokeRect(10,200,700,140);
drawPixelPerfectTextTheHardWay("mind p & q's ? j,g",10,200,700,140,superFontDesc);
ctx.fillStyle = "green";
ctx.strokeRect(20,400,700,120);
superFontDesc.bumsDown = true;
drawPixelPerfectTextTheHardWay("test this Jimmy!!",20,400,700,120,superFontDesc);
ctx.fillStyle = "blue";
ctx.strokeRect(20,20,700,140);
superFontDesc.bumsDown = true;
superFontDesc.extrasIn= true;
drawPixelPerfectTextTheHardWay("test this Jimmy!!",20,20,700,140,superFontDesc);
ctx.fillStyle = "#a50";
ctx.strokeRect(20,570,700,140);
superFontDesc.bumsDown = false;
superFontDesc.extrasIn= true;
drawPixelPerfectTextTheHardWay("????&GGhjqgy",20,570,700,140,superFontDesc);
ctx.font = "20px arial";
ctx.textAlign = "left";
ctx.fillStyle = "black";
ctx.fillText("Round bits in and tails hanging.",10,174);
ctx.fillText("Round bits out and tails in.",10,354);
ctx.fillText("Round bits out and tails hanging.",10,540);
ctx.fillText("Round bits out and tails in.",10,724);
So I am adjusting the size of the stage (canvas) upon window resize. While the canvas itself is readjusting correctly, the content of the canvas are not really scaling and in some cases some of the elements (ex: text, buttons etc.) aren't fully visible.
Here's the code
stage = new Stage(document.getElementById("canvas"));
window.addEventListener('resize',resize,false);
function resize() {
var w = window.innerWidth;
var h = window.innerHeight;
var ow = stage.canvas.width;
var oh = stage.canvas.height;
// keep aspect ratio
scale = Math.min(w / ow, h / oh);
stage.scaleX = scale;
stage.scaleY = scale;
// adjust canvas size
stage.width = ow * scale;
stage.height = oh * scale;
stage.update();
}
Is there something I am missing?
A few quick things:
You can't set the width/height of the stage, but should instead set the size of the canvas. You also don't need to constrain its aspect ratio, unless you are trying to crop content.
stage.width = ow * scale;
stage.height = oh * scale;
Next, you might consider putting your content in a Container, and scaling that, instead of the stage directly.
Hope that helps!
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.