HTML5 canvas在缩放和旋转后平移回来 [英] HTML5 canvas translate back after scale and rotation

查看:277
本文介绍了HTML5 canvas在缩放和旋转后平移回来的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想用画布做几件事。首先我有一个用户上传图像,如果图像大于我想要我需要缩放它。这部分工作很好。最近我们遇到了一个问题,iPhone用户上传图片。这些都有定向问题。我想出了如何获取方向提取,我的问题是当我在画布上操作图像时会发生什么。



这是我需要做的:获取图像,translate(),scale(),rotate原始位置,drawImage()。



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

  if(dimensions [0]> 480 || dimensions [1]> 853){
//缩放图像。
var horizo​​ntal = width>高度;
if(horizo​​ntal){
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);
/ *旋转图像* /
orientation = 8; // manual orientation - >在网站上我们使用loadImage获取方向
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://上下翻转
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;
}

//重新翻译和绘制图像
//ctx.translate(-halfScaledWidth,-halfScaledheight);
ctx.drawImage(image,-halfScaledWidth,-halfScaledheight);
ctx.restore(); //< - RESTORE
}
/ *旋转图片* /
}

我手动设置了方向,所以我可以看到它在每个位置Im看起来担心。如果它的纵向方向我翻转的画布。



我试过save()和restore()。我试过translate(x,y)然后翻译(-x,-y)..我的猜测是,因为规模,网格是关闭的,x和y需要相乘。我试着对scaleRatio这样做,仍然没有工作。



正如你可以看到,我手动设置翻译回,但只适用于我正在工作的图像大小



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



谢谢!

解决方案

转换



如果您对如何跳到底部,你会发现一个替代的方法,你的问题。这是所有的评论。我已经对你想要的内容做了一些猜测。



如果你对我认为一个更简单的方法使用2D转换函数阅读其余的感兴趣。



矩阵Math



使用翻译,缩放和旋转canvas 2D API你正在做的是将现有矩阵乘以每个函数创建的矩阵。



基本上当您执行

  ctx.rotate(Math.PI ); //旋转180度

API会创建一个新的旋转矩阵,并将现有矩阵乘以它。 / p>

与正常的数学乘法矩阵乘法将根据乘法的顺序改变结果。在正常的数学乘法 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​​这种方式对于复杂的链接动画非常方便。不幸的是,如果你不知道矩阵数学,它也是无尽的沮丧的来源。它还强制许多人使用 save restore 函数来恢复默认转换,在某些情况下可以非常GPU性能昂贵。



setTransform()



有函数 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);
//绘制图像
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);
//绘制图像
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);
//绘制图像
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);
//绘制图像
ctx.drawImage(img2,-img2.width / 2,-img2.height / 2);
ctx.setTransform(1,0,0,1,0,0); //恢复默认转换

现在,我相信你仍然想知道这些数字是传递给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 setTransform(1,0,0,1)的六个参数的原点( 0,0 ,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 2,-2,0,canvas.width / 2,canvas.height / 2)



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



轻松旋转90度



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




  • 顺时针旋转90度的向量( x,y )是( -y,x c $ c>)

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



交换x和y,否定x的逆时针旋转。



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

  //输入向量
var x = 1;
var y = 0;
// rotate vector
var rx90 = -y; // swap y to x and make it negative
var ry90 = x; // x to y as is
//再次旋转相同的东西
var rx180 = -ry90;
var rx180 = rx90;
//现在为270
var rx270 = -ry180; // swap y to x and make it negative
var rx270 = rx180;

