Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

After rotate, draw Image at correct position

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/

like image 883
John Smith Avatar asked Dec 05 '22 01:12

John Smith


2 Answers

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();
like image 72
Blindman67 Avatar answered Dec 09 '22 15:12

Blindman67


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 :)
like image 30
Marko Mackic Avatar answered Dec 09 '22 13:12

Marko Mackic