无处不在的前端矩阵变换(下)

简介: 在前端开发中不论CSS3也好,SVG也好,还是Canvas,无论多复杂的图形,都是由一个个点组成的。一个元素渲染后就可以得到一张位图,然后对这个位图上每一点进行变换,就可以得到新的一张位图,从而产生了视觉上的平移translate、旋转rotate、缩放scale、拉伸skew 等效果。这一切都是变换矩阵的功劳。

canvas中的矩阵变换


同样结合一个canvas开源库 fabric.js 来说下canvas中的矩阵变换应用,这个库实现很牛叉,里面有大量的功能点,比如: 通过 canvas 绘制的元素如何解决Retina 屏中元素模糊的问题,以及如何采用离线canvas做性能提升等等。


<!--dom 作为canvas绘制画布-->
<div id="drawing"></div>
<!--js-->
let canvas = new fabric.Canvas('canvas', { width: "375", height: "500" })
let react = new fabric.Rect({ width: 100, height: 100, left: 100, top: 100, fill: 'red', angle: 10 })
canvas.add(react)


如图:



同样说下fabric.js 是如何绘制的


<!-- fabric.js 部分代码截取,有大量的代码省略,只挑取关键流程代码 -->
canvas.add(react)
=>
add: function () {
  /* 变量说明:
    this => new fabric.Canvas 实例对象
  */
  this.renderOnAddRemove && this.requestRenderAll(); 
  return this; 
},
// ...省略部分代码...
renderAll: function () {
  /* 变量说明:
    this => new fabric.Canvas 实例对象
    this.contextContainer => 如上图class="lower-canvas" canvas元素的2d绘图上下文
    this._chooseObjectsToRender() => 获取要渲染的object对象,这里就是react实例对象
  */
  var canvasToDrawOn = this.contextContainer;
  this.renderCanvas(canvasToDrawOn, this._chooseObjectsToRender()); 
  return this;
},
renderCanvas: function(ctx, objects) {
  /* 变量说明:
    this => new fabric.Canvas 实例对象
    ctx => 如上图class="lower-canvas" canvas元素的2d绘图上下文
    objects =>  fabric.js 创建的react实例对象
  */
  // 擦除lower-canvas
  this.clearContext(ctx);  
  ctx.save();
  // 对object元素进行绘制draw
  this._renderObjects(ctx, objects);
  ctx.restore();
},
// ...省略部分代码,最终调用object的render方法,如下 ...
render: function(ctx) {
  /* 变量说明:
    this => fabric.js 创建的react实例对象
    ctx => 如上图class="lower-canvas" canvas元素的2d绘图上下文
  */
  ctx.save();  
  // 对ctx坐标进行变换 很重要 
  this.transform(ctx);
  // 如果元素需要走缓存策略:则会创建缓存canvas, 然后在缓存canvas上绘制object对象(这里是fabric.js 创建的react实例对象),最后再把缓存canvas => ctx.drawImage(this._cacheCanvas, -this.cacheTranslationX, -this.cacheTranslationY); 绘制到lower-canvas上
  if (this.shouldCache()) {
    // 创建了一个缓存canvas、并在缓存canvas上draw绘制object元素
    this.renderCache();
    // 然后将缓存的canvas绘制到lower-canvas中
    this.drawCacheOnCanvas(ctx);
  }
  else {
    this._removeCacheCanvas();
    this.drawObject(ctx);
  }
  ctx.restore();
  // ...省略部分代码...
},
// 接下来主要看 this.transform(ctx); 对lower-canvas的坐标变换
transform: function(ctx) {
  var m;
  if (this.group && !this.group._transformDone) {
    m = this.calcTransformMatrix();
  }
  else {
    // 根据object的信息, 计算矩阵
    m = this.calcOwnMatrix();
  }
  // 将lower-canvas坐标系平移到
  ctx.transform(m[0], m[1], m[2], m[3], m[4], m[5]);
},
calcOwnMatrix: function() {
  // ...省略部分代码...
  /* 变量说明:
    this => fabric.js 创建的react实例对象,也即是object
  */
  var tMatrix = this._calcTranslateMatrix();
  this.translateX = tMatrix[4];
  this.translateY = tMatrix[5];
  cache.key = key;
  cache.value = fabric.util.composeMatrix(this);
  return cache.value;
},
..........  下面这部分是计算object经过scale、rotate等后的中心点坐标 ....................
_calcTranslateMatrix: function() {
  var center = this.getCenterPoint();
  return [1, 0, 0, 1, center.x, center.y];
},
getCenterPoint: function() {
  var leftTop = new fabric.Point(this.left, this.top);
  // 这里的 this.originX, this.originY就是rotate的旋转参考点
  return this.translateToCenterPoint(leftTop, this.originX, this.originY);
},
translateToCenterPoint: function(point, originX, originY) {
  // 根据this.originX, this.originY 计算object中心点center
  var p = this.translateToGivenOrigin(point, originX, originY, 'center', 'center');
  // 计算得到的center点后,再计算旋转后的点坐标
  if (this.angle) {
    return fabric.util.rotatePoint(p, point, degreesToRadians(this.angle));
  }
  return p;
},
rotatePoint: function(point, origin, radians) {
  // 这里计算旋转后的坐标,先减去旋转参考点this.originX, this.originY 的值,然后将得到的值经过旋转radians角度后,再将得到的坐标加上参考点this.originX, this.originY的值,既可得到最终的坐标值
  point.subtractEquals(origin);
  var v = fabric.util.rotateVector(point, radians);
  return new fabric.Point(v.x, v.y).addEquals(origin);
},
rotateVector: function(vector, radians) {
  var sin = fabric.util.sin(radians),
      cos = fabric.util.cos(radians),
      rx = vector.x * cos - vector.y * sin,
      ry = vector.x * sin + vector.y * cos;
  return {
    x: rx,
    y: ry
  };
},
..........  下面这段是对 fabric.util.composeMatrix(this) 说明....................
composeMatrix: function(options) {
  /* 变量说明:
    options => fabric.js 创建的react实例对象,也即是object
  */
  // 平移中心点的矩阵
  var matrix = [1, 0, 0, 1, options.translateX || 0, options.translateY || 0],
      multiply = fabric.util.multiplyTransformMatrices;
  // 平移中心点的矩阵 * 旋转对应的矩阵
  if (options.angle) {
    matrix = multiply(matrix, fabric.util.calcRotateMatrix(options));
  }
  // 平移中心点的矩阵 * 旋转对应的矩阵 * 缩放及拉伸的矩阵
  if (options.scaleX || options.scaleY || options.skewX || options.skewY || options.flipX || options.flipY) {
    matrix = multiply(matrix, fabric.util.calcDimensionsMatrix(options));
  }
  return matrix;
},
// 3*3 的矩阵相乘
multiplyTransformMatrices: function(a, b, is2x2) {
  // Matrix multiply a * b
  return [
    a[0] * b[0] + a[2] * b[1],
    a[1] * b[0] + a[3] * b[1],
    a[0] * b[2] + a[2] * b[3],
    a[1] * b[2] + a[3] * b[3],
    is2x2 ? 0 : a[0] * b[4] + a[2] * b[5] + a[4],
    is2x2 ? 0 : a[1] * b[4] + a[3] * b[5] + a[5]
  ];
},
// 计算旋转的矩阵坐标
calcRotateMatrix: function(options) {
  if (!options.angle) {
    return fabric.iMatrix.concat();
  }
  var theta = fabric.util.degreesToRadians(options.angle),
      cos = fabric.util.cos(theta),
      sin = fabric.util.sin(theta);
  return [cos, sin, -sin, cos, 0, 0];
},
// 计算缩放和拉伸的矩阵坐标
calcDimensionsMatrix: function(options) {
  var scaleX = typeof options.scaleX === 'undefined' ? 1 : options.scaleX,
      scaleY = typeof options.scaleY === 'undefined' ? 1 : options.scaleY,
      scaleMatrix = [
        options.flipX ? -scaleX : scaleX,
        0,
        0,
        options.flipY ? -scaleY : scaleY,
        0,
        0],
      multiply = fabric.util.multiplyTransformMatrices,
      degreesToRadians = fabric.util.degreesToRadians;
  if (options.skewX) {
    scaleMatrix = multiply(
      scaleMatrix,
      [1, 0, Math.tan(degreesToRadians(options.skewX)), 1],
      true);
  }
  if (options.skewY) {
    scaleMatrix = multiply(
      scaleMatrix,
      [1, Math.tan(degreesToRadians(options.skewY)), 0, 1],
      true);
  }
  return scaleMatrix;
},
// 最后将经过平移中心点坐标矩阵 * 旋转对应的矩阵 * 缩放及拉伸的矩阵计算得到的matrix值设置到lower-canvas坐标系中, 如上transform方法所示
ctx.transform(m[0], m[1], m[2], m[3], m[4], m[5]);
//接下来进行元素的绘制, 上面render方法中由于篇幅的原因绘制过程就不展开了
...


