Image Quality decreases when drawn on HTML5 Canvas - Next JS - javascript

I'm drawing an image uploaded by the user on an HTML5 canvas of fixed height and width which can be dragged and resized via mouse.Now I am preserving the aspect ratio of the image with the help of the following draw function.
if(image){
const scaleX = CANVAS_WIDTH / image?.width;
const scaleY = CANVAS_HEIGHT / image?.height;
const scale = Math.min(scaleX, scaleY);
// Calculate the new dimensions of the image
const newWidth = image?.width * scale;
const newHeight = image?.height * scale;
// Clear the canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Draw the image on the canvas
setImageRight((CANVAS_WIDTH + newWidth) / 2);
setImageBottom((CANVAS_HEIGHT + newHeight) / 2);
ctx.drawImage(image, (CANVAS_WIDTH - newWidth) / 2, (CANVAS_HEIGHT - newHeight) / 2, newWidth, newHeight);
}
Here image is a part of a useState hook which I'm setting with the following code
useEffect(() => {
// Load the image onto the canvas
const image = new Image();
image.src = URL.createObjectURL(props.imageFile);
image.onload = () => {
setImage(image);
setImageWidth(image.width);
setImageHeight(image.height);
};
}, [props.imageFile]);
useEffect(() => {
if(image)
drawImage(true,false);
}, [image])
Now even though the aspect ratio of the initial image rendered on the canvas is preserved, the image quality is seriously affected. What can I do to preserve the image quality or reduce the degree to which the image quality is degrading? I am new to HTML5 Canvas methods. So any help would be appreciated greatly
Edit:
The first one is the image I am drawing on the canvas. The second one is the image Photoroom is drawing onto their canvas. You can notice the difference in quality even though both the images, as well as the canvas, are of similar dimensions.

Its depend on you HTML5 canvas resolution [width x height] and also depend on ctx.drawImage() functions width x height.
Just increase width x height resolution of ctx.drawImage() function and see results.

Related

Image transform property not rotating image [duplicate]

