我试图用画布做一些事情。首先我有一个用户上传图像,如果图像比我想要的大,我需要缩小它。那部分工作正常。最近我们遇到了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()。我尝试过翻译(x,y)然后翻译(-x,-y)..我的猜测是因为网格关闭,x和y需要相乘。我尝试对scaleRatio这样做,但仍然没有工作。
正如你所看到的,我手动设置了翻译,但这只适用于我正在使用的图像尺寸,所以根本不是一个好的解决方案!
这是代码: JSFiddle如果我正常旋转,那么一切正常。
谢谢!
答案 0 :(得分:8)
<强>转换强>
如果您对如何跳到底部不感兴趣,您可以找到替代问题的替代方案,以获得简单的答案。这是评论。我对你想要的东西做了一些猜测。
如果您对我认为使用2D转换函数的更简单方法感兴趣,请阅读其余内容。
Matrix Math
当您通过画布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这种方式对于复杂的链接动画非常方便。不幸的是,如果你不了解矩阵数学,它也是无尽挫折的根源。它还迫使许多人使用save
和restore
函数来恢复默认转换,在某些情况下,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的位置将在画布上)。
A像素及其轴
想象一个像素,这是可以通过当前变换进行缩放和旋转的抽象变换像素。它有两个轴,X和Y.为了描述每个轴,我们需要两个数字(一个矢量),它们描述了像素顶部和左侧的屏幕(未变换)方向和比例。因此,对于与屏幕像素匹配的普通像素,X轴从左到右跨越顶部并且是一个像素长。向量是(1,0
)一个像素,没有像素向下。对于沿着屏幕下行的Y轴,向量是(0,1
)没有像素,一个像素向下。原点是右上角的屏幕像素,位于坐标(0,0
)。
因此我们获得了Identity Matrix,2D API(以及许多其他API)的默认矩阵X轴(1,0
),Y轴(0,1
)和原点({{1 }})匹配0,0
的六个参数。
现在说我们想要缩放像素。我们所做的只是增加X和Y轴的大小setTransform(1,0,0,1,0,0)
与setTransform(2,0,0,2,0,0)
相同(来自默认变换)我们的像素顶部现在两个像素长,顶部和两个像素长在左侧。要缩小scale(2,2)
,我们的像素现在可以上下移动半个像素。
这两个轴向量(a,b)&amp; (c,d)可以指向任何方向,完全相互独立,它们不必相互成90度,因此可以使像素倾斜,也不要求它们长度相同你可以改变像素方面。原点也是独立的,只是原点像素的画布绝对坐标,可以设置为画布上或画布外的任何位置。
现在说我们想要顺时针旋转变换90度,将两个轴向上缩放2并将原点定位在画布的中心。我们希望像素的X轴(顶部)长2像素,并指向屏幕。向量是(setTransform(0.5,0,0,0.5,0,0)
)0横向和向下两个。我们希望像素的左侧长2并指向屏幕的左侧(0,2
)负两个横向,没有向下。并且中心的原点是(-2,0
)以获得canvas.width / 2, canvas.height / 2
的最终矩阵
另一种方式是setTransform(0,2,-2,0,canvas.width / 2, canvas.height / 2)
轻松旋转90度
您可能会注意到旋转90度只是交换矢量并更改符号。
setTransform(0,-2,2,0,canvas.width / 2, canvas.height / 2)
)为(x,y
)。-y,x
)为(x,y
)。交换x和y并顺时针取消y或取消逆时针旋转的x。
对于180,它从0度向量(y,-x
)
1,0
或仅仅是x和y
// 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
)-y,x
)-x,-y
)y,-x
)。这是一个非常方便的向量属性,我们可以利用它来简化转换矩阵的创建。在大多数情况下,我们不希望扭曲图像,因此我们知道Y轴始终是从x轴顺时针90度。现在我们只需要描述x轴,并通过将90deg旋转应用于该矢量,我们就有了y轴。
所以变量x,y
和x
是像素顶部的比例和方向(x轴),y
,ox
是原点的位置在画布上(翻译)。
oy
现在创建转换是
var x = 1; // one pixel across
var y = 0; // none down
var ox = canvas.width / 2; // center of canvas
var oy = canvas.height / 2;
请注意,y轴相对于x轴为90度。
Trig和单位矢量
当轴与顶部和侧面对齐时,一切都很简单,如何获得任意角度的轴矢量,例如由ctx.setTransform(x, y, -y, x, ox, oy);
的参数提供的为此我们需要一个小的三角形。数学函数ctx.rotate(angle)
返回角度的x分量,角度和Math.cos(angle)
给出Y分量。对于零度Math.sin(angle)
和cos(0) = 1
,对于90度(sin(0) = 0
弧度)Math.PI/2
和cos(PI/2) = 0
。
使用sin和cos的美妙之处在于我们为向量得到的两个数字总是给出一个长度为1个单位(像素)的向量(这称为归一化向量或单位向量)因此cos(a ) 2 + sin(a) 2 = 1
为什么这很重要?因为它使缩放非常容易。假设我们始终保持方面平方,我们只需要一个数字用于比例。要缩放矢量,只需将其乘以比例
即可sin(PI/2) = 1
向量x,y现在是两个单位长。
比使用保存,恢复,旋转,缩放,翻译更好...... :(
现在把它们放在一起创建一个具有任意旋转,缩放和平移(原点)的矩阵
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;
现在将其应用于上面给出的示例(1)
// 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);
}
这就是你使用setTransform简化转换画布的方式,而不是在保存和恢复的范围内进行猜测,试验和错误,缩放,旋转和来回转换。
使用它来简化代码
答案
现在问你的问题
我不完全确定你所追求的是什么,我认为你不介意缩放画布来容纳图像,图像总是在中心并且方面保持不变。当旋转与屏幕对齐时,我将手动设置变换
// 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
答案 1 :(得分:1)
试试这个https://jsfiddle.net/uLdf4paL/2/。您的代码是正确的,但是当您尝试旋转和缩放图像时,您必须更改orientation
变量(如果它是您想要的)。