Center point scaling on HTML 5 Canvas - javascript

Let me start by saying that I am not a professional developer, but I am managing a development project that has a specific need -- to scale an image placed on an HTML5 canvas from the center point using a slider. The developer has implemented a slider that scales the image from the top-left point, but usability dictates resizing from the center and he has not been able to figure it out. Is this even possible? Please see here for a beta version of the app pre-loaded with an image to test:
http://beta.drumart.com/head-builder?lib-image-url=https://thumbs.dreamstime.com/800thumbs/12617/126170273.jpg
Click the image to select it and use the "Size" slider to resize. I am just looking to find out if this is possible and, if so, point him in the right direction. Many thanks in advance!
Image slider screenshot

I reccommend creating a drawCenteredImage() function, give it an image, x, y, width, height and it will draw the image centered on the given x, y. Thus when you increase x and y it will scale about the center of the image.
function drawCenteredImage(img, x, y, width, height) {
// Assuming globally accessible canvas context variable 'context'
context.drawImage(img, x - width / 2, y - height / 2, width, height);
}
An example of this in use:
% Updated to scale in realtime %
let canvas = document.getElementById("canvas");
let context = canvas.getContext("2d");
let image = new Image();
image.onload = function() {
drawImg();
};
image.src = "https://thumbs.dreamstime.com/800thumbs/12617/126170273.jpg";
function drawCenteredImage(img, x, y, width, height) {
// Assuming globally accessible canvas context variable 'context'
context.drawImage(img, x - width / 2, y - height / 2, width, height);
}
function drawImg() {
context.fillStyle = "white";
context.fillRect(0,0,canvas.width,canvas.height);
let scale = document.getElementById("scl").value;
drawCenteredImage(image, canvas.width / 2, canvas.height / 2, image.width * scale, image.height * scale);
}
let mousedownID = -1;
function mousedown(event) {
if(mousedownID==-1)
mousedownID = setInterval(drawImg, 5);
}
function mouseup(event) {
if(mousedownID!=-1) { //Only stop if exists
clearInterval(mousedownID);
mousedownID=-1;
}
}
//Assign events
document.getElementById("scl").addEventListener("mousedown", mousedown);
document.getElementById("scl").addEventListener("mouseup", mouseup);
//Also clear the interval when user leaves the window with mouse
document.addEventListener("mouseout", mouseup);
<input id="scl" type="range" min="0" max="1" value="0.3" step="0.05">
<canvas id="canvas" width="500px" height="400px"></canvas>

Related

Wrap image around cylinder using canvas drawImage opacity issue

I need to wrap an image around another image of a mug using javascript, and I found this:
Wrap an image around a cylindrical object in HTML5 / JavaScript
This helps when loading the image that has the mug handle on the left. However when using the same function (with tweaked position values) the image has an opacity applied to it. I searched endlessly to figure out for what reason this is happening however I found nothing :/
This is the function used to wrap the image for the mug with the right handle:
function canvas2() {
var canvas = document.getElementById('canvas2');
var ctx = canvas.getContext('2d');
var productImg = new Image();
productImg.onload = function() {
var iw = productImg.width;
var ih = productImg.height;
canvas.width = iw;
canvas.height = ih;
ctx.drawImage(
productImg,
0,
0,
productImg.width,
productImg.height,
0,
0,
iw,
ih
);
loadUpperIMage();
};
productImg.src =
'https://i.ibb.co/B2G8y1m/white-right-ear.jpg';
function loadUpperIMage() {
var img = new Image();
img.src =
'https://i.ibb.co/BnQP0TL/my-mug-image.png';
img.onload = function() {
var iw = img.width;
var ih = img.height;
var xOffset = 48, //left padding
yOffset = 68; //top padding
var a = 70; //image width
var b = 8; //round ness
var scaleFactor = iw / (6 * a);
// draw vertical slices
for (var X = 0; X < iw; X += 1) {
var y = (b / a) * Math.sqrt(a * a - (X - a) * (X - a)); // ellipsis equation
if (!isNaN(y)) {
ctx.drawImage(
img,
X * scaleFactor,
0,
iw / 0.78,
ih,
X + xOffset,
y + yOffset,
1,
162
);
}
}
};
}
}
Hope someone can help with this!
Here is a fiddle with the issue https://jsfiddle.net/L20aj5xr/
It is because of the 4th argument you pass to drawImage - iw / 0.78. By multiplying image width by a value lower than one, you get the value larger than image width. The spec for drawImage says:
When the source rectangle is outside the source image, the source rectangle must be clipped to the source image and the destination rectangle must be clipped in the same proportion.
ctx.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);
Because the source width (sw) you are using is larger than source image size, the destination rectangle "is clipped in the same proportion". The destination rectangle width is 1px because you chose it as a width for each vertical line you are drawing, and after clipping it's width becomes 1 * 0.78 = 0.78px. The width is now less than 1px and to be honest I am not exactly sure how it actually works under the hood, but my guess is that a browser still needs to draw that 1px, but because the source is 0.78px, it kinda stretches the source to that 1px and adds some anti-aliasing to smooth the transition, which results into added transparency (i.e. browser does not have enough information for that 1px and it tries to fill it up the best it can). You can play around with that by incresing sw even more and observe increasing transparency.
To fix your issue I used the value 20 instead of 0.78 like for the first cup and it seemed to look ok.

