如何仅使用变换来旋转 html 画布形状?

Roc*_*lla 1 html javascript canvas transformation rotation

我想知道如何仅使用变换函数来旋转图像。根据我的理解,这是不可能的,因为您可以使用转换做的唯一事情如下:

  • 水平缩放
  • 水平歪斜
  • 垂直倾斜
  • 垂直缩放
  • 水平移动
  • 垂直移动

来源:https ://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Transformations

我不知道其中任何一个如何能够旋转形状,这是否可能。我认为这一定是可能的,因为旋转实际上是一种类型转换。

Bli*_*n67 9

2D 变换基础知识

6 个值作为 3 个向量

变换是一组 6 个数字。6 个数字为 3 对,分别代表 x 轴的方向和比例、y 轴的方向和比例以及原点的位置。

默认变换又名单位矩阵

默认变换(称为单位矩阵)的值ctx.setTransform(1, 0, 0, 1, 0, 0)意味着

  • x 轴是{x: 1, y: 0}从左到右方向上每个 CSS 像素 1 个变换像素
  • y 轴是{x: 0, y: 1}从上到下方向上每个 CSS 像素 1 个变换像素
  • 原点位于左上角的像素位置 {x: 0, y: 0}

缩放

如果我们缩放变换,我们就会增加前两个向量的长度。要缩放 2,变换为ctx.setTransform(2, 0, 0, 2, 0, 0);

  • {x: 2, y: 0}x 轴是 x 方向上从左到右每 2 个 CSS 像素 1 个变换像素
  • {x: 0, y: 2}y 轴是 y 方向从上到下每 2 个 CSS 像素 1 个变换像素
  • 原点仍然是左上角{x: 0, y: 0}

旋转和平移

如果我们想要将 256 x 256 的正方形图像旋转 90 度,则变换为ctx.setTransform(0, 1, -1, 0, 256, 0)

  • x 轴是 y 方向向下每个 CSS 像素 1 个变换像素{x: 0, y: 1}
  • {x: -1, y: 0}y 轴是从右到左负 x 方向上每个 CSS 像素 1 个变换像素
  • 原点(图像 0, 0 将在画布上的位置)是 {x: 256, y: 0}

因此如果我们运行

ctx.setTransform(0, 1, -1, 0, 256, 0);
ctx.drawImage(myImage, 0, 0, 256, 256); // will draw image rotated 90deg CW
Run Code Online (Sandbox Code Playgroud)

我们得到一个旋转的图像。

一个向量

向量是具有 ax 和 y 值的两个值。该矢量定义方向和长度。

创建旋转的单位向量

要将方向转换为向量,我们使用 sin 和 cos

const myDirection = angle;
const myDirectionAsRadians = angle * (Math.PI / 180);  // convert angle to radians

const x = Math.cos(myDirectionAsRadians)
const y = Math.sin(myDirectionAsRadians)
Run Code Online (Sandbox Code Playgroud)

如果我们设置myDirection为 90(度)然后x = 0指向y = 1画布

使用 sin 和 cos 可以创建任意方向的向量。它有一个特殊的属性,即它的长度始终为 1。我们将这样的向量称为单位向量。有时您可能会看到向量被标准化。这会将任意长度的向量转换为单位向量。它是通过将向量 x 和 y 除以它的长度来完成的。

 function normalize(vector) {
     const length = Math.hypot(vector.x, vector.y);
     vector.x /= length;
     vector.y /= length;
 }
Run Code Online (Sandbox Code Playgroud)

注意,长度为零的向量x: 0, y:0不能被归一化。不是因为它没有长度(长度为0)而是因为它没有方向。

缩放旋转向量

我们可以定义角度和比例

const myDirection = -90;
const myDirectionAsRadians = -90 * (Math.PI / 180);  // -90 as radians
const myScale = 2;

const x = Math.cos(myDirectionAsRadians) * myScale
const y = Math.sin(myDirectionAsRadians) * myScale
Run Code Online (Sandbox Code Playgroud)

现在,对于 -90 度,向量是x = 0指向y = -2上方且两个 CSS 像素长。

