缩放和旋转后,HTML5画布会回移

巴勃罗

我正在尝试使用画布做一些事情。首先,我有一个用户上传图像,如果图像大于我想要的尺寸,则需要按比例缩小图像。那部分工作正常。最近,我们遇到了iPhone用户上传图像的问题。这些都有方向性问题。我已经弄清楚了如何提取方向,我的问题是在画布上操作图像时会发生什么。

这就是我需要做的:获取图像,translate(),scale(),rotate(),translate()<-将其返回到其原始位置drawImage()。

当我这样做时,图像的那部分不在深渊中。

if (dimensions[0] > 480 || dimensions[1] > 853) {
    // Scale the image.
    var horizontal = width > height;
    if (horizontal) {
        scaledHeight = 480;
        scaleRatio = scaledHeight / height;
        scaledWidth = width * scaleRatio;
    } else {
        scaledWidth = 640;
        scaleRatio = scaledWidth / width;
        scaledHeight = height * scaleRatio;
    }

    canvas['width'] = scaledWidth;
    canvas['height'] = scaledHeight;
    ctx['drawImage'](image, 0, 0, width, height, 0, 0, scaledWidth, scaledHeight);
    /* Rotate Image */
    orientation = 8; //manual orientation -> on the site we use loadImage to get the orientation
    if(orientation != 1){
        switch(orientation){
            case 8:
            case 6:
                canvas.width = scaledHeight;
                canvas.height = scaledWidth;
                break;
        }

        var halfScaledWidth = scaledWidth/2;
        var halfScaledheight = scaledHeight/2;

        ctx.save(); //<- SAVE

        ctx.clearRect(0,0,canvas.width,canvas.height);
        ctx.translate(halfScaledWidth,halfScaledheight);
        switch(orientation){
            case 8: //rotate left
                ctx.scale(scaleRatio,scaleRatio);
                ctx.rotate(-90*Math.PI/180);
                ctx.translate(-1380,-1055); // <-Manuial numbers
                break;
            case 3: //Flip upside down
                ctx.scale(scaleRatio,scaleRatio);
                ctx.rotate(180*Math.PI/180);
                ctx.translate(-925,-595); //<-Manuial numbers
                break;
            case 6: //rotate right
                ctx.scale(scaleRatio,scaleRatio);
                ctx.rotate(90*Math.PI/180);
                ctx.translate(-462,-130); //<-Manuial numbers
                break;
        }

        //re-translate and draw image
        //ctx.translate(-halfScaledWidth,-halfScaledheight);
        ctx.drawImage(image,-halfScaledWidth, -halfScaledheight);
        ctx.restore(); //<- RESTORE
    }
    /* Rotate Image */
}

我手动设置了方向,因此我可以看到我担心的每个位置的外观。如果是纵向,则翻转画布。

我试过save()和restore()。我试过了translate(x,y)然后translate(-x,-y)。。我的猜测是,由于比例尺,网格已关闭,x和y需要相乘。我尝试对scaleRatio进行操作,但仍然无法正常工作。

如您所见,我手动将转换设置回去,但仅适用于我正在处理的图像尺寸,因此根本不是一个好的解决方案!

这里是代码:JSFiddle如果我正常旋转,一切正常。

谢谢!

盲人67

转变

对于简单的答案,如果您对如何跳到底部不感兴趣,可以找到解决问题的替代方法。所有评论。我对你想要的东西有点猜测。

如果您对我认为更简单的2D转换功能使用方法感兴趣,请阅读其余内容。

矩阵数学

当您通过canvas 2D API使用平移,缩放和旋转时,您正在做的就是将现有矩阵乘以每个函数创建的矩阵。

基本上当你做

ctx.rotate(Math.PI); // rotate 180 deg

API将创建一个新的旋转矩阵,并将现有矩阵与其相乘。

