如何仅使用变换来旋转html画布形状? [英] How do I rotate a html canvas shape by only using a transform?

查看:51
本文介绍了如何仅使用变换来旋转html画布形状?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想知道如何仅通过使用变换功能来旋转图像.根据我的理解,这是不可能的,因为您只能执行以下转换:

I am wondering how you can rotate an image by only using the transform function. From my understanding this is not possible, since the only things you can do with transform are the following:

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

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

我不知道其中任何一个将如何旋转形状,甚至可能.我认为这是有可能的,因为旋转实际上是一种类型转换.

And I don't see how any of these would be able to rotate the shape, is this even possible. I assume it must be possible, since rotate is in-fact a type transformation.

推荐答案

2D变换基础

6个值作为3个向量

转换是一组6个数字.由3对组成的6个数字分别表示x轴的方向和比例,y轴的方向和比例以及原点的位置.

2D transform basics

6 values as 3 vectors

The transform is a set of 6 numbers. The 6 numbers as 3 pairs represent the direction and scale of the x axis, the direction and scale of the y axis, and the position of the origin.

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

The default transform (called the identity matrix) has the values ctx.setTransform(1, 0, 0, 1, 0, 0) meaning that

  • x轴是每个CSS像素在 {x:1,y:0} 从左到右的方向上变换的像素
  • y轴是每个CSS像素沿 {x:0,y:1} 上下方向
  • 原点位于像素位置{x:0,y:0}左上角
  • the x axis is 1 transformed pixel per CSS pixel in the direction {x: 1, y: 0} left to right
  • the y axis is 1 transformed pixel per CSS pixel in the direction {x: 0, y: 1} top to bottom
  • the origin is at pixel location {x: 0, y: 0} top left corner

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

If we scale the transform we increase the length of the first two vectors. To scale by 2 the transform is ctx.setTransform(2, 0, 0, 2, 0, 0);

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

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

If we want to rotate by 90deg a square 256 by 256 image then the transform is ctx.setTransform(0, 1, -1, 0, 256, 0)

  • x轴是每个CSS像素在y方向上向下变换的1个像素 {x:0,y:1}
  • y轴是每个CSS像素在负x方向上的1个变换像素 {x:-1,y:0} 从右到左
  • 原点(图像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

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

We get a rotated image.

向量是两个具有x和y值的值.向量定义方向和长度.

A vector is two values that have a x and y value. The vector defines a direction and length.

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

To convert a direction to a vector we use sin and cos

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

const x = Math.cos(myDirectionAsRadians)
const y = Math.sin(myDirectionAsRadians)

如果将 myDirection 设置为90(度),则 x = 0 y = 1 指向画布

If we set myDirection to 90 (deg) then x = 0 and y = 1 pointing down the canvas

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

Using sin and cos creates a vector in any direction. It has a special property in that its length is always 1. We call such a vector a Unit vector. You may sometimes see a vector being normalized. This converts a vector of any length to a unit vector. It is done by dividing the vector x and y by its length.

 function normalize(vector) {
     const length = Math.hypot(vector.x, vector.y);
     vector.x /= length;
     vector.y /= length;
 }

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

NOTE a vector with zero length eg x: 0, y:0 can not be normalized. Not because it has no length (the length is 0) but because it has no direction.

我们可以定义角度和比例尺

We can define an angle and a scale

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

现在,对于-90度,向量为 x = 0 y = -2 向上且两个CSS像素长.

Now for -90 deg the vector is x = 0 and y = -2 pointing up and two CSS pixels long.

对于均匀的比例尺和旋转度(图像始终为正方形),我们需要的是单个矢量.例如从上面.通过交换两个分量并取反新的x,可以将 x = 0 y = -2 (向上)旋转90 CW.例如, xx = -y y = x 从其中获得 xx = 2 y = 0 2个CSS像素左两右.因此,我们具有x和y轴的方向和比例.y轴始终与x呈90 CW.

For a uniform scale and rotation (the image is always square) all we need is a single vector. For example from the above. x = 0 and y = -2 (pointing up) can be rotated 90 CW by swapping the two components and negating the new x. eg xx = -y and y = x to get xx = 2 and y = 0 2 CSS pixels from left two right. Thus we have the direction and scale of both the x and y axis. With the y axis always 90 CW from the x.

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

To create a transform that rotates any angle and scales by any amount

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

 }

绘制图像

让我们创建一个函数,该函数将在画布上均匀旋转和缩放的任何位置绘制图像.我们将以图像的中心为参考点

Drawing an image

Lets create a function that will draw an image anywhere on the canvas that is rotated and scaled uniformly. We will use the center of the image as the reference point

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

   }

还有更多