I am experimenting with animation in <canvas> and can't work out how to draw an image at an angle. The desired effect is a few images drawn as usual, with one image rotating slowly. (This image is not at the centre of the screen, if that makes any difference).
You need to modify the transformation matrix before drawing the image that you want rotated.
Assume image points to an HTMLImageElement object.
var x = canvas.width / 2;
var y = canvas.height / 2;
var width = image.width;
var height = image.height;
context.translate(x, y);
context.rotate(angleInRadians);
context.drawImage(image, -width / 2, -height / 2, width, height);
context.rotate(-angleInRadians);
context.translate(-x, -y);
The x, y coordinates is the center of the image on the canvas.
It is interesting that the first solution worked for so many people, it didn't give the result I needed.
In the end I had to do this:
ctx.save();
ctx.translate(positionX, positionY);
ctx.rotate(angle);
ctx.translate(-x,-y);
ctx.drawImage(image,0,0);
ctx.restore();
where (positionX, positionY) is the coordinates on the canvas that I want the image to be located at and (x, y) is the point on the image where I want the image to rotate.
I have written a function (based on Jakub's answer) that allows user to paint an image in a X,Y position based on a custom rotation in a custom rotation point:
function rotateAndPaintImage ( context, image, angleInRad , positionX, positionY, axisX, axisY ) {
context.translate( positionX, positionY );
context.rotate( angleInRad );
context.drawImage( image, -axisX, -axisY );
context.rotate( -angleInRad );
context.translate( -positionX, -positionY );
}
Then you can call it like this:
var TO_RADIANS = Math.PI/180;
ctx = document.getElementById("canvasDiv").getContext("2d");
var imgSprite = new Image();
imgSprite.src = "img/sprite.png";
// rotate 45ยบ image "imgSprite", based on its rotation axis located at x=20,y=30 and draw it on context "ctx" of the canvas on coordinates x=200,y=100
rotateAndPaintImage ( ctx, imgSprite, 45*TO_RADIANS, 200, 100, 20, 30 );

How can I calculate image overflow in the canvas, so I can limit the image editor to always be filled no matter the scale/position of image?

I have a canvas in a react app which I can zoom in image and move it right/left/up/down.
I want to be able to calculate image overflow size so that I can limit the left/right/up/down actions when there's no more room too go.
Here's the working app:
https://codesandbox.io/s/reverent-mestorf-9gd1t?file=/src/App.js
As you can see when you zoom out the image will no longer be filled inside canvas. I want to be able to disable zoom out button when image is going to be smaller than canvas.
I also want to be able to disable move right/left/up/down buttons when there's no more room in the image to move.
current behavior just shows empty spots inside canvas.
Here's the canvas code inside my useEffect, whenever src, x, y and scale changes we'll render the canvas.
useEffect(() => {
if (src) {
const canvas = canvasRef.current
const canvasContext = canvas?.getContext('2d')
const imageObj = new Image()
imageObj.onload = () => {
if (canvas) {
const hRatio = canvas.width / imageObj.width
const vRatio = canvas.height / imageObj.height
// Calculate ratio so the image is filled inside canvas
const ratio = Math.max(hRatio, vRatio)
canvasContext?.clearRect(0, 0, canvas.width, canvas.height)
canvasContext?.drawImage(
imageObj,
x,
y,
imageObj.width,
imageObj.height,
0,
0,
// for zooming scale is between 0 and 2
imageObj.width * ratio * scale,
imageObj.height * ratio * scale
)
}
}
imageObj.src = src
}
}, [src, x, y, scale])
After a lot of playing around with canvas I found the solution.
my old draw image:
canvasContext?.drawImage(
imageObj,
x,
y,
imageObj.width,
imageObj.height,
0,
0,
// for zooming scale is between 0 and 2
imageObj.width * ratio * scale,
imageObj.height * ratio * scale
)
my new draw image:
canvasContext?.drawImage(
imageObj,
x,
y,
// scale is between 0 and 2 for zooming
imageObj.width * ratio * scale,
imageObj.height * ratio * scale
)
I needed to apply the ratio and zoom to the source image size instead of canvas's.
So now to simply calculating overflow I can do this:
overflowY = 3000(canvas height) - (imageObj.height * ratio * scale)
overflowX = 4500(canvas width) - (imageObj.width * ratio * scale)

CreateJS: Elements in Stage or Canvas are not scaling properly on window resize

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!

Scaling HTML Canvas elements

In my main project I have lots of little canvas elements (think: sprites from a sprite map) and I need to be able to scale them before drawing them to the main canvas. Here is my basic code for this:
var c = document.getElementById("canvas");
var ctx = c.getContext("2d");
ctx.canvas.width = window.innerWidth;
ctx.canvas.height = window.innerHeight;
var miniCanvas = document.createElement('canvas');
var miniCanvasContext = miniCanvas.getContext('2d');
miniCanvasContext.beginPath();
miniCanvasContext.rect(0,0,100,100);
miniCanvasContext.closePath();
miniCanvasContext.fillStyle = 'red';
miniCanvasContext.fill();
miniCanvasContext.scale(2,2);//THIS IS THE PROBLEM
ctx.drawImage(miniCanvas, 100, 100);
This test is up at jsfiddle: JSFIDDLE
Essentially, I need this red square to be increased in size by two times.
Thanks.
The drawImage method has a version that lets you draw the miniCanvas with scaling:
// the last 2 parameters are scaled Width & scaled Height
// parameters:
// 1st: the image source
// 2nd,3rd: the upper-left part of the source to clip from
// 4th,5th: the width & height to clip from the source
// 6th,7th: the upper-left part of the canvas to draw the clipped image
// 8th,9th: the scaled width & height to draw on the canvas
ctx.drawImage(miniCanvas, 0,0,100,100, 0,0,200, 200);

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