阅读 93

canvas在transform的时候都做了些啥?

欣仔最近在为厂里设计一个canvas框架,这个框架的目的是为了让vue 或者react 的脚手架在构建应用并且需要引用外部canvas框架的时候,使得框架和当前构建环境本身能够更好的融合,以及,这个框架支持webgl渲染。

当然在此期间之前有些曾经用到过但也是一带而过的技术点,顺带这次也跟着夯实了一下。

这篇文章讲述的是一些原理性的东西,如果作为开发人员你的你能够比较有耐性的看完,说明你有很大的潜力在“35”岁之后继续从事这份职业,或许还能保持一点相对的高薪,想必也是极好的。


所以还是要 WARNNING 一下:

数学不好者退散!!!

大家都知道canvas现如今在H5项目中扮演的角色,也都大概知道canvas的一些基本的操作,常规的诸如:

context.moveTo(100,100);
contect.lineTo(100,200);复制代码

这样的用法欣仔就不多说了,如果还有人不知道的,如果你已经不是初学者了,那对于这样的基本操作还不了解的话,那自然是不可原谅的了,自打300大板然后面壁思过之后去查漏补缺吧。。。

说到这里,你们认为作为新时代的“前端开发者”,需要了解nodejs么?

欣仔今天话风比较收不住,厂里面隔壁中心的某产品与我对接工作的时候,说到他们组的前端不会nodejs。

于是我就随口说了一句:“现在的前端不会nodejs是要被淘汰掉的”。。之后,他再也没有回我,大概是我说错话了。



当然我现在还不能保证我的组员每个人都能够掌握nodejs的相关操作,但是我也在引导他们往这条路上前行。

不过回到主题,欣仔在这篇文章中想要说的是诸如

context.translate(100,100);
context.scale(0.5);
context.rotate(20)复制代码

这些操作是如何影响到绘图的时候点的坐标的,其中的原理虽然不需要各位开发常规H5的时候去付诸实现,但是其却在其他场景中扮演者极其重要的功能,包括色阶的计算,滤镜的计算,以及更接近于底层的opengl/webgl的对应功能的运算原理都是一致的,市面上的3D引擎内三角块儿的顶点坐标的运算,也都是通过一样的方式实现的。

大概你们想不到(大佬除外)如果是一个

x+1//x坐标加1复制代码

这样一个明眼人都能够知道的非常简单的操作,在

translateX(x+1)复制代码

的时候都,内部都经历了些啥?

这里欣仔不经联想到了一种叫“认知隔阂”的东西,啥叫“认知隔阂”?欣仔我自己瞎掰的。。。大概意思就是,你所认为的简单的功能,实现起来比你想象得要复杂的多。

那实现一个x位移的数值加一有什么复杂的?的确不复杂,但是要实现一整套类似的功能集,比如x坐标的旋转,x坐标的缩放等操作,就相对比较繁琐,特别是处理一整块坐标集的时候。

其实程序员本身对于自己不了解的领域,依然也会有认知隔阂,有时候甚至也会有点想当然。这像极了产品经理或者account妹子相对于欣仔这样的程序员沟通的时候产生的认知隔阂。


回归正题:

context.translate(100,100);复制代码

这段代码的字面意思是画布的绘制区域移动分别移动至坐标(100,100)的位置,然后开始绘制。

这大概就是认知给我们造成的一种假象,因为字面意思和最终效果的呈现可以完美的匹配。但实际上他是怎么去实现位置的变换的呢?

前人的科学家以他们的聪明才智,给了我们一套不太为人知,却又高效的内在实现机制:矩阵运算,学术上叫。。。额。。。。线性代数。

当我们认为的坐标(100,100)在矩阵中可以描述为:

|100, 0,  0,|
|0,  100, 0,|
|tx, ty,  1 |复制代码

x,y的值分别取了这个“矩阵”的‘00’位置和‘11’位置的值,分别再在对应的数值上加上tx 和 ty 。初始情况下,一个未做过变换的矩阵信息是:

|100, 0,  0,|
|0,  100, 0,|
|0,   0,  1 |复制代码

即:tx,ty都为0。针对于当前矩形,应用于他的元素的坐标的x和y分别都是

100+0复制代码

这只是利用矩阵计算坐标信息的一种约定方式,你看到的一个坐标的信息实际上包含了这么多数据,如果你想给一个元素设置一个具体的坐标,除了直观的x,y的直接赋值,还可以通过给他这样一个矩阵,让 x,y 做标与之建立关系映射。

那么当我们想让(100,100)这个坐标变成(120,150)的时候,通过矩阵该怎么实现呢?

矩阵相乘

这是几乎所有我所见过的关于位移,以及像素颜色处理的基本公式。一个3x3的矩阵,与另外一个3*3的矩阵相乘,得到另外一个3*3的矩阵,更复杂的会是也会有4x4的矩阵相乘。

在这里呢,得到的那个结果矩阵,就根据上面欣仔提到的一个关系关联到对应的坐标。

比如:

|100, 0, 0,|
|0, 100 ,  0|
|0, 0, 1,|  
     x 
|1, 0 , 0| 
|0, 1, 0 |    
|tx,ty,1 |复制代码

这样的一个乘法,会得到一个什么样的结果?

根据矩阵的运算规则,如下简单的描述