与普通的数学乘法不同,矩阵乘法会根据乘法顺序改变结果。在正常的数学乘法中,A * B = B * A但这不适用于矩阵mA * mB != mB * mA(请注意不等于)

当您需要应用多个不同的转换时,这将变得更加棘手。

ctx.scale(2,2);
ctx.rotate(Math.PI/2);
ctx.translate(100,100);

与给出的结果不同

ctx.scale(2,2);
ctx.translate(100,100);
ctx.rotate(Math.PI/2);

您需要应用变形的顺序取决于您要实现的目标。对于复杂的链接动画,以这种方式使用API​​非常方便。不幸的是,如果您不了解矩阵数学,它也会带来无尽的挫败感。它还迫使许多人使用saverestore函数来恢复默认转换,这在某些情况下可能会导致GPU性能的损失很大。

setTransform()

我们很幸运,因为2D API还具有ctx.setTransform(a, b, c, d, e, f)您真正需要的所有功能此功能将现有的转换替换为提供的转换。大多数文档对于的含义都比较模糊,a,b,c,d,e,f但是其中包含的是旋转,比例和平移。

该功能的一种方便用法是设置默认转换,而不是使用保存和还原。

我经常看到这类事情。(示例1,在下面进一步引用)

// transform for image 1
ctx.save();  // save state
ctx.scale(2,2);
ctx.rotate(Math.PI/2);
ctx.translate(100,100);
// draw the image
ctx.drawImage(img1, -img1.width / 2, -img1.height / 2);
ctx.restore(); // restore saved state

// transform for image 2
ctx.save();  // save state agian
ctx.scale(1,1);
ctx.rotate(Math.PI);
ctx.translate(100,100);
// draw the image
ctx.drawImage(img2, -img2.width / 2, -img2.height / 2);
ctx.restore(); // restore saved state

一种更简单的方法是通过将其设置为“身份”矩阵来删除保存和还原并手动重置转换

ctx.scale(2,2);
ctx.rotate(Math.PI/2);
ctx.translate(100,100);
// draw the image
ctx.drawImage(img1, -img1.width / 2, -img1.height / 2);
ctx.setTransform(1,0,0,1,0,0); // restore default transform

// transform for image 2
ctx.scale(1,1);
ctx.rotate(Math.PI);
ctx.translate(100,100);
// draw the image
ctx.drawImage(img2, -img2.width / 2, -img2.height / 2);
ctx.setTransform(1,0,0,1,0,0); // restore default transform

现在,我确定您仍然想知道这些数字传递给setTransform的含义是什么?

记住它们的最简单方法是2个向量和1个坐标。这两个向量描述了单个像素的方向和比例,坐标只是原点的x,y像素位置(在0,0处绘制的位置将在画布上)。

像素及其轴

想象一个像素,这是可以通过当前变换缩放和旋转的抽象变换像素。它具有X和Y两个轴。要描述每个轴,我们需要两个数字(一个向量),它们描述屏幕的(未变换的)方向以及像素的顶部和左侧的比例。因此,对于与屏幕像素匹配的普通像素,X轴从左到右横跨顶部,长度为一个像素。向量是(1,0)跨一个像素,向下没有像素。对于沿屏幕向下的Y轴,向量为(0,1)无像素,向下为一个像素。原点是屏幕右上角的像素,位于像素(0,0)处。

因此,我们得到了Identity Matrix,这是2D API(以及许多其他API)的默认矩阵X轴(1,0),Y轴(0,1)和原点(0,0),它们与的六个参数匹配setTransform(1,0,0,1,0,0)

现在说我们要按比例放大像素。我们所要做的是增加X的尺寸和Y轴setTransform(2,0,0,2,0,0)是一样的scale(2,2)(从默认的转换),我们的像素的顶部是现在两个像素长在顶部和两个像素长久下来左侧。setTransform(0.5,0,0,0.5,0,0)现在按比例缩小像素为上下半像素。

