在画布上旋转现有图像

3

我想要旋转一个已经在HTML画布上绘制的图像,操作如下:

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

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

var startX;
var startY;
var isDown = false;


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



var img = new Image();
img.crossOrigin='anonymous';
img.onload = function () {

    var ratio = img.width / img.height;

    imageWidth = 71;
    imageHeight = imageWidth / ratio;
    imageY = (245-imageHeight)/2;
    if (imageHeight > 245) {
        imageHeight = 245;
        imageWidth = imageHeight * ratio;
        imageY = 0;
    }

    imageX = ((canvas.width-imageWidth)/2);
    imageY = ((canvas.height-imageHeight)/2);

    imageRight = imageX + imageWidth;
    imageBottom = imageY + imageHeight;

    draw(true, false);
}

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;
        }

        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);

    }


}


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

我希望这很简单:
    function rotateImage() {
        var rotateCanvas = document.getElementById('editorCanvas');
        var rotateContext = canvas.getContext('2d');
        rotateContext.rotate(90 * Math.PI / 180);
    }

但我认为唯一的方法是重新绘制图像?

在画布上旋转图像是否有不重新绘制它的方法,如果没有,我该如何在画布上旋转图像?


1
图像只是简单地绘制在画布上,它并没有“附着”在上面。因此,如果您想要更改它,比如移动或旋转,您需要擦除画布并重新绘制。您可以在不重新绘制的情况下完成操作,但是您将看到旧的未擦除图像与旋转后的图像重叠的位置。 - Spencer Wieczorek
3个回答

3

最简单的方法是使用“copy”合成模式将画布绘制到自身。当绘制新内容时,“copy”会清除背景:

示例

var ctx = document.querySelector("canvas").getContext("2d");
ctx.moveTo(75, 10);                      // some graphics...
ctx.lineTo(30, 140);
ctx.lineTo(120, 140);
ctx.fill();

// prep canvas for next actions
ctx.translate(75, 75);                   // translate to canvas center
ctx.rotate(Math.PI*0.5);                 // add rotation transform
ctx.globalCompositeOperation = "copy";   // set comp. mode to "copy"

// rotate on each click
document.querySelector("button").onclick = function() {
  // canvas uses itself as source, source region, target offset to
  // compensate for translation above so it rotates around center:
  ctx.drawImage(ctx.canvas,  0, 0, 150, 150,  -75, -75, 150, 150);
};
body {background:#eee}
<canvas width=150></canvas>
<button>Rotate</button>

示例2 - 每次设置完成

var ctx = document.querySelector("canvas").getContext("2d");
ctx.moveTo(75, 10);                      // some graphics...
ctx.lineTo(30, 140);
ctx.lineTo(120, 140);
ctx.fill();

// rotate on each click
document.querySelector("button").onclick = function() {
  ctx.save();
  // prep canvas for rotation
  ctx.translate(75, 75);                   // translate to canvas center
  ctx.rotate(Math.PI*0.5);                 // add rotation transform
  ctx.globalCompositeOperation = "copy";   // set comp. mode to "copy"
  ctx.drawImage(ctx.canvas,  0, 0, 150, 150,  -75, -75, 150, 150);
  ctx.restore();
};

// or use, instead of save/restore, at the end:
// ctx.setTransform(1,0,0,1,0,0);
// ctx.globalCompositeOperation = "source-over";
body {background:#eee}
<canvas width=150></canvas>
<button>Rotate</button>


感谢您的回答,虽然我可以看到它在示例中运行,并且当我逐行复制代码时也可以运行。但是当我尝试将其集成到我的代码中时,它要么不起作用,要么只会导致画布上的图像被隐藏。有什么建议吗? - Alex Saidani
@AlexSaidani 刚刚重置了变换和合成模式。简单的方法是在应用任何变换之前使用 ctx.save(),完成后再使用 restore()。或者,手动重置使用 setTransform(1,0,0,1,0,0),并将 comp 模式设置回“source-over”。上面显示的设置步骤可以移动到按钮处理程序内部。 - user1693593
@AlexSaidani 添加了第二个示例。 - user1693593

2
如果您不想存储所有重建画布所需的命令,可以使用第二个内存画布来旋转现有画布内容。
步骤如下:
  • 创建第二个内存画布。
  • 将主要画布复制到第二个画布上。
  • 清除主要画布。
  • 将旋转点设置为主画布的中心。
  • (但您可以设置任何旋转点)
  • 旋转90度(==PI / 2)。
  • 将第二个画布重新绘制到(现在旋转的)主画布上。
  • 清理 - 取消旋转和取消平移主画布。

enter image description hereenter image description here

示例代码和演示:

var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;

var img=new Image();
img.onload=start;
img.src="https://dl.dropboxusercontent.com/u/139992952/multple/leftarrow.png";
function start(){

  ctx.drawImage(img,cw/2-img.width/2,ch/2-img.width/2);

  $('#rotate').click(function(){
    rotate(cw/2,ch/2,90);
  });

}

function rotate(rotationPointX,rotationPointY,degreeRotation){

  // Create an second in-memory canvas:
  var mCanvas=document.createElement('canvas');
  mCanvas.width=canvas.width;
  mCanvas.height=canvas.height;
  var mctx=mCanvas.getContext('2d');

  // Draw your canvas onto the second canvas
  mctx.drawImage(canvas,0,0);

  // Clear your main canvas
  ctx.clearRect(0,0,canvas.width,canvas.height);

  // Rotate the main canvas

  // set the rotation point as center of the canvas
  // (but you can set any rotation point you desire)
  ctx.translate(rotationPointX,rotationPointY);

  // rotate by 90 degrees (==PI/2)
  var radians=degreeRotation/180*Math.PI;
  ctx.rotate(radians);


  // Draw the second canvas back to the (now rotated) main canvas:
  ctx.drawImage(mCanvas,-canvas.width/2,-canvas.height/2);

  // clean up -- unrotate and untranslate
  ctx.rotate(-radians);
  ctx.translate(-canvas.width/2,-canvas.height/2);

}
body{ background-color: ivory; }
#canvas{border:1px solid red;}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<button id=rotate>Rotate the existing image.</button>
<br>
<canvas id="canvas" width=300 height=300></canvas>

[ 将rotate()函数添加到问题代码中 ]

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

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

var startX;
var startY;
var isDown = false;


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

var img = new Image();
img.crossOrigin='anonymous';
img.onload = function () {

  var ratio = img.width / img.height;

  imageWidth = 71;
  imageHeight = imageWidth / ratio;
  imageY = (245-imageHeight)/2;
  if (imageHeight > 245) {
    imageHeight = 245;
    imageWidth = imageHeight * ratio;
    imageY = 0;
  }

  imageX = ((canvas.width-imageWidth)/2);
  imageY = ((canvas.height-imageHeight)/2);

  imageRight = imageX + imageWidth;
  imageBottom = imageY + imageHeight;

  draw(true, false);
}
img.src='https://dl.dropboxusercontent.com/u/139992952/multple/leftarrow.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;
    }

    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);

  }


}

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