Image following a mouse on a canvas

I want to have an image follow the mouse around the canvas, which is fairly easy, but the catch is that I want my canvas to change with screen resolution (it is set using CSS to be 70vw).
When the resolution decreases and the window becomes smaller this means that using a normal method of using clientX doesn't work.
My code so far is this:
var mouseX = e.clientX/document.documentElement.clientWidth * 1920;
var mouseY = e.clientY/document.documentElement.clientHeight * 943;
This tries to convert the users clientX into the value it would be on a 1920x1080 monitor.
However, this isn't really accurate and doesn't work very well on even 1920x1080 monitors. Any help would be appreciated.
You can't scale the canvas using CSS in the way that you think. A canvas is basically a more advanced image. Scaling the canvas via CSS just stretches the canvas the same way an image would stretch. To change the canvas height and width, you need to change it's height and width attributes in the tag or via code. This will physically change the canvas to the size that you want without scaling and/or stretching.
That being said, we can use this to watch for window size changes and resize the canvas when the window changes.
window.addEventListener('resize', e => {
canvas.width = window.innerWidth
canvas.height = window.innerHeight
})
With some basic math, we can calculate what a 70% width would be, it would be done like this
window.addEventListener('resize', e => {
canvas.width = window.innerWidth * 0.7
canvas.height = window.innerHeight
})
The next thing we need to do is get the local position of the mouse on the canvas, which can be done using mousePosition - canvasOffset like this
let x = e.clientX - canvas.offsetLeft
let y = e.clientY - canvas.offsetTop
When all is said and done, we end up with something like this (To see it in action press run then click on Full Page and you will see the canvas resize):
const canvas = document.querySelector('canvas')
const ctx = canvas.getContext('2d')
// Set the inital height and width of the canvas
canvas.width = window.innerWidth
canvas.height = window.innerHeight
canvas.addEventListener('mousemove', e => {
ctx.clearRect(0, 0, canvas.width, canvas.height)
// Get the local x/y coordinates of the mouse on the canvas
let x = e.clientX - canvas.offsetLeft
let y = e.clientY - canvas.offsetTop
// Draw a dot where the mouse is
ctx.beginPath();
ctx.arc(x, y, 10, 0, 2 * Math.PI, false);
ctx.fillStyle = 'white';
ctx.fill();
})
// Update the height and width when the window size changes
window.addEventListener('resize', e => {
canvas.width = window.innerWidth
canvas.height = window.innerHeight
})
body {
padding: 0;
margin: 0;
}
canvas {
background-color: black;
display: block;
}
<canvas></canvas>
In this example below, we use a canvas that is 70% the width and height of the screen and center it with CSS. However, we never touch the height/width with css because it will mess up the canvas' coordinate system. This part is done with JavaScript.
const canvas = document.querySelector('canvas')
const ctx = canvas.getContext('2d')
// Set the inital height and width of the canvas
canvas.width = window.innerWidth * 0.7
canvas.height = window.innerHeight * 0.7
canvas.addEventListener('mousemove', e => {
ctx.clearRect(0, 0, canvas.width, canvas.height)
// Get the local x/y coordinates of the mouse on the canvas
let x = e.clientX - canvas.offsetLeft
let y = e.clientY - canvas.offsetTop
// Draw a dot where the mouse is
ctx.beginPath();
ctx.arc(x, y, 10, 0, 2 * Math.PI, false);
ctx.fillStyle = 'white';
ctx.fill();
})
// Update the height and width when the window size changes
window.addEventListener('resize', e => {
canvas.width = window.innerWidth * 0.7
canvas.height = window.innerHeight * 0.7
})
body {
padding: 0;
margin: 0;
}
canvas {
background-color: black;
display: block;
position: absolute;
left: 0;
right: 0;
bottom: 0;
top: 0;
margin: auto;
}
<canvas></canvas>
I took my snippet from my answer to create a full screen canvas.
I added this for mouse movement:
let User = { x: 0, y: 0 };
//controles if the mouse is moving
window.addEventListener(
"mousemove",
e => {
User.x = e.clientX;
User.y = e.clientY;
},
false
);
Uncomment: cvs.ctx.drawImage(image, User.x, User.y); in the ShowImage() function to draw an image at the mouse x and y position.
Mind to replace the path of the image source: image.src = "Your/Path/To/Image.png";
/**
* #author RensvWalstijn. GitHub: https://github.com/RensvWalstijn
* Sets the canvas properties.
* #param {object} Cvs Give the html canvas Id.
* #param {boolean} Fullscreen Change the canvas fullscreen default false.
* #param {string} Dimension Change the canvas dimension default "2d".
* #return {object}
*/
function NewCanvas(cvs, fullscreen, dimension) {
if (!dimension) dimension = "2d";
var ctx = cvs.getContext(dimension);
if (fullscreen) {
cvs.style.position = "fixed";
cvs.style.left = cvs.x = 0;
cvs.style.top = cvs.y = 0;
} else {
var rect = cvs.getBoundingClientRect();
cvs.x = rect.left;
cvs.y = rect.top;
}
cvs.ctx = ctx;
cvs.dimension = dimension;
cvs.fullscreen = fullscreen;
return cvs;
}
/**
* #author RensvWalstijn. GitHub: https://github.com/RensvWalstijn
* Updates the canvas width and hight.
* #param {object} Cvs NewCanvas() object.
* #param {boolean} Clear Change the canvas clear default true.
*/
function UpdateCvs(cvs) {
if (cvs.fullscreen) {
//if the width is not the same resize the canvas width
if (window.innerWidth != cvs.width) {
cvs.width = window.innerWidth;
}
//if the height is not the same resize the canvas height
if (window.innerHeight != cvs.height) {
cvs.height = window.innerHeight;
}
} else {
let rect = cvs.getBoundingClientRect();
cvs.x = rect.left;
cvs.y = rect.top;
}
}
function ClearCvs(cvs) {
if (cvs.dimension == "2d")
// set fillRect to clearRect to clear all of the canvas
// fillRect is used here to show the full canvas
cvs.ctx.fillRect(0, 0, cvs.width, cvs.height);
}
/**
* #author RensvWalstijn. GitHub: https://github.com/RensvWalstijn
* get html element by id.
* #param {string} id give the html element id.
* #return {object} document.getElementById(id);
*/
function GetId(id) { return document.getElementById(id) }
// To create your canvas object.
var canvas = NewCanvas(GetId("yourCanvasId"), true);
// If you want to update your canvas size use this:
window.addEventListener("resize", function() {
UpdateCvs(canvas);
});
let User = { x: 0, y: 0 };
//controles if the mouse is moving
window.addEventListener(
"mousemove",
e => {
User.x = e.clientX;
User.y = e.clientY;
},
false
);
// Set it to current width
UpdateCvs(canvas);
ClearCvs(canvas);
// create an image
let image = new Image();
image.src = "Your/Path/To/Image.png";
function ShowImage(cvs) {
// Use this line to draw your image.
// cvs.ctx.drawImage(image, User.x, User.y);
// Shows where your image will be drawn.
cvs.ctx.clearRect(User.x, User.y, 100, 100);
}
function Update() {
ClearCvs(canvas);
ShowImage(canvas);
// keeps it looping
window.requestAnimationFrame(Update)
}
// Init the loop
Update();
<canvas id="yourCanvasId"></canvas>