这两个轴矢量(a,b)和(c,d)可以指向任何方向,彼此完全独立,它们不必彼此成90度角,因此可以使像素倾斜,也可以要求它们的长度相同,以便您可以更改像素长宽比。原点也是独立的,仅是原点像素的画布绝对坐标,可以设置为画布上或下的任何位置。

现在说我们要顺时针旋转变换90Deg,将两个轴按比例缩放2,并将原点放置在画布的中心。我们希望像素的X轴(顶部)长2像素并指向屏幕下方。向量是(0,2)跨0和向下两个。我们希望像素的左侧长度为2,并指向屏幕左侧(-2,0)负两个,且不向下。中心的原点是(canvas.width / 2, canvas.height / 2),得到的最终矩阵为setTransform(0,2,-2,0,canvas.width / 2, canvas.height / 2)

旋转另一种方式是 setTransform(0,-2,2,0,canvas.width / 2, canvas.height / 2)

轻松旋转90度

您可能会注意到旋转90度只是交换向量并更改符号。

  • x,y顺时针旋转90度的向量()为(-y,x)。
  • x,y逆时针旋转90度的向量()为(y,-x)。

交换x和y,然后对y进行顺时针方向取反,或者对x进行逆时针方向取反。

对于180,它从0度向量(1,0)开始

// input vector
var x = 1;
var y = 0;
// rotated vector
var rx90 = -y; // swap y to x and make it negative
var ry90 = x;  // x to y as is
// rotate again same thing
var rx180 = -ry90; 
var rx180 = rx90;
// Now for 270
var rx270 = -ry180; // swap y to x and make it negative
var rx270 = rx180;