快速旋转矢量 90 度 CW

对于统一的缩放和旋转(图像始终是正方形),我们需要的只是一个向量。例如从上面的例子。x = 0y = -2(指向上方)可以通过交换两个分量并否定新的 x 来顺时针旋转 90 度。例如xx = -y,从左到右y = x获取2 个 CSSxx = 2像素。y = 0这样我们就有了 x 轴和 y 轴的方向和比例。y 轴始终与 x 顺时针 90 度。

使用变换来绘制

创建旋转和缩放变换

创建可旋转任意角度并缩放任意量的变换

 function scaleAndRotate(scale, rotate) { // rotate is in radians
     // get direction and length of x axis
     const xAX = Math.cos(rotate) * scale;
     const xAY = Math.sin(rotate) * scale;
     // get direction and length of y axis that is 90 deg CW of x axis and same length
     const [yAX, yAY] = [-xAY, xAX];  // swap and negate new x

     // set the transform
     ctx.setTransform(xAX, xAY, yAX, yAY, 0, 0);

 }
Run Code Online (Sandbox Code Playgroud)

绘制图像

让我们创建一个函数,该函数将在画布上的任意位置绘制统一旋转和缩放的图像。我们将使用图像的中心作为参考点

 function drawImageScaleRotate(img, x, y, scale, rotate) {
      // define the direction and scale of x axis
      const xAX = Math.cos(rotate) * scale;
      const xAY = Math.sin(rotate) * scale;

      // create the transform with yaxis at 90 CW of x axis and origin at x, y
      ctx.setTransform(xAX, xAY, -xAY, xAX, x, y);

      // Draw the image so that its center is at the new origin x, y
      ctx.drawImage(img, -img.width / 2, -img.height / 2);

   }
Run Code Online (Sandbox Code Playgroud)

还有更多

当我们设置变换时,ctx.setTranform我们替换现有的变换。此转换仍然有效。如果我们使用ctx.transform, ctx.rotate, ctx.scalectx.translate变换将应用于当前变换,您可以分阶段构建变换。

就 CPU 周期而言,转换函数相对昂贵。也就是说,使用 sin 和 cos 构建矩阵比使用ctx.scale, ctx.rotate,ctx.translate从默认值开始执行相同的操作要快得多。

构建转换可能会变得很棘手,因为我们需要跟踪我们所处的阶段。

我们通常只使用这些函数来创建链接的变换,而不是变换单个图像(文本、路径或其他图像)。

例如坦克等游戏对象。坦克的车体发生变形(旋转和定位),然后炮塔随车体旋转,但通过使用具有额外的独立旋转ctx.rotate。完整的解释超出了这个问题的范围。

最终函数

由此我们可以创建一个简化的函数,该函数将绘制一个以任意位置为中心的图像,并且统一缩放和旋转

 function drawImageScaleRotate(img, x, y, scale, rotate) {
      const xAX = Math.cos(rotate) * scale;
      const xAY = Math.sin(rotate) * scale;
      ctx.setTransform(xAX, xAY, -xAY, xAX, x, y);
      ctx.drawImage(img, -img.width / 2, -img.height / 2);
   }
Run Code Online (Sandbox Code Playgroud)

要将转换重置为默认值,请使用ctx.resetTransform “注释”尚未完全支持或使用ctx.setTransform(1,0,0,1,0,0);

动画转换速度比 CSS + HTML 或 SVG 更快

使用上述函数是绘制动画旋转缩放图像的第二快方法,比 CSS + HTML 或 SVG 更快。您实际上可以用动画图像填充屏幕。

演示

ctx.setTransform(0, 1, -1, 0, 256, 0);
ctx.drawImage(myImage, 0, 0, 256, 256); // will draw image rotated 90deg CW
Run Code Online (Sandbox Code Playgroud)

如果您想知道哪种是绘制动画内容最快的方法。那是通过 webGL。以上可以在大多数设备上以良好的帧速率绘制 1000 个缩放旋转图像。WebGL 可以轻松地同时绘制 10000 个(具有额外功能,例如彩色)。