Resize image and rotate canvas 90 degrees

There's quite a few topics here about rotating images with canvas on js. I read most of them, and couldn't figure out a solution for my problem.
I'm receiving an image (from an upload component) of whatever resolution. I'm resizing it to 1024x768 like:
var canvas = document.createElement('canvas');
var ctx = canvas.getContext("2d");
if (img.width >= img.height) {
canvas.width = 1024;
canvas.height = 768;
} else {
canvas.width = 768;
canvas.height = 1024;
}
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
It works fine.
But on Safari/iOs, when I take a picture and upload, the image has ALWAYS a higher width value than height, so the code above doesn't work.
So I decided to use exif-js to detect the image's orientation. When the Orientation attribute is higher than 4, I need to rotate the image 90 degrees, and swap height and width values.
I tried to rotate the image like this:
canvas.width = 768; // swapping values
canvas.height = 1024;
ctx.translate(canvas.width/2, canvas.height/2); // translate to center
ctx.rotate(Math.PI/2); // rotate 90 degrees
ctx.drawImage(img, -img.width/2,-img.height/2); // not sure of the dx and dy values here...
The image is rotated. But it has taken just a small portion of the original image to display on the canvas, so it feels "zoomed in"... it seems that I'm using the wrong values on the drawImage method, but not sure how to fix.
How can I fix this rotation with fixed height and width values?
To rotate 90 deg clockwise on a new canvas.
const canvas = document.createElement("canvas");
canvas.width = image.height;
canvas.height = image.width;
const ctx = canvas.getContext("2d");
ctx.setTransform(
0,1, // x axis down the screen
-1,0, // y axis across the screen from right to left
image.height, // x origin is on the right side of the canvas
0 // y origin is at the top
);
ctx.drawImage(image,0,0);
ctx.setTransform(1,0,0,1,0,0); // restore default
If you need to scale the image to fit a size (assuming image will be rotated)
const width = 1024; // after rotation
const height = 768; // after rotation
const scale = width / image.height; // how much to scale the image to fit
const canvas = document.createElement("canvas");
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext("2d");
ctx.setTransform(
0,scale, // x axis down the screen
-scale,0, // y axis across the screen from right to left
width, // x origin is on the right side of the canvas
0 // y origin is at the top
);
ctx.drawImage(image,0,0);
ctx.setTransform(1,0,0,1,0,0); // restore default

