Deepu S A Deepu S A - 5 months ago 48
Javascript Question

To rotation an image in canvas using mouse

In my code I am loading an image in to a canvas. Then I need to resize, rotate and drag it. I managed to implement both dragging and resizing.

How can I implement rotation(along the center of the image) using mouse on this code.

My HTML page:

<!doctype html>
<html>
<head>
<link rel="stylesheet" type="text/css" media="all" href="css/reset.css" /> <!-- reset css -->
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>

<style>
body{ background-color: ivory; padding:10px;}
#canvas{border:1px solid red;}
</style>

<script>
$(function(){

var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");

var canvasOffset=$("#canvas").offset();
var offsetX=canvasOffset.left;
var offsetY=canvasOffset.top;

var startX;
var startY;
var isDown=false;


var pi2=Math.PI*2;
var resizerRadius=8;
var rr=resizerRadius*resizerRadius;
var draggingResizer={x:0,y:0};
var imageX=50;
var imageY=50;
var imageWidth,imageHeight,imageRight,imageBottom;
var draggingImage=false;
var startX;
var startY;



var img=new Image();
img.onload=function(){
imageWidth=img.width;
imageHeight=img.height;
imageRight=imageX+imageWidth;
imageBottom=imageY+imageHeight
draw(true,false);
}
img.src="https://dl.dropboxusercontent.com/u/139992952/stackoverflow/facesSmall.png";


function draw(withAnchors,withBorders){

// clear the canvas
ctx.clearRect(0,0,canvas.width,canvas.height);

// draw the image
ctx.drawImage(img,0,0,img.width,img.height,imageX,imageY,imageWidth,imageHeight);

// optionally draw the draggable anchors
if(withAnchors){
drawDragAnchor(imageX,imageY);
drawDragAnchor(imageRight,imageY);
drawDragAnchor(imageRight,imageBottom);
drawDragAnchor(imageX,imageBottom);
}

// optionally draw the connecting anchor lines
if(withBorders){
ctx.beginPath();
ctx.moveTo(imageX,imageY);
ctx.lineTo(imageRight,imageY);
ctx.lineTo(imageRight,imageBottom);
ctx.lineTo(imageX,imageBottom);
ctx.closePath();
ctx.stroke();
}

}

function drawDragAnchor(x,y){
ctx.beginPath();
ctx.arc(x,y,resizerRadius,0,pi2,false);
ctx.closePath();
ctx.fill();
}

function anchorHitTest(x,y){

var dx,dy;

// top-left
dx=x-imageX;
dy=y-imageY;
if(dx*dx+dy*dy<=rr){ return(0); }
// top-right
dx=x-imageRight;
dy=y-imageY;
if(dx*dx+dy*dy<=rr){ return(1); }
// bottom-right
dx=x-imageRight;
dy=y-imageBottom;
if(dx*dx+dy*dy<=rr){ return(2); }
// bottom-left
dx=x-imageX;
dy=y-imageBottom;
if(dx*dx+dy*dy<=rr){ return(3); }
return(-1);

}


function hitImage(x,y){
return(x>imageX && x<imageX+imageWidth && y>imageY && y<imageY+imageHeight);
}


function handleMouseDown(e){
startX=parseInt(e.clientX-offsetX);
startY=parseInt(e.clientY-offsetY);
draggingResizer=anchorHitTest(startX,startY);
draggingImage= draggingResizer<0 && hitImage(startX,startY);
}

function handleMouseUp(e){
draggingResizer=-1;
draggingImage=false;
draw(true,false);
}

function handleMouseOut(e){
handleMouseUp(e);
}

function handleMouseMove(e){

if(draggingResizer>-1){

mouseX=parseInt(e.clientX-offsetX);
mouseY=parseInt(e.clientY-offsetY);

// resize the image
switch(draggingResizer){
case 0: //top-left
imageX=mouseX;
imageWidth=imageRight-mouseX;
imageY=mouseY;
imageHeight=imageBottom-mouseY;
break;
case 1: //top-right
imageY=mouseY;
imageWidth=mouseX-imageX;
imageHeight=imageBottom-mouseY;
break;
case 2: //bottom-right
imageWidth=mouseX-imageX;
imageHeight=mouseY-imageY;
break;
case 3: //bottom-left
imageX=mouseX;
imageWidth=imageRight-mouseX;
imageHeight=mouseY-imageY;
break;
}

// enforce minimum dimensions of 25x25
if(imageWidth<25){imageWidth=25;}
if(imageHeight<25){imageHeight=25;}

// set the image right and bottom
imageRight=imageX+imageWidth;
imageBottom=imageY+imageHeight;

// redraw the image with resizing anchors
draw(true,true);

}else if(draggingImage){

imageClick=false;

mouseX=parseInt(e.clientX-offsetX);
mouseY=parseInt(e.clientY-offsetY);

// move the image by the amount of the latest drag
var dx=mouseX-startX;
var dy=mouseY-startY;
imageX+=dx;
imageY+=dy;
imageRight+=dx;
imageBottom+=dy;
// reset the startXY for next time
startX=mouseX;
startY=mouseY;

// redraw the image with border
draw(false,true);

}


}


$("#canvas").mousedown(function(e){handleMouseDown(e);});
$("#canvas").mousemove(function(e){handleMouseMove(e);});
$("#canvas").mouseup(function(e){handleMouseUp(e);});
$("#canvas").mouseout(function(e){handleMouseOut(e);});


}); // end $(function(){});
</script>

</head>

