I've created a basic HTML5 image slider where images move from top to bottom in a canvas.
I want all the images rotated at angle of 5 degrees. When I tried it out there seems to be some
distortion to the canvas and the image is not properly rotated.
I've tried the method for rotation mentioned in the below post
How do I rotate a single object on an html 5 canvas?
Fiddle - http://jsfiddle.net/DS2Sb/
Code
this.createImage = function (image, width, height) {
var fbWallImageCanvas = document.createElement('canvas');
var fbWallImageCanvasContext = fbWallImageCanvas.getContext('2d');
fbWallImageCanvas.width = width;
fbWallImageCanvas.height = height;
fbWallImageCanvasContext.save();
fbWallImageCanvasContext.globalAlpha = 0.7;
this.rotateImage(image, 0, 0, width, height, 5, fbWallImageCanvasContext);
fbWallImageCanvasContext.drawImage(image, width, height);
fbWallImageCanvasContext.restore();
return fbWallImageCanvas;
};
this.rotateImage = function (image, x, y, width, height, angle, context) {
var radian = angle * Math.PI / 180;
context.translate(x + width / 2, y + height / 2);
context.rotate(radian);
context.drawImage(image, width / 2 * (-1), height / 2 * (-1), width, height);
context.rotate(radian * (-1));
context.translate((x + width / 2) * (-1), (y + height / 2) * (-1));
};
The distortion you see is due to the fact that a rotated image will only fit in a larger canvas. So what we see is a rectangle view on a rotated image.
The computations are not that easy to get things done properly, but instead of pre-computing the rotated image, you might rotate them just when you draw them, which lets you also change the angle whenever you want (and opacity also btw).
So i simplified createImage, so that it just stores the image in a canvas (drawing a canvas is faster than drawing an image) :
this.createImage = function(image , width, height) {
var fbWallImageCanvas = document.createElement('canvas');
fbWallImageCanvas.width = width;
fbWallImageCanvas.height = height;
var fbWallImageCanvasContext = fbWallImageCanvas.getContext('2d');
fbWallImageCanvasContext.drawImage(image,0,0);
return fbWallImageCanvas;
};
And i changed drawItem so it draws the image rotated :
this.drawItem = function(ct) {
var angle = 5;
var radian = angle * Math.PI/180;
ct.save();
ct.translate(this.x + this.width/2 , this.y + this.height/2);
ct.rotate(radian);
ct.globalAlpha = 0.7;
ct.drawImage(fbc, - this.width/2, -this.height/2 , this.width, this.height);
ct.restore();
this.animate();
};
You'll probably want to refactor this, but you see the idea.
fiddle is here :
http://jsfiddle.net/DS2Sb/1/
Here is a link to a small html5 tutorial I created a while ago:
https://bitbucket.org/Garlov/html5-sidescroller-game-source
And here is the rotate code:
// save old coordinate system
ctx.save();
// move to the middle of where we want to draw our image
ctx.translate(canvas.width/2, canvas.height-64);
// rotate around that point
ctx.rotate(0.02 * (playerPosition.x));
//draw playerImage
ctx.drawImage(playerImage, -playerImage.width/2, -playerImage.height/2);
//and restore coordniate system to default
ctx.restore();
Related
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 );
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)
I have a code sample where I am adjusting a large image to a smaller size with the canvas matching the size of the image. There is a rotate right functionality that will rotate the image inside of the canvas which will adjust the canvas size. I almost have it perfectly working but the image is perfect size only when original or upside down. I can't get the height to be right ratio when the image is rotated on left or right. Here is my jsfiddle and code which is modified code from a different fiddle. Image is just used as test.
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var canvasWidth = 400;
var canvasHeight;
var degrees=0;
var image=document.createElement("img");
image.onload=function(){
canvas.width = canvasWidth;
canvasHeight = 400 / (image.width / image.height);
canvas.height = canvasHeight;
ctx.drawImage(image,0,0,canvas.width,canvas.height);
}
image.src="https://www.magezinepublishing.com/equipment/images/equipment/Lumix-DMCFZ330-5824/highres/Panasonic-Lumix-FZ330-Wide-P1010001_1438873612.jpg";
$("#clockwise").click(function(){
degrees+=90
if (degrees >= 360) degrees = 0;
if (degrees === 0 || degrees === 180 ) {
canvas.width = canvasWidth;
canvas.height = canvasHeight;
}
else {
// swap
canvas.width = canvasHeight;
canvas.height = canvasWidth;
}
ctx.save();
// you want to rotate around center of canvas
ctx.translate(canvas.width/2,canvas.height/2);
ctx.rotate(degrees*Math.PI/180);
ctx.drawImage(image, -canvas.width*0.5, -canvas.height*0.5, canvas.width, canvas.height);
ctx.restore();
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<canvas id="canvas" ></canvas><br>
<button id="clockwise">Rotate right</button>
jsfiddle
Any help is appreciated.
Remember you rotate your image. You are changing the aspect ratio of the image when using the swapped canvas width/height in the image draw call. By your calculations, the original image, rotated, should already fit the canvas. The image you produce is also stretched because of this.
Use ctx.drawImage(image, -canvasWidth*0.5, -canvasHeight*0.5, canvasWidth, canvasHeight);.
Else said, you want to draw the same image, then rotate/translate it with canvas calls. But what you are doing is draw a different sized, stretched image when being rotated 90° or 270° and then rotate/translate that.
To rotate an image in 90deg steps.
Rather the use the translate and scale you can rotate the image by directly setting the tranformation matrix.
ctx.setTransform(a,b,c,d,e,f);
The arguments are as follows
a,b the vector describing the direction and size of a pixels x axis
c,d the vector describing the direction and size of a pixels y axis
e,f the coordinate of the origin (where 0,0 will be)
All these are in canvas pixel coordinates. The default is a = 1, b =0 x axis of a pixel is 1 pixel across, and 0 down, c = 0, d = 1 y axis 0 pixels across and one pixel down, e = 0, f = 0 origin at the top left.
To rotate the image 90 deg clockwise you want the xAxis to go down the canvas and the y axis to go from right to left. The origin is shifted to the top right of the canvas.
ctx.setTransform(
0,1, // x axis down
-1,0 // y axis from left to right
ctx.canvas.height, // origin x and y to top right
0,
)
ctx.drawimage(image,0,0);
As you are scaling by 0.5 this means the pixels will be half the size, and as you are drawing an image you want the origin so that the image fits the image.
// rotate image 90 deg and scale 0.5
ctx.setTransform(
0,0.5, // x axis down
-0.5,0 // y axis from left to right
image.height * 0.5, // origin x and y to top right
0,
)
ctx.drawimage(image,0,0);
You can do the same for each additional rotation
// rotate 180 scale 0.5
ctx.setTransform(-0.5,0,0,-0.5, image.width * 0.5,image.height * 0.5);
// rotate -90 scale 0.5
ctx.setTransform(0,-0.5,0.5,0, 0,image.width* 0.5);
The image dimensions for each rotation is as follows
// for 0deg and 180 deg rotation
canvas.width = image.width * 0.5;
canvas.width = image.height * 0.5;
// for 90deg and -90 deg rotation
canvas.width = image.height * 0.5;
canvas.width = image.width * 0.5;
To restore the transform to the default just set the transform to the identity matrix
ctx.setTransform(1,0,0,1,0,0);
The problem I am having is that Canvas will not draw a ShadowBlur effect when drawing my image if I rotate the Canvas at all to draw it. It works perfectly fine if I set the rotation value to 0 degrees.
I threw together a jsfiddle real fast, the image is pixelated and distorted but anyhow it reproduces the issue https://jsfiddle.net/zsw7wkv4/1/
Edit: Seems to be a Chrome only issue
Here is the code
var canvas = document.getElementById('GameCanvas');
var ctx = canvas.getContext("2d");
var asset = card.asset;
// set the card height based off the width
var height = width * 2.66;
// save the canvas before rotating
ctx.save();
// hover effect for drawn card
if (core.information.xoffset >= left && core.information.xoffset <= left + width && core.information.yoffset >= top && core.information.yoffset <= top + height) {
ctx.shadowColor = 'white';
ctx.shadowBlur = 15;
}
// translate to the center of the card
ctx.translate(core.information.pwidth * (left + width/2), core.information.pheight * (top + height/2));
// rotate the canvas for the card
ctx.rotate(rotation * Math.PI/180);
// translate back
ctx.translate(-core.information.pwidth * (left + width/2), -core.information.pheight * (top + height/2));
// draw the card
ctx.drawImage(asset, core.information.pwidth * left, core.information.pheight * top, core.information.pwidth * width, core.information.pheight * height);
// restore the canvas after rotating
ctx.restore();
Have same issue.
Draw your blured image to a temp canvas first, then draw that as an image (rotated) on your "final" canvas.
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 );