当我们使用 ctx.setTranform 设置转换时,我们将替换现有的转换.此转换保持最新状态.如果我们使用 ctx.transform ctx.rotate ctx.scale ctx.translate 到当前转换,您可以分阶段构建转换.

There is much more

When we set the transform with ctx.setTranform we replace the existing transform. This transform remains current. If we use ctx.transform, ctx.rotate, ctx.scale, ctx.translate the transforms are applied to the current transform, you build a transform in stages.

就CPU周期而言,转换功能相对昂贵.与使用 ctx.scale ctx.rotate ctx.translate 进行操作相比,使用sin和cos构建矩阵的方式要快得多从默认开始同样的事情.

The transform functions are relatively expensive in terms of CPU cycles. That is way using sin and cos to build the matrix is much faster than using ctx.scale, ctx.rotate, ctx.translate to do the same thing starting from default.

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

Building transforms can become tricky as we need to keep track of what stage we are at.

我们通常只使用这些功能来转换单个图像(文本,路径或其他内容),而不是创建链接的转换.

We generally only use these function not to transform a single image (text, path, or what ever) but to create linked transforms.

例如游戏对象,例如坦克.坦克的车身先变形(旋转并定位),然后使用 ctx.rotate 与炮塔一起旋转但具有额外的独立旋转的炮塔.完整的解释超出了此问题的范围.

For example a game object like a tank. The body of the tank is transformed (rotated and positioned) then the turret which is rotated with the body but has an additional independent rotation by using ctx.rotate. Full explanation is beyond the scope of this question.

通过所有这些操作,我们可以创建一个简化的函数,该函数将以其中心在任意位置绘制的图像,并进行均匀缩放和旋转

From all this we can create a simplified function that will draw an image with its center at any location, that is uniformly scaled and rotated

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

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

To reset the transform to the default use ctx.resetTransform NOTE not fully supported yet or use ctx.setTransform(1,0,0,1,0,0);

使用上述功能是绘制动画旋转缩放图像的第二快的方法,比CSS + HTML或SVG更快.您可以从字面上填满动画图像.

Using the above function is the 2nd fastest way to draw animated rotated scaled images, faster than CSS + HTML or SVG. You can literally fill the screen with animated images.

var w,h;
var image = new Image;
image.src = "https://i.stack.imgur.com/C7qq2.png?s=328&g=1";
var canvas = document.createElement("canvas");
var ctx = canvas.getContext("2d");
canvas.style.position = "absolute";
canvas.style.top = "0px";
canvas.style.left = "0px";
document.body.appendChild(canvas);
const resize = () => { w = canvas.width = innerWidth; h = canvas.height = innerHeight;}
const rand = (min,max) => Math.random() * (max ?(max-min) : min) + (max ? min : 0);
const DO = (count,callback) => { while (count--) { callback(count) } }

resize();
addEventListener("resize",resize);

const sprites = [];
DO(500,()=>{
    sprites.push({
       xr : rand(w), yr : rand(h),
       x : 0, y : 0, // actual position of sprite
       r : rand(Math.PI * 2),
       scale : rand(0.1,0.25),
       dx : rand(-2,2), dy : rand(-2,2),
       dr : rand(-0.2,0.2),
    });
});

function drawImage(image, spr){
    const xAX = Math.cos(spr.r) * spr.scale;
    const xAY = Math.sin(spr.r) * spr.scale;
    ctx.setTransform(xAX, xAY, -xAY, xAX, spr.x, spr.y); 
    ctx.drawImage(image, -image.width / 2, -image.height / 2);
}

function update(){
    var ihM,iwM;
    ctx.setTransform(1,0,0,1,0,0);
    ctx.clearRect(0,0,w,h);
    if(image.complete){
      var iw = image.width;
      var ih = image.height;
      for(var i = 0; i < sprites.length; i ++){
          var spr = sprites[i];
          spr.xr += spr.dx;
          spr.yr += spr.dy;
          spr.r += spr.dr;
          // keeps images in canvas adds space to all sides so that image
          // can move completely of the canvas befor warping to other side
          // I do this to prevent images visualy popping in and out at edges
          iwM = iw * spr.scale * 2 + w;
          ihM = ih * spr.scale * 2 + h;
          spr.x = ((spr.xr % iwM) + iwM) % iwM - iw * spr.scale;
          spr.y = ((spr.yr % ihM) + ihM) % ihM - ih * spr.scale;
          
          drawImage(image,spr);
      }
    }    
    requestAnimationFrame(update);
}
requestAnimationFrame(update);

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

If you are wondering which is the fastest way to draw animated content. That is via webGL. The above can draw 1000 scaled rotated images on most devices at a good frame rate. WebGL can easily draw 10000 (with extra features eg colored) in the same time.

这篇关于如何仅使用变换来旋转html画布形状?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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