或全部以x和y表示

  • 0度(x,y
  • 90度(-y,x
  • 180度(-x,-y
  • 270度(y,-x
  • 然后返回360(x,y)。

这是向量的一个非常方便的属性,我们可以利用它来简化转换矩阵的创建。在大多数情况下,我们不想倾斜图像,因此我们知道Y轴始终与x轴顺时针成90度。现在,我们只需要描述x轴,并通过将90deg旋转应用于该矢量就可以得到y轴。

因此,增值经销商xy有规模,我们的像素(X轴)的顶部的方向oxoy是画布(翻译)在原点的位置。

var x = 1; // one pixel across
var y = 0; // none down
var ox = canvas.width / 2; // center of canvas
var oy = canvas.height / 2;

现在创建转换是

ctx.setTransform(x, y, -y, x, ox, oy);

请注意,y轴与x轴成90度角。

三角函数和单位向量

当轴与顶部和侧面对齐时,一切都很好,也很容易,如何获得任意角度的轴矢量,如ctx.rotate(angle)For参数所提供的那样,因此需要一点点触发。数学函数Math.cos(angle)返回角度的x分量,角度并Math.sin(angle)给出Y分量。对于零度cos(0) = 1sin(0) = 090度(Math.PI/2弧度)cos(PI/2) = 0sin(PI/2) = 1

使用sin和cos的好处在于,我们为向量获得的两个数字始终为我们提供一个长度为1个单位(像素)的向量(这称为归一化向量或单位向量),因此cos(a)2 + sin(a)2 = 1

为什么这么重要?因为它使缩放非常容易。假设我们始终保持宽高比为平方,则比例尺只需要一个数字。要缩放矢量,只需将其乘以比例

var scale = 2;  // scale of 2
var ang = Math.random() * 100; // any random angle
var x = Math.cos(ang);  // get the x axis as a unit vector.
var y = Math.sin(ang);
// scale the axis
x *= scale;
y *= scale;

向量x,y现在是两个单位长。

优于使用保存,还原,旋转,缩放,平移... :(

现在将它们放在一起以创建具有任意旋转,缩放和平移(原点)的矩阵

// ctx is the 2D context, 
// originX, and originY is the origin, same as ctx.translate(originX,originY)
// rotation is the angle in radians same as ctx.rotate(rotation)
// scale is the scale of x and y axis same as ctx.scale(scale,scale)
function createTransform(ctx,originX,originY,rotation,scale){
    var x, y;
    x = Math.cos(rotation) * scale;
    y = Math.sin(rotation) * scale;
    ctx.setTransform(x, y, -y, x, originX, originY);
}

现在将其应用于上面给出示例(1)

// dont need ctx.save();  // save state
// dont need ctx.scale(2,2);
// dont need ctx.rotate(Math.PI/2);
// dont need ctx.translate(100,100);
createMatrix(ctx, 100, 100, Math.PI/2, 2)
// draw the image normally
ctx.drawImage(img1, -img1.width / 2, -img1.height / 2);
// dont need ctx.restore(); // restore saved state

// transform for image 2
// dont need ctx.save();  // save state agian
// dont need ctx.scale(1,1);
// dont need ctx.rotate(Math.PI);
// dont need ctx.translate(100,100);
// we don't have to reset the default transform because 
// ctx.setTransform completely replaces the current transform
createMatrix(ctx, 100, 100, Math.PI/2, 2)
// draw the image
ctx.drawImage(img2, -img2.width / 2, -img2.height / 2);
// dont need ctx.restore(); // restore saved state

这就是您使用setTransform简化画布转换的方式,而不是猜测,反复试验,缩放,旋转和在保存和还原的海洋中来回转换。

使用它来简化您的代码

答案

现在你的问题

我不确定您要做什么,我想您不介意缩放画布以容纳图像,图像始终在中心并且外观保持不变。当旋转与屏幕对齐时,我将手动设置变换

    // this is in set up code
    const MAX_SIZE_WIDTH = 640;
    const MAX_SIZE_HEIGHT = 480;
    orientationData = [];
    orientationData[6] = Math.PI/2; // xAxis pointing down
    orientationData[8] = -Math.PI/2; // xAxis pointing up
    orientationData[3] = Math.PI; //xAxis pointing to left


    // in your code
    var orient,w,h,iw,ih,scale,ax,ay; // w and h are canvas size

    // assume image is the loaded image
    iw = image.width;  // get the image width and height so I dont have to type as much.
    ih = image.height;

    if(orientation != 1){
        var orient = orientationData[orientation];
        if(orient === undefined){
             return; // bad data so return
        }

        // get scale  and resize canvas to suit

        // is the image on the side 
        if(orientation === 6 || orientation === 8){
             // on side so swap width and height
             // get the height and width scales for the image, dont scale 
             // if the image is smaller than the dimension
             scale = Math.min(1,
                  MAX_SIZE_WIDTH / ih, 
                  MAX_SIZE_HEIGHT  / iw
             );
             w = canvas.width = scale * ih;  
             h = canvas.height = scale * iw;
        }else{
             // for normal orientation
             scale = Math.min(1,
                  MAX_SIZE_WIDTH / iw, 
                  MAX_SIZE_HEIGHT  / ih
             );
             h = canvas.height = scale * ih;  
             w = canvas.width = scale * iw;
        }


        // Do you really need to clear the canvas if the image is filling it??
        // ensure that the default transform is set
        ctx.setTransform(1, 0, 0, 1, 0, 0);
        // clear the canvas 
        ctx.clearRect(0, 0, w, h);

        // now create the transformation matrix to 
        // position the image in the center of the screen

        // first get the xAxis and scale it
        ax = Math.cos(orient) * scale;
        ay = Math.sin(orient) * scale;
        // now set the transform, the origin is always the canvas center
        // and the Y axis is 90 deg clockwise from the xAxis and same scale
        ctx.setTransform(ax, ay, -ay, ax, w / 2, h / 2);

        // now draw the image offset by half its width and height 
        // so that it is centered on the canvas

        ctx.drawImage(image,-iw / 2, -ih / 2);

        // restore the default transform
        ctx.setTransform(1, 0, 0, 1, 0, 0);
  } // done.

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章