<body>
<p>Resize the image using the 4 draggable corner anchors</p>
<p>You can also drag the image</p>
<canvas id="canvas" width=350 height=350></canvas>
</body>
</html>

Answer

Here’s how to use a drag-handle to rotate an image

enter image description hereenter image description here

The mousedown event handler hit-tests if the user is starting to drag the rotation-handle.

This hit-testing is made easier with context.isPointInPath(x,y) which tests whether a specified [x,y] coordinate is inside the most recently drawn path (Conveniently, the rotation-handle is actually a path).

So mousedown activates the drag-handle like this:

  • Calculate the current mouseX and mouseY.
  • Redraw the rotation handle (required because isPointInPath hit-tests just the most recent path)
  • Set the isDown flag if the user did click in the rotation handle.

The mousedown code looks like this:

function handleMouseDown(e){
  mouseX=parseInt(e.clientX-offsetX);
  mouseY=parseInt(e.clientY-offsetY);
  drawRotationHandle(false);
  isDown=ctx.isPointInPath(mouseX,mouseY);
}

Yes...we could have simply hit-tested a circle on the end of the rotation-handle, but using isPointInPath will allow you to draw whatever fancy rotation handle you desire.

And isPointInPath has another nice benefit. When the context containing the path is rotated, isPointInPath will hit-test the rotated path for you. This means you don't have to code the math to unrotate the mouse coordinates to do the hit testing--it's done for you!

The mousemove handler redraws the rotatable image at the angle specified by the rotation-handle:

  • If the isDown flag is not set, just return (the user is not dragging the rotation-handle).
  • Calculate the current mouseX and mouseY.
  • Calculate the current angle of the rotation-handle.
  • Redraw the rotatable image at the current angle.

The mousemove code looks like this:

function handleMouseMove(e){
  if(!isDown){return;}

  mouseX=parseInt(e.clientX-offsetX);
  mouseY=parseInt(e.clientY-offsetY);
  var dx=mouseX-cx;
  var dy=mouseY-cy;
  r=Math.atan2(dy,dx);
  draw();
}

The image is drawn at the specified rotation using context's transform methods

function drawRect(){
    ctx.save();
    ctx.translate(cx,cy);
    ctx.rotate(r);
    ctx.drawImage(img,0,0);
    ctx.restore();
}

Finally, the mouseup and mouseout handlers stop the drag operation by clearing the isDown flag.

function handleMouseUp(e){
  isDown=false;
}

function handleMouseOut(e){
  isDown=false;
}

Here is code and a Fiddle: http://jsfiddle.net/m1erickson/QqwKR/

<!doctype html>
<html>
<head>
<link rel="stylesheet" type="text/css" media="all" href="css/reset.css" /> <!-- reset css -->
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>

<style>
    body{ background-color: ivory; }
    #canvas{border:1px solid red;}
</style>

<script>
$(function(){

    var canvas=document.getElementById("canvas");
    var ctx=canvas.getContext("2d");

    var canvasOffset=$("#canvas").offset();
    var offsetX=canvasOffset.left;
    var offsetY=canvasOffset.top;

    var isDown=false;

    var cx=canvas.width/2;
    var cy=canvas.height/2;
    var w;
    var h;
    var r=0;

    var img=new Image();
    img.onload=function(){
        w=img.width/2;
        h=img.height/2;
        draw();
    }
    img.src="facesSmall.png";


    function draw(){
        ctx.clearRect(0,0,canvas.width,canvas.height);
        drawRotationHandle(true);
        drawRect();
    }

    function drawRect(){
        ctx.save();
        ctx.translate(cx,cy);
        ctx.rotate(r);
        ctx.drawImage(img,0,0,img.width,img.height,-w/2,-h/2,w,h);
        ctx.restore();
    }

    function drawRotationHandle(withFill){
        ctx.save();
        ctx.translate(cx,cy);
        ctx.rotate(r);
        ctx.beginPath();
        ctx.moveTo(0,-1);
        ctx.lineTo(w/2+20,-1);
        ctx.lineTo(w/2+20,-7);
        ctx.lineTo(w/2+30,-7);
        ctx.lineTo(w/2+30,7);
        ctx.lineTo(w/2+20,7);
        ctx.lineTo(w/2+20,1);
        ctx.lineTo(0,1);
        ctx.closePath();
        if(withFill){
            ctx.fillStyle="blue";
            ctx.fill();
        }
        ctx.restore();
    }

    function handleMouseDown(e){
      mouseX=parseInt(e.clientX-offsetX);
      mouseY=parseInt(e.clientY-offsetY);
      drawRotationHandle(false);
      isDown=ctx.isPointInPath(mouseX,mouseY);
      console.log(isDown);
    }

    function handleMouseUp(e){
      isDown=false;
    }

    function handleMouseOut(e){
      isDown=false;
    }

    function handleMouseMove(e){
      if(!isDown){return;}

      mouseX=parseInt(e.clientX-offsetX);
      mouseY=parseInt(e.clientY-offsetY);
      var dx=mouseX-cx;
      var dy=mouseY-cy;
      r=Math.atan2(dy,dx);
      draw();
    }

    $("#canvas").mousedown(function(e){handleMouseDown(e);});
    $("#canvas").mousemove(function(e){handleMouseMove(e);});
    $("#canvas").mouseup(function(e){handleMouseUp(e);});
    $("#canvas").mouseout(function(e){handleMouseOut(e);});

}); // end $(function(){});
</script>

</head>

<body>
    <p>Rotate by dragging blue rotation handle</p>
    <canvas id="canvas" width=300 height=300></canvas>
</body>
</html>