因为本身这篇不是用来科普矩阵运算的,所以关于其计算过程也只能一笔带过了,如要欣仔去科普更多的东西,那也超过欣仔的知识范围了。当然如果大家有兴趣的话可以去搜一下线性代数的基础,希望有所收获。现如今的线性代数已经大量的应用于人工智能相关的计算中了,这大概就是线性代数的魅力所在吧。当然我们没有处在研究领域,所以只需知道相关的应用方式即可啦。

再次回归正题:我上面提到的相乘得过程应该是


|100, 0,  0,|
|,   100 , 0|
|0,  0, 1 ,|  
        x 
|1,   0 , 0|
|0,   1,  1 |
|tx,  ty,1 |复制代码

简化的结果为:

|100,0, 0 |
|0, 100,0 |
|tx,ty, 1 |复制代码

根据上面提到的赋值方式,实际的结果就是(100+tx,100+ty)。

目标点如果是(120,150)的话,tx和ty分别就是20 和 50.

求乘矩阵就是


|1,   0,  0 |
|0,   1,  0 |
|20,  50, 1 |复制代码


因为默认情况下,左边的矩阵对应的元素默认的位置信息,tx和ty默认为0,右边的求乘矩形(以下都成为求乘矩阵)用于变换相乘,3x3的矩阵任何一个位置都可以赋值,结合这个公式,可以做出成各种各样的变换。

看起来是个非常复杂的过程,以至于包括欣仔在内的大部分人来说,见到这样的场景也是需要先深吸一口气的。但稍加琢磨发现其也并不是特别复杂不是么。特别是当你想要做一些高级的动效的时候,利用矩阵去计算会是一个非常不错的方法。

上面说了移动,接下来说

缩放

当我们需要将(100,100)缩放到(50,50)的时候,结合与上面的矩阵关系,我们只需要进行矩阵运算

|100, 0,  0,|
|0.5, 0 , 0|
|0,  100, 0,|  
x 
|0, 0.5 , 0|
|0,   0,  1 |
|0,  0,  1 |  
 =
|50,0, 0 |
|0, 50,0 |
|0,0, 1  |复制代码

缩放的概念需要结合点集才能有效的表现,比如有10000个分布在画布上的点坐标构成了一个猫的形状,当这些坐标的数值统一缩放0.5倍,则可以得到一个缩放一半的的猫的形状。


说完缩放说说坐标转换


坐标转换是一个相对比较复杂的运算,坐标转换的需求可以用下面的图示表示:假设我现在的坐标点是A(x,y),旋转了特定的角度到C(c,d),目前只知道角度的大小angle,求出C的坐标。

(能看到这里吐血的人也退下吧,欣仔我对不住了。。。)


推导的过程我也不多说了,就直接用下面这个公式

c=cos(angle)*x-sin(angle)*y;
d=cos(angle)*y+sin(angle)*x;复制代码

如果把相同的因子用单独的变量表示如下

let cos=cos(angle);
let sin=sin(angle);
x1=cos*x-sin*y;y1=cos*y+sin*x;复制代码

把他放到我们的矩阵的计算结果中描述如下:

|cos*x-sin*y,  0,      0|
|0,        cos*y+sin*x,0|
|0,            0,      1|   复制代码

那么再往上推导,可以得出求乘矩阵就是


|cos,sin, 0 |
|-sin,cos,0 |
|0,   0,  1 |复制代码

所以我们要做一个坐标转换的功能,只需用上诉得出的求乘矩阵做一个这样的运算就可以了:


|100, 0,  0,|
|0, 100 ,  0|
|0,  0, 1,| 
 x 
|cos, -sin , 0|
|sin,   cos,  1 | 
|0,     0,  1 |  
=
|cos*x-sin*y,  0,      0|
|0,        cos*y+sin*x,0|
|0,            0,      1|复制代码


很感谢能够看到这里的同学。。按照欣仔的认知范围,能够看到这里的人。。大概也就寥寥无几了,大概各自的心理也如同”人生太过艰辛,求放过“这样的感慨。只是想到那可怜巴巴的阅读数,欣仔的心也在滴血啊。



所以当我需要对点同时缩放,旋转,以及移动的是时候,原矩阵用三个不同的矩阵做三次乘法。假设场景为:缩放0.5,旋转60度,以及向左向下平移100像素,计算过程如下:

原矩阵
|100, 0,  0,|
|0,  100, 0,|
|0,   0,  1 |     
X
平移矩阵
|1,   0,  0 |
|0,   1,  0 |
|100, 100,1 |     
X     
旋转矩阵
|cos(60),-sin(60), 0 |
|sin(60),cos(60),0 |
|0,      0,       1 |     
X     
缩放矩阵
|0.5,   0,  0 |
|0,   0.5,  0 |
|0,     0,  1 |复制代码

结果是那种作为读者的你第一眼无法看出端倪的矩阵值,所以就不贴出来了。

欣仔含着泪写到这里,也不想再多写了,生怕再写下去,就有人要取关了,所以我也要小心翼翼的结尾了。

后面的分享将结合以上所说的原理,做一篇实操的分享。将利用大家一致在争论的“TS究竟有什么用”这个话题的主角:TypeScript 去做一次开发分享给你们,希望对大家有所帮助。

如果喜欢还没关注的话,就请扫描一下金光闪闪的QRCODE关注吧。


关注下面的标签,发现更多相似文章
评论