或所有只是x和y




  • 0 deg( x,y

  • 90deg( y,x

  • 180deg( -x,-y
  • b
  • 270deg( y,-x

  • 并返回360( x, / code>)。



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



因此vars x y 是像素顶部的缩放和方向(x轴), ox

 <$ c> 

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

现在创建的转换是

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

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



Trig和单位向量



当轴对齐顶部和两侧时,你如何获得一个轴的向量在一个任意角度,如由 ctx.rotate(angle)的参数提供为了我们需要一点点的trig。数学函数 Math.cos(angle)返回角度,角度的x分量和 Math.sin(angle)给我们Y分量。对于90 deg(<$ c $),对于零度 cos(0)= 1 sin(0)= 0 (PI / 2)= 0 sin(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); //获取x轴作为单位向量。
var y = Math.sin(ang);
//缩放轴
x * = scale;
y * = scale;

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



比使用save,restore,rotate,scale,translate ...更好:(



创建具有任意旋转,缩放和平移(原点)的矩阵

  // ctx是2D上下文,
// originX,originY是原点,与ctx.translate(originX,originY)相同
//旋转角度以弧度表示,与ctx.rotate(旋转)相同
// scale是缩放x和y轴与ctx.scale(scale,scale)相同
function createTransform(ctx,originX,originY,rotation,scale){
var x,y;
x = Math.cos (旋转)*缩放;
y = Math.sin(rotation)* scale;
ctx.setTransform(x,y,-y,x,originX,originY);
} $ b b

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

  // 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)
//正常绘制图像
ctx.drawImage(img1,-img1.width / 2,-img1。 height / 2);
// dont need ctx.restore(); // restore saved state

// transform for image 2
//不需要ctx.save(); // save state agian
// dont need ctx.scale(1,1);
// dont need ctx.rotate(Math.PI);
// dont need ctx.translate(100,100);
//我们不必重置默认转换,因为
// ctx.setTransform完全替换当前转换
createMatrix(ctx,100,100,Math.PI / 2,2 )
//绘制图片
ctx.drawImage(img2,-img2.width / 2,-img2.height / 2);
// dont need ctx.restore(); // restore saved state

这就是如何使用setTransform简化canvas的转换,而不是猜测



使用它来简化您的代码



回答



现在回答您的问题



我不完全确定你是什么,我想你不介意缩放画布来适应图像,图像总是在中心,而且方面保持不变。由于旋转对齐屏幕,我将手动设置转换

  //这是设置代码
const MAX_SIZE_WIDTH = 640;
const MAX_SIZE_HEIGHT = 480;
orientationData = [];
orientationData [6] = Math.PI / 2; // xAxis指向下
orientationData [8] = -Math.PI / 2; // xAxis pointing up
orientationData [3] = Math.PI; // xAxis指向左


//在你的代码中
var orient,w,h,iw,ih,scale,ax,ay; // w和h是画布大小

//假设图像是加载的图像
iw = image.width; //得到图像的宽度和高度,所以我不必键入多少。
ih = image.height;

if(orientation!= 1){
var orient = orientationData [orientation];
if(orient === undefined){
return; //坏数据所以返回
}

//获取比例并调整画布以适应

//是边上的图像
if方向=== 6 ||方向=== 8){
//边上这样交换宽度和高度
//获取图像的高度和宽度比例,不缩放
/ /如果图像小于尺寸
scale = Math.min(1,
MAX_SIZE_WIDTH / ih,
MAX_SIZE_HEIGHT / iw
);
w = canvas.width = scale * ih;
h = canvas.height = scale * iw;
} else {
//正常方向
scale = Math.min(1,
MAX_SIZE_WIDTH / iw,
MAX_SIZE_HEIGHT / ih
);
h = canvas.height = scale * ih;
w = canvas.width = scale * iw;
}


//如果图像填充,你真的需要清除画布吗?
//确保默认转换设置为
ctx.setTransform(1,0,0,1,0,0);
//清除画布
ctx.clearRect(0,0,w,h);

//现在创建转换矩阵为
//将图像置于屏幕中央

//首先获取xAxis并缩放
ax = Math.cos(orient)* scale;
ay = Math.sin(orient)* scale;
//现在设置变换,原点总是画布中心
//,Y轴从xAxis顺时针旋转90度,同样的缩放
ctx.setTransform(ax,ay, -ay,ax,w / 2,h / 2);

//现在绘制图像偏移宽度和高度的一半
//以使其以画布为中心

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

//恢复默认转换
ctx.setTransform(1,0,0,1,0,0);
} // done。


I'm trying to do a few thing with canvas. First I have a user upload an image, if the image is larger than I want I need to scale it down. That part is working just fine. Recently we ran into an issue with iPhone users uploading images. These have orientation issues. I've figured out how to get the orientation extracted, my issue is what happens when I manipulate the image in the canvas.

This is what I need to do: Get the image, translate(), scale(), rotate(), translate() <- get it back to its original position, drawImage().

When I do that part of the image is off in the abyss.

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 */
}

I have orientation set manually so I can see how it looks in each position Im worried about. If its a portrait orientation I flip the canvas.

I've tried save() and restore(). I've tried translate(x,y) then translate(-x,-y).. My guess is that because of the scale the grid is off and x and y need to be multiplied. I tried doing that against the scaleRatio and still didn't work.

As you can see I manually set the translate back but that only works with the image size I am working with, so not a good solution at all!

Here is the code: JSFiddle If I do a normal rotate right it all works.

Thanks!

解决方案

Transformations

For the easy answer if you are not interested in the how skip to the bottom where you will find an alternative approch to your problem. It is all commented. I have made a bit of a guess as to what you wanted.

If you are interested in what I consider a simpler way to use the 2D transformation functions read the rest.

Matrix Math

When you use translate, scale, and rotate via the canvas 2D API what you are doing is multiplying the existing matrix with one created with each function.

Basically when you do

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

the API creates a new rotation matrix and multiplies the existing matrix with it.

Unlike normal math multiplication matrix multiplication will change the result depending on what order you multiply. In normal math multiplication A * B = B * A but this does not hold true for matrices mA * mB != mB * mA (Note the not equals)

This becomes more problematic when you need to apply several different transformations.

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

Does not give the same result as

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

The order that you need to apply the tranforms depends on what you are trying to achieve. Using the API this way is very handy for complex linked animations. Unfortunately it is also a source of endless frustration if you are not aware of matrix math. It also forces many to use the save and restore functions to restore the default transformation, that in some situations can be very costly in GPU performance.

setTransform()

We are in luck though as the 2D API also has the function ctx.setTransform(a, b, c, d, e, f) which is all you really should ever need. This function replaces the existing transform with the one supplied. Most of the documentation is rather vague as to the meaning of the a,b,c,d,e,f but contained in them is the rotation, scale, and translation.

One handy use of the function is to set the default transform rather than use save and restore.

I see this type of thing a lot. (The Example 1, referenced further down)

// 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

An easier way is to just drop the save and restores and reset the transform manually by setting it to the Identity matrix

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

Now then I am sure you are still wondering what are these numbers being passed to setTransform and what do they mean?

The easiest way to remember them is as 2 vectors and 1 coordinate. The two vectors describe the direction and scale of a single pixel, the coordinate is simply the x,y pixel location of the origin (the location that drawing at 0,0 will be on the canvas).

A Pixel and its axis

Imagine a single pixel, this is the abstract transformed pixel that can be scaled and rotated by the current transformation. It has two axis, X and Y. To describe each axis we need two numbers ( a vector) these describes the screen (untransformed) direction and scale of the top and left side of the pixel. So for a normal pixel that matches the screen pixels the X axis is across the top from left to right and is one pixel long. The vector is (1,0) one pixel across, no pixels down. For the Y axis that goes down the screen the vector is (0,1) no pixels across, one pixel down. The origin is the top right screen pixel which is at coordinate (0,0).

Thus we get the Identity Matrix, the default matrix for the 2D API (and many other APIs) The X axis (1,0), Y axis (0,1) and the origin (0,0) which match the six arguments for setTransform(1,0,0,1,0,0).

Now say we want to scale the pixel up. All we do is increase the size of the X and Y Axis setTransform(2,0,0,2,0,0) is the same as scale(2,2) (from the default transform) Our pixel's top is now two pixels long across the top and two pixels long down the left side. To scale down setTransform(0.5,0,0,0.5,0,0) our pixel is now half a pixel across and down.

These two axis vectors (a,b) & (c,d) can point in any direction, are completely independent of each other , they don't have to be at 90 deg to each other so can skew the pixel, nor do they require that they be the same length so you can change the pixel aspect. The origin is also independent and is just the canvas absolute coordinates in pixels of the origin and can be set to anywhere on or off the canvas.

Now say we want to rotate the transform 90Deg clockwise, scale up both axes by 2 and position the origin at the center of the canvas. We want the X axis (top) of our pixel to be 2 pixels long and pointing down the screen. The vector is (0,2) 0 across and two down. We want the left side of our pixel to 2 long and point to the left of the screen (-2,0) Negative two across and none down. And the origin at the center is (canvas.width / 2, canvas.height / 2) to get the final matrix that is setTransform(0,2,-2,0,canvas.width / 2, canvas.height / 2)

Rotate the other way is setTransform(0,-2,2,0,canvas.width / 2, canvas.height / 2)

Easy Rotate 90deg

You may notice that rotating 90 degrees is just swapping the vectors and changing a sign.

  • The vector (x,y) rotated 90 degrees clockwise is (-y,x).
  • The vector (x,y) rotated 90 degrees anti-clockwise is (y,-x).

Swap the x, and y and negate the y for clockwise or negate the x for the anticlockwise rotation.

For 180 it is starting at 0 deg vector (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;

Or all in terms of just x and y

  • 0 deg (x,y)
  • 90deg (-y,x)
  • 180deg (-x,-y)
  • 270deg (y,-x)
  • and back to 360 (x,y).

This is a very handy attribute of a vector that we can exploit to simplify the creation of our transformation matrix. In most situations we do not want to skew our image thus we know that the Y axis is always 90Deg clockwise from the x axis. Now we only need to describe the x axis and by applying the 90deg rotation to that vector we have the y axis.

So the vars x and y are the scale and direction of the top of our pixel (x axis), ox, oy are the location of the origin on the canvas (translation) .

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

Now to create the transform is

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

Note that the y axis is at 90 degs to the x axis.

Trig and the Unit vector

All well and easy when the axis are aligned to the top and sides, how do you get the vector for a axis at an arbitrary angle such as is supplied by the argument for ctx.rotate(angle) For that we need a tiny bit of trig. The Math function Math.cos(angle) returns the x component of the angle, angle and Math.sin(angle) gives us the Y component. For zero deg cos(0) = 1 and sin(0) = 0 for 90 deg (Math.PI/2 radians) cos(PI/2) = 0 and sin(PI/2) = 1.

The beauty of using sin and cos is that the two numbers that we get for our vector always give us a vector that is 1 unit (pixel) long (this is called a normalised vector or a unit vector) thus cos(a)2 + sin(a)2 = 1

Why does this matter? because it makes scaling very easy. Assuming that we always keep the aspect square we only need one number for the scale. To scale a vector you simply multiply it by the scale

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;

the vector x,y is now two units long.

Better than using save, restore, rotate, scale, translate... :(

Now put it all together to create a matrix with an arbitrary rotation, scale and translation (origin)

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

Now to apply that to the example (1) given above

// 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

And that is how you use setTransform to simplify transforming the canvas, rather than guessing, trial and error, scale, rotates, and translates back and forth within a sea of save and restores.

Using that to simplify your code

The answer

And now to your question

I am not entirely sure what you are after, I presume you a dont mind scaling the canvas to accommodate the image, that the image is always in the center and that the aspect remains the same. As the rotations are aligned to the screen I will set the transforms manualy

    // 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.

这篇关于HTML5 canvas在缩放和旋转后平移回来的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