5

As you can see I try to rotate an image in a canvas: enter image description here 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/

John Smith
  • 6,105
  • 16
  • 58
  • 109
  • But you're not using this `rotate()` function. You're using `ctx.rotate()`. – John Bupit Jan 09 '16 at 01:23
  • 1
    The red triangles will always be a by-product of rotating the image as long as you fill the canvas background with red. Are you asking how to shrink the rotated image to fit inside the original bounds of the canvas element? – markE Jan 09 '16 at 02:33

5 Answers5

15

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 enter image description here 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();
Blindman67
  • 51,134
  • 11
  • 73
  • 136
  • *[Thanks for your help!](http://stackoverflow.com/questions/35346296/scale-rotated-image-to-fill-html5-canvas-using-javascript-trigonometry)* – 700 Software Feb 12 '16 at 22:05
  • @GeorgeBailey You are welcome and thank you for the points as well. – Blindman67 Feb 13 '16 at 07:46
  • and do you know how to keep image moving around but keep it fill the canvas? I mean, dragging image to change it position. – 0xlkda May 07 '23 at 13:38
  • @0xlkda Brings the angle into a normalized range. Converting angles that give the same result. – Blindman67 May 11 '23 at 09:09
5

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 :)
Marko Mackic
  • 2,293
  • 1
  • 10
  • 19
1

What would be your preferred method to rotate the image?

Translate the context coordinates to the center, rotate, then draw using negative offsets,

ctx.save();
ctx.translate(width/2, height/2);
ctx.rotate(-40*Math.PI/180);
ctx.drawImage(img, -width/2, -height/2);
ctx.restore();

How would you display the full image on the canvas, without red borders?

To fit the image in the canvas at any arbitrary rotation, you'll have to make both width and height (at least) C where C=sqrt(width^2 + height^2). Translate to C/2 in both directions.

jamieguinan
  • 1,640
  • 1
  • 10
  • 14
  • 1
    I'm beginning to think the questioner wants the image scaled down to fit inside the size of the original canvas -- see the clue in the questioners comment to John Bupit. But I'm not really sure... :-// – markE Jan 09 '16 at 02:36
  • @markE ohhh, you're probably right. I think Blindman67 solved that problem. – jamieguinan Jan 09 '16 at 03:30
0

To rotate around the center, you can first translate it to the center point, then rotate, and then translate it back. Something like this:

ctx.translate(width/2, height/2);
ctx.rotate(-40*Math.PI/180);
ctx.translate(-width/2, -height/2);

// Grab the Canvas and Drawing Context
var canvas = $('#c');
var ctx = canvas.get(0).getContext('2d');

//rotate point x,y around origin with angle a, get back new point 
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];
}

// Create an image element
var img = document.createElement('IMG');

// When the image is loaded, draw it
img.onload = function() {
  var width = img.naturalWidth;
  var height = img.naturalHeight;

  canvas.attr('width', width);
  canvas.attr('height', height);

  //only to show that borders of canvas
  ctx.rect(0, 0, width, height);
  ctx.fillStyle = "red";
  ctx.fill();

  ctx.save();
  ctx.translate(width / 2, height / 2);
  ctx.rotate(-40 * Math.PI / 180); //comment this line out to see orign image
  ctx.translate(-width / 2, -height / 2);
  ctx.drawImage(img, 0, 0);
  ctx.restore();

}

// Specify the src to load the image
img.src = "http://i.imgur.com/gwlPu.jpg";
body {
  background: #CEF;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.0/jquery.min.js"></script>
<canvas id="c"></canvas>
John Bupit
  • 10,406
  • 8
  • 39
  • 75
  • Thanks for your answer. But I am searching for an solution where the border points of the image are located on border of the canvas. As you can see the canvas has to be enlarged. – John Smith Jan 09 '16 at 01:32
-2

Move centre of rotation to approximace centre of the Canvas: See the sourcecode for http://wisephoenix.org/html5/Rotation.htm Which is written in html5 and javascript.

Arif Burhan
  • 507
  • 4
  • 12