How can i resize my canvas image

page 1:
//Calls the function from page 2 and the callback image is set as the source of the control.
previewImage(current, function(img) {
jQuery(".mapItem").attr("src",img.src);
});
page 2:
//The functions callback returns an image which we use in page 1 (above)
var canvas = document.createElement('canvas');
context = canvas.getContext('2d');
context.drawImage(this.m_Images[i],0,0,canvas.width,canvas.height);
var t = new Image();
t.src = canvas.toDataURL();
callback(t);
The issue:
I have 2 JavaScript pages, the first one has an image control and the second one has a function that returns a callback as an image.
My control in page 1 (.mapItem) has a height and width of 75.2px (fixed). The image that is coming from the callback however will have a different size each time e.g one day it can be 200px * 300px and one day it can be 150px * 200px etc
How can I clip or CUT the image of the callback? I want the image (t) to zero (0) as x and y starting points and then clip the image where ever the .mapItem control height and width is.
I need this to be proportional ratio. So I can't just add the following code:
context.drawImage(this.m_Images[i],0,0,72.5,72.5); because this will ruin the image as we dont even know if it is square shaped.
Thanks in advance :)
You can determine the proportions of callback image and then apply them to the page 1 image thus:
Let's assume that the callback image is 300x200px. The ratio of the image's height-to-width can be expressed as...
var ratio = callbackImage.height / callbackImage.width;
...or, in this case...
var ratio = 200 / 300; // 0.666...
We know the width of the page 1 canvas is 72.5 so we can apply the ratio to that value to determine the proportional height of the callback Image like so...
var canvasWidthHeight = 72.5;
var imageHeight = canvasWidthHeight * ratio; // 48.333...
To center the callback Image on the page 1 canvas calculate the y offset like so..
var y = (canvasWidthHeight - imageHeight) / 2;
...and now you can use the canvas drawImage method with these values...
context.drawImage(
this.m_Images[i],
0, y,
canvasWidthHeight, imageHeight
);
If the callback image was higher than it was wide you'd apply the ratio the page 1 canvas dimensions to work out the x offset rather than the y offest. If the callback image was square then its ratio would be 1.0 and you could simply paint it into the square page 1 canvas at
context.drawImage(
this.m_Images[i],
0, 0,
canvasWidthHeight, canvasWidthHeight
);
All together the code might look something like this...
var image = this.m_Images[i];
var canvas = {
width: 72.5,
height: 72.5
};
var wh = 0;
var ratio = image.height / image.width;
if (image.width > image.height) { // landscape
wh = canvas.width * ratio;
context.drawImage(
image,
0, (canvas.height - wh) / 2,
canvas.width, wh
);
} else if (image.width < image.height) { // portrait
ratio = image.width / image.height;
wh = canvas.height * ratio;
context.drawImage(
image,
(canvas.width - wh) / 2, 0,
wh, canvas.height
);
} else { // square
context.drawImage(
image,
0, 0,
canvas.width, canvas.height
);
}
Hope that helps. ;)
EDIT: You may need to ensure that the new Image() has fully loaded before initiating the callback. You can do this with the following snippet...
// callback preparation code as before...
var t = new Image();
t.addEventListener('load', function() {
callback(this);
});
t.src = canvas.toDataURL();

