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 绘制元素过程中坐标矩阵的变换说完了,具体的代码细节自行查阅源码吧!
最后
了解矩阵变换的概念,旨在揭开相关开源代码实现的背后细节。