function rotate(rotationPointX,rotationPointY,degreeRotation){

  // Create an second in-memory canvas:
  var mCanvas=document.createElement('canvas');
  mCanvas.width=canvas.width;
  mCanvas.height=canvas.height;
  var mctx=mCanvas.getContext('2d');

  // Draw your canvas onto the second canvas
  mctx.drawImage(canvas,0,0);

  // Clear your main canvas
  ctx.clearRect(0,0,canvas.width,canvas.height);

  // Rotate the main canvas

  // set the rotation point as center of the canvas
  // (but you can set any rotation point you desire)
  ctx.translate(rotationPointX,rotationPointY);

  // rotate by 90 degrees (==PI/2)
  var radians=degreeRotation/180*Math.PI;
  ctx.rotate(radians);


  // Draw the second canvas back to the (now rotated) main canvas:
  ctx.drawImage(mCanvas,-canvas.width/2,-canvas.height/2);

  // clean up -- unrotate and untranslate
  ctx.rotate(-radians);
  ctx.translate(-canvas.width/2,-canvas.height/2);

}

$('#rotate').click(function(){
  rotate(canvas.width/2,canvas.height/2,90);
});
body{ background-color: ivory; }
canvas{border:1px solid red;}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<button id=rotate>Rotate</button>
<br>
<canvas id="editorCanvas" width=300 height=300></canvas>


感谢您的回答,虽然我可以看到它在示例中运行,并且当我逐行复制代码时也可以运行。但是当我尝试将其集成到我的代码中时,它要么不起作用,要么只会导致画布上的图像被隐藏。有什么建议吗? - Alex Saidani
你没有设置任何img.src,所以你的代码没有显示任何图像!在我添加了图像源之后,我的答案中的旋转函数确实可以旋转您的图像(请参见答案的补充)。但是,请注意,您的draw()函数重新绘制原始图像和锚点而不进行旋转。这会撤销旋转效果,因此您需要调整那些处理程序,以便将图像和锚点以零旋转而不是新旋转绘制出来。我建议保存当前的旋转角度,并在适当的translate/rotate中包装您的drawImage和drawAnchors,以便它们以新角度绘制。 - markE
回到这个问题,感谢您详细的回答。但是我仍然无法使其正常工作。现在它可以旋转了,但正如您所提到的,绘制函数会将图像重置为其原始角度。您有没有想过如何在不重置的情况下使其正常工作? - Alex Saidani

0

正如Spencer所说,Canvas更像是一张纸,你可以在上面画画。如果你画了一个圆形,你不能只旋转它,你必须擦掉它然后在新的位置重新画。

你需要跟踪你的对象状态(旋转、位置、颜色等),然后使用这些信息重新绘制到Canvas上。

如果你想要一个更加简单易用的移动对象解决方案,可以尝试使用ocanvas。它提供了一个处理对象而不是像素绘制的框架。它比标准的Canvas更高级,但不会过于复杂。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接