Canvas - 如何正确旋转 vertical/horizontal 图片?

Canvas - How to rotate vertical/horizontal image correctly?

我看过许多轮换 canvas/scaling canvas 解决的问题,但解决方案并没有真正解决这个问题,所以仍然不确定如何让它与 canvas.

有一个垂直固定尺寸的矩形100w×150h,如下图红框所示。当添加并旋转图像 (vertical/horizontal/square) 时,它应该在垂直固定尺寸矩形内正确旋转和缩放,如下例所示。

在第一个示例中,我们将使用 垂直 图像(埃菲尔铁塔原始图像在 240w × 400h),这是所有四次旋转时的样子角度:

在第二个示例中,我们将使用 水平 图像(1280w × 720h 的狗原始图像),这是它在所有四个旋转角度下的样子:

使用 canvas 完成此任务的最有效方法是什么?

(我知道 css 可以使用 transform: rotate(90deg) 并使用背景 size/position 属性,但我正在尝试学习如何使用 canvas vertical/horizontal/square 张图片)。

Here is a fiddle.

我们不需要任何 canvas.width/2-image.width/2 代码,因此只需使用 ctx.drawImage(image,0,0, canvas.width, canvas.height) 即可将您的 onload 更改为。除此之外,您还可以定义一个全局 ratio 变量,当您向侧面旋转并需要向上缩放时,该变量将用于正确缩放:

var ratio = 1;
image.onload=function(){
    canvas.width = 100;
    canvas.height = 150;
    ratio = canvas.height/canvas.width; // We will use this for scaling the image to fit

    ctx.drawImage(image,0,0, canvas.width, canvas.height);
}

现在以图像为中心旋转图像的最佳方法是将图像中心平移到 canvas 的 (0,0) 点。然后你可以旋转并将它移回原来的位置。这是因为当应用旋转时,canvas (0,0) 点就是旋转点。

function drawRotated(degrees){
    ctx.clearRect(0,0,canvas.width,canvas.height);
    ctx.save();
    ctx.translate(canvas.width/2,canvas.height/2);   // Move image center to 0,0
    ctx.rotate(degrees*Math.PI/180);                 // Rotate will go from center
    ctx.translate(-canvas.width/2,-canvas.height/2); // Move image back to normal spot
    ctx.drawImage(image,0,0, canvas.width, canvas.height)
    ctx.restore();
}

使用目前的代码,正常和 180 度图像看起来都不错。但是横向的需要向上缩放,为此添加一些逻辑来检测图像是否向左或向右翻转,然后按 ratio 变量缩放(1.5 在这种情况下 ).

function drawRotated(degrees){
    ctx.clearRect(0,0,canvas.width,canvas.height);
    ctx.save();
    ctx.translate(canvas.width/2,canvas.height/2);
    ctx.rotate(degrees*Math.PI/180);
    if((degrees - 90) % 180 == 0) // Is the image sideways?
        ctx.scale(ratio, ratio);  // Scale it up to fill the canvas
    ctx.translate(-canvas.width/2,-canvas.height/2);
    ctx.drawImage(image,0,0, canvas.width, canvas.height)
    ctx.restore();
}

Updated Fiddle

更新:

水平图像看起来很奇怪的原因有两点。目前,缩放假定图像在横向时需要放大,如果是水平图像,则逻辑被翻转。相反,我们希望在正常翻转或上下翻转时放大:

function drawRotated(degrees) {
    ctx.clearRect(0,0,canvas.width,canvas.height);
    ...

    if(imgRatio < 1) angleToScale += 90
    if(angleToScale % 180 == 0)
        ctx.scale(ratio, ratio);

    ctx.translate(-canvas.width/2,-canvas.height/2);
    ...
}

此处我们根据 imgRatio < 1 是否声明图像是水平的来确定。否则它将是垂直的。虽然这对于声称垂直与水平图像有点宽泛,但它可以用于假设我们只有垂直或水平图像的目的。

尽管在这些更改之后仍然存在一些问题 (see this fiddle)。这是因为当我们绘制图像时,我们将其拟合到垂直的 canvas,导致图像在绘制到 canvas.

时拉伸

这可以通过更改我们绘制图像目标的位置来解决。对于水平图像,我们希望将其水平绘制:

一个注释是对 onload 方法的一些更改:

var ratio = 0;
var xImgOffset = 0;
var yImgOffset = 0;
image.onload=function(){
    canvas.width = 100;
    canvas.height = 150;
    ratio = canvas.height/canvas.width;
    var imgRatio = image.height/image.width;

    if(imgRatio < 1) { // Horizonal images set Height then proportionally scale width
        var dimDiff = image.height/canvas.width;
        image.height = canvas.width;         // This keeps in mind that the image 
        image.width = image.width / dimDiff; // is rotated, which is why width is used
    } else {           // Verticle images set Height then proportionally scale height
        var dimDiff = image.width/canvas.width;
        image.width = canvas.width;
        image.height = image.height / dimDiff;
    }

    xImgOffset = -(image.width - canvas.width) / 2;
    yImgOffset = -(image.height - canvas.height) / 2;

    drawRotated(0);
}

立即调用 drawRotated 方法以应用缩放更改。 xImgOffsetyImgOffset 是水平和垂直 canvas 大小的起始位置与原始图像尺寸成比例的位置差异。

视觉上看起来像这样:

在上图中,我们需要在 canvas 中绘制水平图像作为绿色水平矩形。对于垂直图像,绘制图像时宽度设置为 canvas 宽度,高度按偏移量按比例缩放,因此图像居中。同样这对于水平图像也是一样的,我们只需要记住我们正在绘制它就好像 canvas 最初是水平的(参见第一个图 )。

最后整个方法看起来像这样:

function drawRotated(degrees){
    ctx.clearRect(0,0,canvas.width,canvas.height);
    ctx.save();
    ctx.translate(canvas.width/2,canvas.height/2);
    ctx.rotate(degrees*Math.PI/180);
    var angleToScale = degrees - 90;
    var imgRatio = image.height/image.width;

    if(imgRatio < 1) angleToScale += 90
    if(angleToScale % 180 == 0)
        ctx.scale(ratio, ratio);

    ctx.translate(-canvas.width/2,-canvas.height/2);
    ctx.drawImage(image, xImgOffset, yImgOffset, image.width, image.height);
    ctx.restore();
}

Updated Fiddle For both horizontal and vertical images with original image ratio and cropping

此设置适用于任何 canvas 维度和大小。