As you can see I try to rotate an image in a canvas: https://jsfiddle.net/1a7wpsp8/4/
Because I rotated the image around [0, image_height]
, Image-Information got lost. Parts of the head are missing and the height of the Canvas is now to small to display the whole rotated image.
To fix this problem I think I have to move the rotate-origin more to the right and increase the canvas height.
I added a method to get the rotated point around the origin, by a specific angle:
function rotate(x, y, a) {
var cos = Math.cos,
sin = Math.sin,
a = a * Math.PI / 180,
xr = x * cos(a) - y * sin(a);
yr = x * sin(a) + y * cos(a);
return [xr, yr];
}
With that I tried to calculate the new rotating origin, but failed.
What would be your preferred method to rotate the image? How would you display the full image on the canvas, without red borders? Thanks https://jsfiddle.net/1a7wpsp8/4/
To rotate the image and scale it so that at no point the edge of the image is inside the canvas bounds you need to workout the distance from the center of the canvas to a corner.
// assuming canvas is the canvas and image is the image.
var dist = Math.sqrt(Math.pow(canvas.width/2,2)+Math.pow(canvas.height/2,2));
Now you need to find the distance from the image center to its closest edge;
var imgDist = Math.min(image.width,image.height)/2
So now we have the information we need to get the minimum scale the image can be to always fit the canvas when drawn at the center of the canvas.
var minScale = dist / imgDist;
Now to rotate around the center of the image, scale it and position it at the center of the canvas
First calculate the direction and size of a pixel's top edge (X axis)
// ang is the rotation in radians
var dx = Math.cos(ang) * minScale;
var dy = Math.sin(ang) * minScale;
Then create the transformation matrix where the first two numbers are the X axis the second two are the Y axis that is at 90deg clockwise from the X axis and the last two that are the origin in canvas pixels coordinates from the canvas origin at the top left corner.
// ctx is the canvas 2D context
ctx.setTransform(dx, dy, -dy, dx, canvas.width / 2, canvas.height / 2);
Now draw the image offset by half its height and width.
ctx.drawImage(image,-image.width / 2, - image.height / 2);
And last thing is just to restore the default transformation
ctx.setTransform(1,0,0,1,0,0);
And you are done.
As a function
// ctx is canvas 2D context
// angle is rotation in radians
// image is the image to draw
function drawToFitRotated(ctx, angle, image){
var dist = Math.sqrt(Math.pow(ctx.canvas.width /2, 2 ) + Math.pow(ctx.canvas.height / 2, 2));
var imgDist = Math.min(image.width, image.height) / 2;
var minScale = dist / imgDist;
var dx = Math.cos(angle) * minScale;
var dy = Math.sin(angle) * minScale;
ctx.setTransform(dx, dy, -dy, dx, ctx.canvas.width / 2, ctx.canvas.height / 2);
ctx.drawImage(image, -image.width / 2, - image.height / 2);
ctx.setTransform(1, 0, 0, 1, 0, 0);
}
UPDATE As this is an interesting problem I had a look to see if I could work out how to best fit the rotated image ensuring that the image fills the canvas at all times but also that as much of the image is shown. The solution means that as the image rotates the scale changes and because the sides come to corners the scale has 4 points where it suddenly stops shrinking and starts growing again. Nevertheless this is good for static images that are rotated but need to fill the canvas.
The best way to describe this is with a picture The image is green, the canvas is red. The rotation of the image is the angle R We need to find the length of the lines C-E and C-F. Ih and Iw are half the image height and width.
From the canvas we work out the length of lines C-B which is the square root of the sum of half the canvas height (ch) and width (cw) squared.
Then we need the angle A which is acos of (ch) / length line C-B
Now that we have the angle A we can work out the angle A1 and A2. Now we have a length C-B and angle A1, A2 we can solve the right triangles C-E-B and C-F-B for C-E and C-F. Which is C-E = length C-B * cos(A1) and C-F = length C-B * cos(A2)
Then get the scales for the image height which is length C-E divide Ih and C-E divide Iw. As the image aspect may not match the rectangle we need to fit we need to keep the maximum of the two scale.
So we end up with a scale that fits.
There is one more problem. The math works fine for angles 0 -90 deg but fails for the rest. Luckily the problem is symmetrical for each quadrant. For angles > 180 we mirror and rotate back 180 and then for angles > 90 < 180 we reverse and rotate back 90 deg. I do it all in radians in the demo. We also make sure that the angles given are always in the range 0 - 360 (0 to Math.PI * 2).
Then its just a matter of creating the matrix and drawing the image.
In the demo I have added two Boxes. The red BLUE one is a half sized copy of the canvas outline and the Blue RED is the image half sized. I do this so you can see why the images scale is not smooth as it rotates.
I will keep this function for myself as well its a handy one to have.
Se the Demo code for the new function.
demo = function(){
/** fullScreenCanvas.js begin **/
var canvas = (function(){
var canvas = document.getElementById("canv");
if(canvas !== null){
document.body.removeChild(canvas);
}
// creates a blank image with 2d context
canvas = document.createElement("canvas");
canvas.id = "canv";
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
canvas.style.position = "absolute";
canvas.style.top = "0px";
canvas.style.left = "0px";
canvas.style.zIndex = 1000;
canvas.ctx = canvas.getContext("2d");
document.body.appendChild(canvas);
return canvas;
})();
var ctx = canvas.ctx;
var image = new Image();
image.src = "http://i.imgur.com/gwlPu.jpg";
var w = canvas.width;
var h = canvas.height;
var cw = w / 2; // half canvas width and height
var ch = h / 2;
// Refer to diagram in answer
function drawBestFit(ctx, angle, image){
var iw = image.width / 2; // half image width and height
var ih = image.height / 2;
// get the length C-B
var dist = Math.sqrt(Math.pow(cw,2) + Math.pow(ch,2));
// get the angle A
var diagAngle = Math.asin(ch/dist);
// Do the symmetry on the angle
a1 = ((angle % (Math.PI *2))+ Math.PI*4) % (Math.PI * 2);
if(a1 > Math.PI){
a1 -= Math.PI;
}
if(a1 > Math.PI/2 && a1 <= Math.PI){
a1 = (Math.PI/2) - (a1-(Math.PI/2));
}
// get angles A1, A2
var ang1 = Math.PI/2 - diagAngle - Math.abs(a1);
var ang2 = Math.abs(diagAngle - Math.abs(a1));
// get lenghts C-E and C-F
var dist1 = Math.cos(ang1) * dist;
var dist2 = Math.cos(ang2) * dist;
// get the max scale
var scale = Math.max(dist2/(iw),dist1/(ih));
// create the transform
var dx = Math.cos(angle) * scale;
var dy = Math.sin(angle) * scale;
ctx.setTransform(dx, dy, -dy, dx, cw, ch);
ctx.drawImage(image, -iw, - ih);
// draw outline of image half size
ctx.strokeStyle = "red";
ctx.lineWidth = 2 * (1/scale);
ctx.strokeRect(-iw / 2, -ih / 2, iw, ih)
// reset the transform
ctx.setTransform(1, 0, 0, 1, 0, 0);
// draw outline of canvas half size
ctx.strokeStyle = "blue";
ctx.lineWidth = 2;
ctx.strokeRect(cw - cw / 2, ch - ch / 2, cw, ch)
}
// Old function
function drawToFitRotated(ctx, angle, image){
var dist = Math.sqrt(Math.pow(cw,2) + Math.pow(ch,2));
var imgDist = Math.min(image.width, image.height) / 2;
var minScale = dist / imgDist;
var dx = Math.cos(angle) * minScale;
var dy = Math.sin(angle) * minScale;
ctx.setTransform(dx, dy, -dy, dx, cw, ch);
ctx.drawImage(image, -image.width / 2, - image.height / 2);
ctx.setTransform(1, 0, 0, 1, 0, 0);
}
var angle = 0;
function update(){ // animate the image
if(image.complete){
angle += 0.01;
drawBestFit(ctx,angle,image);
}
// animate until resize then stop remove canvas and flag that it has stopped
if(!STOP){
requestAnimationFrame(update);
}else{
STOP = false;
var canv = document.getElementById("canv");
if(canv !== null){
document.body.removeChild(canv);
}
}
}
update();
}
/** FrameUpdate.js end **/
var STOP = false; // flag to tell demo app to stop
// resize Tell demo to stop then wait for it to stop and start it again
function resizeEvent(){
var waitForStopped = function(){
if(!STOP){ // wait for stop to return to false
demo();
return;
}
setTimeout(waitForStopped,200);
}
STOP = true;
setTimeout(waitForStopped,100);
}
// if resize stop demo and restart
window.addEventListener("resize",resizeEvent);
// start demo
demo();
Here is a fiddle with a code for your image rotation :) http://jsfiddle.net/6ZsCz/1911/
HTML :
canvas id="canvas" width=300 height=300></canvas><br>
<button id="clockwise">Rotate right</button>
<button id="counterclockwise">Rotate left</button>
JS:
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var width=0;
var height=0;
var diagonal = 0;
var angleInDegrees=0;
var image=document.createElement("img");
image.onload=function(){
width = image.naturalWidth;
height = image.naturalHeight;
diagonal=Math.sqrt(Math.pow(width,2)+Math.pow(height,2));
ctx.canvas.height = diagonal;
ctx.canvas.width = diagonal;
ctx.clearRect(0,0,canvas.width,canvas.height);
ctx.save();
ctx.translate(diagonal/2,diagonal/2);
ctx.rotate(0);
ctx.drawImage(image,-width/2,-height/2);
ctx.restore();
}
image.src="http://i.imgur.com/gwlPu.jpg";
$("#clockwise").click(function(){
angleInDegrees+=45;
drawRotated(angleInDegrees);
});
$("#counterclockwise").click(function(){
angleInDegrees-=45;
drawRotated(angleInDegrees);
});
function drawRotated(degrees){
ctx.clearRect(0,0,canvas.width,canvas.height);
ctx.save();
ctx.translate(diagonal/2,diagonal/2);
ctx.rotate(degrees*Math.PI/180);
ctx.drawImage(image,-width/2,-height/2);
ctx.restore();
}
CSS(for canvas background):
canvas{border:1px solid red;
background:red;border-radius:50%} // to see exectly centered :)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With