到这里终于把fabric.js 绘制元素过程中坐标矩阵的变换说完了,具体的代码细节自行查阅源码吧!


最后


了解矩阵变换的概念,旨在揭开相关开源代码实现的背后细节。


相关文章
|
前端开发
无处不在的前端矩阵变换(上)
在前端开发中不论CSS3也好,SVG也好,还是Canvas,无论多复杂的图形,都是由一个个点组成的。一个元素渲染后就可以得到一张位图,然后对这个位图上每一点进行变换,就可以得到新的一张位图,从而产生了视觉上的平移translate、旋转rotate、缩放scale、拉伸skew 等效果。这一切都是变换矩阵的功劳。
|
3月前
|
存储 人工智能 前端开发
前端大模型应用笔记(三):Vue3+Antdv+transformers+本地模型实现浏览器端侧增强搜索
本文介绍了一个纯前端实现的增强列表搜索应用,通过使用Transformer模型,实现了更智能的搜索功能,如使用“番茄”可以搜索到“西红柿”。项目基于Vue3和Ant Design Vue,使用了Xenova的bge-base-zh-v1.5模型。文章详细介绍了从环境搭建、数据准备到具体实现的全过程,并展示了实际效果和待改进点。
266 14
|
3月前
|
JavaScript 前端开发 程序员
前端学习笔记——node.js
前端学习笔记——node.js
76 0
|
3月前
|
人工智能 自然语言处理 运维
前端大模型应用笔记(一):两个指令反过来说大模型就理解不了啦?或许该让第三者插足啦 -通过引入中间LLM预处理用户输入以提高多任务处理能力
本文探讨了在多任务处理场景下,自然语言指令解析的困境及解决方案。通过增加一个LLM解析层,将复杂的指令拆解为多个明确的步骤,明确操作类型与对象识别,处理任务依赖关系,并将自然语言转化为具体的工具命令,从而提高指令解析的准确性和执行效率。
|
3月前
|
存储 弹性计算 算法
前端大模型应用笔记(四):如何在资源受限例如1核和1G内存的端侧或ECS上运行一个合适的向量存储库及如何优化
本文探讨了在资源受限的嵌入式设备(如1核处理器和1GB内存)上实现高效向量存储和检索的方法,旨在支持端侧大模型应用。文章分析了Annoy、HNSWLib、NMSLib、FLANN、VP-Trees和Lshbox等向量存储库的特点与适用场景,推荐Annoy作为多数情况下的首选方案,并提出了数据预处理、索引优化、查询优化等策略以提升性能。通过这些方法,即使在资源受限的环境中也能实现高效的向量检索。
|
3月前
|
机器学习/深度学习 弹性计算 自然语言处理
前端大模型应用笔记(二):最新llama3.2小参数版本1B的古董机测试 - 支持128K上下文,表现优异,和移动端更配
llama3.1支持128K上下文,6万字+输入,适用于多种场景。模型能力超出预期,但处理中文时需加中英翻译。测试显示,其英文支持较好,中文则需改进。llama3.2 1B参数量小,适合移动端和资源受限环境,可在阿里云2vCPU和4G ECS上运行。
179 1
|
3月前
|
前端开发 算法 测试技术
前端大模型应用笔记(五):大模型基础能力大比拼-计数篇-通义千文 vs 文心一言 vs 智谱 vs 讯飞vsGPT
本文对比测试了通义千文、文心一言、智谱和讯飞等多个国产大模型在处理基础计数问题上的表现,特别是通过链式推理(COT)提示的效果。结果显示,GPTo1-mini、文心一言3.5和讯飞4.0Ultra在首轮测试中表现优秀,而其他模型在COT提示后也能显著提升正确率,唯有讯飞4.0-Lite表现不佳。测试强调了COT在提升模型逻辑推理能力中的重要性,并指出免费版本中智谱GLM较为可靠。
102 0
前端大模型应用笔记(五):大模型基础能力大比拼-计数篇-通义千文 vs 文心一言 vs 智谱 vs 讯飞vsGPT
|
4月前
|
SpringCloudAlibaba JavaScript 前端开发
谷粒商城笔记+踩坑(2)——分布式组件、前端基础,nacos+feign+gateway+ES6+vue脚手架
分布式组件、nacos注册配置中心、openfegin远程调用、网关gateway、ES6脚本语言规范、vue、elementUI
谷粒商城笔记+踩坑(2)——分布式组件、前端基础,nacos+feign+gateway+ES6+vue脚手架
|
5月前
|
存储 前端开发 JavaScript
前端语言串讲 | 青训营笔记
前端语言串讲 | 青训营笔记
58 0
|
7月前
|
JSON 前端开发 JavaScript
前端Ajax、Axios和Fetch的用法和区别笔记
前端Ajax、Axios和Fetch的用法和区别笔记
143 2

热门文章

最新文章