canvas转换方法与转换矩阵

1,137 阅读3分钟
canvas转换方法与转换矩阵

背景

最近做项目,遇到这样一个场景:需要将视频旋转方位,随时可以切换,且一个页面要展示多个同样的视频源,并且大小和方位不一致。由于该项目是实时通话的场景,底层依赖于外部,不支持多个id播放。因此,复制播放的video就成了解决方案。

视频就是有多张连续的图片在时间上的不断堆叠和切换,切换频率大于我们的视觉反应速度(每秒帧数),于是就有了动态效果,让人感觉不到其实看到的是一张张图片。最传统的电影放映就可以直观地感受到其中的原理,苏乞儿在悟透亢龙有悔也是基于此原理(手动狗头)。最终,我选择了定时从视频源截取图片,使用canvas绘图实现了该功能。

1.PNG

原理

本文将围绕如何实现如上图的旋转进行讲解,不讲canvas绘图入门,主要讲canvas如何实现图像的平移、旋转、伸缩操作。canvas的转换方法有如下几种:

方法描述
scale()缩放当前绘图至更大或更小
rotate()旋转当前绘图
translate()重新映射画布上的 (0,0) 位置
transform()替换绘图的当前变换矩阵
setTransform()将当前转换重置为单位矩阵。然后运行 transform()

转换方法可以理解为:将原来坐标(x0,y0)(x_0,y_0)转换为新坐标(x,y)(x,y)的一种运算。scale实现了x轴和y轴的拉伸或者压缩;rotate指的是绕原点旋转多少角度;translate指的是进行平移操作;transform则可以一次性进行缩放、旋转、平移;setTransform和transform一样,区别在于transform是在已有的变换矩阵(稍后讲解)的基础上,继续transform,而setTransform则先把原有转换矩阵置位单位矩阵,再进行transform运算。说人话:transform是相对与目前的状态继续操作;setTransform则是在最初始状态的操作

变换矩阵是数学线性代数中的一个概念。在线性代数中,线性变换能够用矩阵来表示。

T(x0y0)=(xy)T \left(\begin{matrix} x_0 \\y_0 \end{matrix} \right)=\left(\begin{matrix} x \\y \end{matrix} \right)

对于二维空间的坐标变换可以用以下公式表示:

T(x0y01)=(xy1)[acebdf001](x0y01)=(ax0+cy0+ebx0+dy0+f1)=(xy1)T \left(\begin{matrix} x_0 \\y_0 \\1 \end{matrix} \right)=\left(\begin{matrix} x \\y \\ 1 \end{matrix} \right) \\\left[ \begin{matrix} a & c & e \\ b & d & f \\ 0 & 0 & 1 \end{matrix} \right] \left(\begin{matrix} x_0 \\y_0 \\1 \end{matrix} \right) =\left(\begin{matrix} ax_0+cy_0+e \\bx_0+dy_0+f \\ 1 \end{matrix} \right) =\left(\begin{matrix} x \\y \\ 1 \end{matrix} \right)

canvas 的transform对应的调用方法为: transform(a,b,c,d,e,f)。

如何计算所需要的transform

当我们知道变换前和变换后的位置,就可以求解变换矩阵T。旋转矩阵有6个变量,我们只需要3个点即可(3点决定一个面;六元一次方程需要六个等式,一个点两个变量,所以需要3个点)。

在canvas坐标系中(应该说屏幕坐标系):左上角为原点(0,0),向右为正x轴,向下为正y轴,右下角的坐标为(w,h)。图1变换前后的过程如下所示:

2.jpg

w和h分别为原图片的宽和高,我们选取1、2、3点进行方程的求解。虽然任意选择不共线的3个点都可以求解出来,但是由于1、2、3的0比较多,方便求解,可以得到以下方程组:

0+0+e=00+0+f=0aw+0+e=0bw+0+f=w0+ch+e=h0+dh+f=00+0+e=0\\0+0+f=0\\aw+0+e=0\\bw+0+f=w\\0+ch+e=h\\0+dh+f=0\\

可以轻松求解,得出变换矩阵:

a=d=e=f=0b=c=1T=[010100001]a=d=e=f=0\\ b=c=1\\ T=\left[ \begin{matrix} 0 & 1 & 0 \\ 1 & 0 & 0 \\ 0 & 0 & 1 \end{matrix} \right]

js实现代码如下:

<body>
  <img id="src" src="./老泉.jpg" />
  <canvas id="dist"></canvas>
  <script>
    const img = document.getElementById("src");
    img.onload = () => {
      const canvas = document.getElementById("dist");
      const ctx = canvas.getContext("2d");
      const newWidth = img.width;
      const newHeight = img.height;
      canvas.width = newHeight; //newHeight
      canvas.height = newWidth; // newWidth
      ctx.transform(0, 1, 1, 0, 0, 0);
      ctx.drawImage(img, 0, 0, img.width, img.height, 0, 0, newWidth, newHeight);
    }
  </script>
</body>

其他变换方向或者大小变换可以手工推导试试。如果需要进行缩放验证,需要注意drawImage函数的最后两个参数,最后两个参数表示新图片的宽和高,如有缩放,也需同步变化。可以参考一代码:

<body>
  <img id="src" src="./老泉.jpg" />
  <canvas id="dist"></canvas>

  <script>
    const img = document.getElementById("src");
    img.onload = () => {

      const canvas = document.getElementById("dist");
      const ctx = canvas.getContext("2d");
      const radio = 0.5;
      const newWidth = radio * img.width;
      const newHeight = radio * img.height;
      canvas.width = newHeight; //newHeight
      canvas.height = newWidth; // newWidth
      ctx.transform(0, 1, 1, 0, 0, 0);
      // ctx.transform(0, 1, -1, 0, newHeight, 0);
      // ctx.transform(0, -1, 1, 0, 0, newWidth);
      ctx.drawImage(img, 0, 0, img.width, img.height, 0, 0, newWidth, newHeight);
    }
  </script>
</body>

其中,注释的行是其他几种旋转的矩阵,有兴趣的读者可以验证一下。