Can I center canvas image

I'm drawing a large canvas image as a background, the image is larger that the window size. I'm wondering if theres a way for me to center the image to fit on full screen. If so, how? this is what I'm doing:
var canvas = document.getElementById('canvas'),
context = canvas.getContext('2d');
window.addEventListener('resize', resizeCanvas, false);
function resizeCanvas() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
drawStuff();
}
resizeCanvas();
function drawStuff() {
var imageObj = new Image();
imageObj.onload = function() {
context.drawImage(imageObj, 69, 50);
};
imageObj.src = '/resources/img/bg.png';
}
Here is an optional way of centering an image to canvas not using transform (also see note below):
imageObj.onload = function() {
var x = (canvas.width - this.width ) * 0.5, // this = image loaded
y = (canvas.height - this.height) * 0.5;
ctx.drawImage(this, x, y);
};
Since the image is larger than the canvas x and y will be negative in this case, which is perfectly fine. If the image was smaller it would work just as fine too. If you do the drawing outside the load handler you would of course need to use imageObj instead of this.
NOTE: The way you have set up your resize handler is not the best way to handle image repositioning - you should only load the image once, then reuse that object. As resizing typically creates a number of events it would trigger an equal number of image reloads.
For this to work properly you could do something like this instead:
var canvas = document.getElementById('canvas'),
context = canvas.getContext('2d'),
imageObj = new Image(); // declare globally
imageObj.onload = function() {
// now set up handler when image is actually loaded
// - else drawImage will fail (width, height is not available and no data)
window.addEventListener('resize', resizeCanvas, false);
// initial call to draw image first time
resizeCanvas();
};
imageObj.src = '/resources/img/bg.png';
function resizeCanvas() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
drawStuff();
}
function drawStuff() {
var x = (canvas.width - imageObj.width ) * 0.5,
y = (canvas.height - imageObj.height) * 0.5;
ctx.drawImage(imageObj, x, y);
}
It's not perfect as the resize event queue will still be large and may lag - there are solutions for this too, for example this one (with minor modifications).
Here's how to center the image on the canvas. Any vertical or horizontal overflow will be off-canvas:
imageObj.onload = function() {
ctx.translate(canvas.width/2,canvas.height/2);
ctx.drawImage(imageObj,-imageObj.width/2,-imageObj.height/2);
ctx.translate(-canvas.width/2,-canvas.height/2);
};
Good luck with your project!
If you are adding image after uploading then you can use this, it works for me :)
var image_center_width = (canvas.width - img.width) / 2;
var image_center_height = (canvas.height - img.height) / 2;
img.set({
left : image_center_width,
top : image_center_height,
});
canvas.add(img)

Categories