UNPKG

30.3 kBJavaScriptView Raw
1/*!
2 2D Transformation Matrix v2.7.5
3 (c) Epistemex.com 2014-2018
4 License: MIT
5*/
6
7/**
8 * 2D transformation matrix object initialized with identity matrix.
9 *
10 * The matrix can synchronize a canvas 2D context by supplying the context
11 * as an argument, or later apply current absolute transform to an
12 * existing context.
13 *
14 * To synchronize a DOM element you can use [`toCSS()`]{@link Matrix#toCSS} or [`toCSS3D()`]{@link Matrix#toCSS3D}.
15 * together with for example the `style.transform` property.
16 *
17 * @param {CanvasRenderingContext2D} [context] - Optional context to sync with Matrix
18 * @param {HTMLElement} [element=null] - DOM Element to synchronize
19 * @prop {number} a - scale x
20 * @prop {number} b - shear y
21 * @prop {number} c - shear x
22 * @prop {number} d - scale y
23 * @prop {number} e - translate x
24 * @prop {number} f - translate y
25 * @prop {CanvasRenderingContext2D} [context] - set or get current synchronized 2D context
26 * @prop {HTMLElement} [element] - get current synchronized DOM element
27 * @prop {boolean} [useCSS3D=false] - is a DOM element is defined for sync., choose whether to use 2D (false) or 3D (true) matrix to sync it.
28 * @constructor
29 * @license MIT license
30 * @copyright Epistemex.com 2014-2018
31 */
32function Matrix(context, element) {
33 var me = this,
34 _el;
35 me._t = me.transform;
36
37 me.a = me.d = 1;
38 me.b = me.c = me.e = me.f = 0;
39
40 // sync context
41 if (context) (me.context = context).setTransform(1, 0, 0, 1, 0, 0);
42
43 // sync DOM element
44 Object.defineProperty(me, 'element', {
45 get: function () {
46 return _el;
47 },
48 set: function (el) {
49 if (!_el) {
50 me._px = me._getPX();
51 me.useCSS3D = false;
52 }
53 _el = el;
54 (me._st = _el.style)[me._px] = me.toCSS();
55 },
56 });
57
58 if (element) me.element = element;
59}
60
61/**
62 * Returns a new matrix that transforms a triangle `t1` into another triangle
63 * `t2`, or throws an exception if it is impossible.
64 *
65 * Note: the method can take both arrays as well as literal objects.
66 * Just make sure that both arguments (`t1`, `t2`) are of the same type.
67 *
68 * @param {{px: number, py: number, qx: number, qy: number, rx: number, ry: number}|Array} t1 - Object or array containing the three points for the triangle.
69 * For object use obj.px, obj.py, obj.qx, obj.qy, obj.rx and obj.ry. For arrays provide the points in the order [px, py, qx, qy, rx, ry], or as point array [{x:,y:}, {x:,y:}, {x:,y:}]
70 * @param {{px: number, py: number, qx: number, qy: number, rx: number, ry: number}|Array} t2 - See description for t1.
71 * @param {CanvasRenderingContext2D} [context] - optional canvas 2D context to use for the matrix
72 * @returns {Matrix}
73 * @throws Exception is matrix becomes not invertible
74 * @static
75 */
76Matrix.fromTriangles = function (t1, t2, context) {
77 var m1 = new Matrix(),
78 m2 = new Matrix(context),
79 r1,
80 r2,
81 rx1,
82 ry1,
83 rx2,
84 ry2;
85
86 if (Array.isArray(t1)) {
87 if (typeof t1[0] === 'number') {
88 rx1 = t1[4];
89 ry1 = t1[5];
90 rx2 = t2[4];
91 ry2 = t2[5];
92 r1 = [t1[0] - rx1, t1[1] - ry1, t1[2] - rx1, t1[3] - ry1, rx1, ry1];
93 r2 = [t2[0] - rx2, t2[1] - ry2, t2[2] - rx2, t2[3] - ry2, rx2, ry2];
94 } else {
95 rx1 = t1[2].x;
96 ry1 = t1[2].y;
97 rx2 = t2[2].x;
98 ry2 = t2[2].y;
99 r1 = [
100 t1[0].x - rx1,
101 t1[0].y - ry1,
102 t1[1].x - rx1,
103 t1[1].y - ry1,
104 rx1,
105 ry1,
106 ];
107 r2 = [
108 t2[0].x - rx2,
109 t2[0].y - ry2,
110 t2[1].x - rx2,
111 t2[1].y - ry2,
112 rx2,
113 ry2,
114 ];
115 }
116 } else {
117 r1 = [
118 t1.px - t1.rx,
119 t1.py - t1.ry,
120 t1.qx - t1.rx,
121 t1.qy - t1.ry,
122 t1.rx,
123 t1.ry,
124 ];
125 r2 = [
126 t2.px - t2.rx,
127 t2.py - t2.ry,
128 t2.qx - t2.rx,
129 t2.qy - t2.ry,
130 t2.rx,
131 t2.ry,
132 ];
133 }
134
135 m1.setTransform.apply(m1, r1);
136 m2.setTransform.apply(m2, r2);
137
138 return m2.multiply(m1.inverse());
139};
140
141/**
142 * Create a matrix from a transform list from an SVG shape. The list
143 * can be for example baseVal (i.e. `shape.transform.baseVal`).
144 *
145 * The resulting matrix has all transformations from that list applied
146 * in the same order as the list.
147 *
148 * @param {SVGTransformList} tList - transform list from an SVG shape.
149 * @param {CanvasRenderingContext2D} [context] - optional canvas 2D context to use for the matrix
150 * @param {HTMLElement} [dom] - optional DOM element to use for the matrix
151 * @returns {Matrix}
152 * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/SVGTransformList|MDN / SVGTransformList}
153 */
154Matrix.fromSVGTransformList = function (tList, context, dom) {
155 var m = new Matrix(context, dom),
156 i = 0;
157
158 while (i < tList.length) m.multiply(tList[i++].matrix);
159
160 return m;
161};
162
163/**
164 * Create and transform a new matrix based on given matrix values, or
165 * provide SVGMatrix or a (2D) DOMMatrix, WebKitCSSMatrix or another
166 * instance of a generic Matrix.
167 *
168 * @example
169 *
170 * var m = Matrix.from(1, 0.2, 0, 2, 120, 97);
171 * var m = Matrix.from(domMatrix, ctx);
172 * var m = Matrix.from(svgMatrix);
173 * var m = Matrix.from(cssMatrix);
174 * var m = Matrix.from(matrix);
175 * var m = Matrix.from(vector [,pre-x] [,pre-y] [,doScale]);
176 *
177 * @param {*} a - number representing a in [a-f], or a Matrix object containing properties a-f. Vector is given as an object with properties x and y.
178 * @param {*} [b] - b property if a is not a matrix object, or optional canvas 2D context.
179 * If vector is input this will be pre-translate for x.
180 * @param {number} [c] - If vector is input this will be pre-translate for y.
181 * @param {number} [d] - If vector is input, set this to true to use scale and translate of 1,
182 * false to use hypotenuse as translate distance instead and no scale.
183 * @param {number} [e]
184 * @param {number} [f]
185 * @param {CanvasRenderingContext2D} [context] - optional canvas context to synchronize
186 * @param {HTMLElement} [dom] - optional DOM element to use for the matrix
187 * @returns {Matrix}
188 * @static
189 */
190Matrix.from = function (a, b, c, d, e, f, context, dom) {
191 var m = new Matrix(context, dom),
192 scale,
193 dist,
194 q;
195
196 if (typeof a === 'number') m.setTransform(a, b, c, d, e, f);
197 else if (typeof a.x === 'number') {
198 // vector
199
200 q = Math.sqrt(a.x * a.x + a.y * a.y);
201 scale = dist = 1;
202
203 if (d) scale = q;
204 else dist = q;
205
206 m.translate(b || 0, c || 0)
207 .rotateFromVector(a)
208 .scaleU(scale)
209 .translate(dist, 0);
210 } else {
211 if (typeof a.is2D === 'boolean' && !a.is2D)
212 throw 'Cannot use 3D DOMMatrix.';
213 if (b) m.context = b;
214 if (c) m.element = c;
215 m.multiply(a);
216 }
217
218 return m;
219};
220
221Matrix.prototype = {
222 _getPX: function () {
223 var lst = ['t', 'oT', 'msT', 'mozT', 'webkitT', 'khtmlT'],
224 i = 0,
225 p,
226 style = document.createElement('div').style;
227
228 while ((p = lst[i++]))
229 if (typeof style[p + 'ransform'] !== 'undefined') return p + 'ransform';
230 },
231
232 /**
233 * Concatenates transforms of this matrix onto the given child matrix and
234 * returns a new matrix. This instance is used on left side.
235 *
236 * @param {Matrix|SVGMatrix} cm - child matrix to apply concatenation to
237 * @returns {Matrix} - new Matrix instance
238 */
239 concat: function (cm) {
240 return this.clone().multiply(cm);
241 },
242
243 /**
244 * Flips the horizontal values.
245 * @returns {Matrix}
246 */
247 flipX: function () {
248 return this._t(-1, 0, 0, 1, 0, 0);
249 },
250
251 /**
252 * Flips the vertical values.
253 * @returns {Matrix}
254 */
255 flipY: function () {
256 return this._t(1, 0, 0, -1, 0, 0);
257 },
258
259 /**
260 * Reflects incoming (velocity) vector on the normal which will be the
261 * current transformed x axis. Call when a trigger condition is met.
262 *
263 * @param {number} x - vector end point for x (start = 0)
264 * @param {number} y - vector end point for y (start = 0)
265 * @returns {{x: number, y: number}}
266 */
267 reflectVector: function (x, y) {
268 var v = this.applyToPoint(0, 1),
269 d = (v.x * x + v.y * y) * 2;
270
271 x -= d * v.x;
272 y -= d * v.y;
273
274 return { x: x, y: y };
275 },
276
277 /**
278 * Short-hand to reset current matrix to an identity matrix.
279 * @returns {Matrix}
280 */
281 reset: function () {
282 return this.setTransform(1, 0, 0, 1, 0, 0);
283 },
284
285 /**
286 * Rotates current matrix by angle (accumulative).
287 * @param {number} angle - angle in radians
288 * @returns {Matrix}
289 */
290 rotate: function (angle) {
291 var cos = Math.cos(angle),
292 sin = Math.sin(angle);
293 return this._t(cos, sin, -sin, cos, 0, 0);
294 },
295
296 /**
297 * Converts a vector given as `x` and `y` to angle, and
298 * rotates (accumulative). x can instead contain an object with
299 * properties x and y and if so, y parameter will be ignored.
300 * @param {number|*} x
301 * @param {number} [y]
302 * @returns {Matrix}
303 */
304 rotateFromVector: function (x, y) {
305 return this.rotate(
306 typeof x === 'number' ? Math.atan2(y, x) : Math.atan2(x.y, x.x)
307 );
308 },
309
310 /**
311 * Helper method to make a rotation based on an angle in degrees.
312 * @param {number} angle - angle in degrees
313 * @returns {Matrix}
314 */
315 rotateDeg: function (angle) {
316 return this.rotate((angle * Math.PI) / 180);
317 },
318
319 /**
320 * Scales current matrix uniformly and accumulative.
321 * @param {number} f - scale factor for both x and y (1 does nothing)
322 * @returns {Matrix}
323 */
324 scaleU: function (f) {
325 return this._t(f, 0, 0, f, 0, 0);
326 },
327
328 /**
329 * Scales current matrix accumulative.
330 * @param {number} sx - scale factor x (1 does nothing)
331 * @param {number} sy - scale factor y (1 does nothing)
332 * @returns {Matrix}
333 */
334 scale: function (sx, sy) {
335 return this._t(sx, 0, 0, sy, 0, 0);
336 },
337
338 /**
339 * Scales current matrix on x axis accumulative.
340 * @param {number} sx - scale factor x (1 does nothing)
341 * @returns {Matrix}
342 */
343 scaleX: function (sx) {
344 return this._t(sx, 0, 0, 1, 0, 0);
345 },
346
347 /**
348 * Scales current matrix on y axis accumulative.
349 * @param {number} sy - scale factor y (1 does nothing)
350 * @returns {Matrix}
351 */
352 scaleY: function (sy) {
353 return this._t(1, 0, 0, sy, 0, 0);
354 },
355
356 /**
357 * Converts a vector given as `x` and `y` to normalized scale.
358 * @param x
359 * @param y
360 * @returns {Matrix}
361 */
362 scaleFromVector: function (x, y) {
363 return this.scaleU(Math.sqrt(x * x + y * y));
364 },
365
366 /**
367 * Apply shear to the current matrix accumulative.
368 * @param {number} sx - amount of shear for x
369 * @param {number} sy - amount of shear for y
370 * @returns {Matrix}
371 */
372 shear: function (sx, sy) {
373 return this._t(1, sy, sx, 1, 0, 0);
374 },
375
376 /**
377 * Apply shear for x to the current matrix accumulative.
378 * @param {number} sx - amount of shear for x
379 * @returns {Matrix}
380 */
381 shearX: function (sx) {
382 return this._t(1, 0, sx, 1, 0, 0);
383 },
384
385 /**
386 * Apply shear for y to the current matrix accumulative.
387 * @param {number} sy - amount of shear for y
388 * @returns {Matrix}
389 */
390 shearY: function (sy) {
391 return this._t(1, sy, 0, 1, 0, 0);
392 },
393
394 /**
395 * Apply skew to the current matrix accumulative. Angles in radians.
396 * Also see [`skewDeg()`]{@link Matrix#skewDeg}.
397 * @param {number} ax - angle of skew for x
398 * @param {number} ay - angle of skew for y
399 * @returns {Matrix}
400 */
401 skew: function (ax, ay) {
402 return this.shear(Math.tan(ax), Math.tan(ay));
403 },
404
405 /**
406 * Apply skew to the current matrix accumulative. Angles in degrees.
407 * Also see [`skew()`]{@link Matrix#skew}.
408 * @param {number} ax - angle of skew for x
409 * @param {number} ay - angle of skew for y
410 * @returns {Matrix}
411 */
412 skewDeg: function (ax, ay) {
413 return this.shear(
414 Math.tan((ax / 180) * Math.PI),
415 Math.tan((ay / 180) * Math.PI)
416 );
417 },
418
419 /**
420 * Apply skew for x to the current matrix accumulative. Angles in radians.
421 * Also see [`skewDeg()`]{@link Matrix#skewDeg}.
422 * @param {number} ax - angle of skew for x
423 * @returns {Matrix}
424 */
425 skewX: function (ax) {
426 return this.shearX(Math.tan(ax));
427 },
428
429 /**
430 * Apply skew for y to the current matrix accumulative. Angles in radians.
431 * Also see [`skewDeg()`]{@link Matrix#skewDeg}.
432 * @param {number} ay - angle of skew for y
433 * @returns {Matrix}
434 */
435 skewY: function (ay) {
436 return this.shearY(Math.tan(ay));
437 },
438
439 /**
440 * Set current matrix to new absolute matrix.
441 * @param {number} a - scale x
442 * @param {number} b - shear y
443 * @param {number} c - shear x
444 * @param {number} d - scale y
445 * @param {number} e - translate x
446 * @param {number} f - translate y
447 * @returns {Matrix}
448 */
449 setTransform: function (a, b, c, d, e, f) {
450 var me = this;
451 me.a = a;
452 me.b = b;
453 me.c = c;
454 me.d = d;
455 me.e = e;
456 me.f = f;
457 return me._x();
458 },
459
460 /**
461 * Translate current matrix accumulative.
462 * @param {number} tx - translation for x
463 * @param {number} ty - translation for y
464 * @returns {Matrix}
465 */
466 translate: function (tx, ty) {
467 return this._t(1, 0, 0, 1, tx, ty);
468 },
469
470 /**
471 * Translate current matrix on x axis accumulative.
472 * @param {number} tx - translation for x
473 * @returns {Matrix}
474 */
475 translateX: function (tx) {
476 return this._t(1, 0, 0, 1, tx, 0);
477 },
478
479 /**
480 * Translate current matrix on y axis accumulative.
481 * @param {number} ty - translation for y
482 * @returns {Matrix}
483 */
484 translateY: function (ty) {
485 return this._t(1, 0, 0, 1, 0, ty);
486 },
487
488 /**
489 * Multiplies current matrix with new matrix values. Also see [`multiply()`]{@link Matrix#multiply}.
490 *
491 * @param {number} a2 - scale x
492 * @param {number} b2 - skew y
493 * @param {number} c2 - skew x
494 * @param {number} d2 - scale y
495 * @param {number} e2 - translate x
496 * @param {number} f2 - translate y
497 * @returns {Matrix}
498 */
499 transform: function (a2, b2, c2, d2, e2, f2) {
500 var me = this,
501 a1 = me.a,
502 b1 = me.b,
503 c1 = me.c,
504 d1 = me.d,
505 e1 = me.e,
506 f1 = me.f;
507
508 /* matrix column order is:
509 * a c e
510 * b d f
511 * 0 0 1
512 */
513 me.a = a1 * a2 + c1 * b2;
514 me.b = b1 * a2 + d1 * b2;
515 me.c = a1 * c2 + c1 * d2;
516 me.d = b1 * c2 + d1 * d2;
517 me.e = a1 * e2 + c1 * f2 + e1;
518 me.f = b1 * e2 + d1 * f2 + f1;
519
520 return me._x();
521 },
522
523 /**
524 * Multiplies current matrix with source matrix.
525 * @param {Matrix|DOMMatrix|SVGMatrix} m - source matrix to multiply with.
526 * @returns {Matrix}
527 */
528 multiply: function (m) {
529 return this._t(m.a, m.b, m.c, m.d, m.e, m.f);
530 },
531
532 /**
533 * Divide this matrix on input matrix which must be invertible.
534 * @param {Matrix} m - matrix to divide on (divisor)
535 * @throws Exception if input matrix is not invertible
536 * @returns {Matrix}
537 */
538 divide: function (m) {
539 return this.multiply(m.inverse());
540 },
541
542 /**
543 * Divide current matrix on scalar value != 0.
544 * @param {number} d - divisor
545 * @throws Exception if divisor is zero
546 * @returns {Matrix}
547 */
548 divideScalar: function (d) {
549 var me = this;
550
551 if (!d) throw 'Division on zero';
552
553 me.a /= d;
554 me.b /= d;
555 me.c /= d;
556 me.d /= d;
557 me.e /= d;
558 me.f /= d;
559
560 return me._x();
561 },
562
563 /**
564 * Get an inverse matrix of current matrix. The method returns a new
565 * matrix with values you need to use to get to an identity matrix.
566 * Context from parent matrix is not applied to the returned matrix.
567 *
568 * @param {boolean} [cloneContext=false] - clone current context to resulting matrix
569 * @param {boolean} [cloneDOM=false] - clone current DOM element to resulting matrix
570 * @throws Exception is input matrix is not invertible
571 * @returns {Matrix} - new Matrix instance
572 */
573 inverse: function (cloneContext, cloneDOM) {
574 var me = this,
575 m = new Matrix(
576 cloneContext ? me.context : null,
577 cloneDOM ? me.element : null
578 ),
579 dt = me.determinant();
580
581 if (!dt) throw 'Matrix not invertible.';
582
583 m.a = me.d / dt;
584 m.b = -me.b / dt;
585 m.c = -me.c / dt;
586 m.d = me.a / dt;
587 m.e = (me.c * me.f - me.d * me.e) / dt;
588 m.f = -(me.a * me.f - me.b * me.e) / dt;
589
590 return m;
591 },
592
593 /**
594 * Interpolate this matrix with another and produce a new matrix.
595 * `t` is a value in the range [0.0, 1.0] where 0 is this instance and
596 * 1 is equal to the second matrix. The `t` value is not clamped.
597 *
598 * Context from parent matrix is not applied to the returned matrix.
599 *
600 * Note: this interpolation is naive. For animation containing rotation,
601 * shear or skew use the [`interpolateAnim()`]{@link Matrix#interpolateAnim} method instead
602 * to avoid unintended flipping.
603 *
604 * @param {Matrix|SVGMatrix} m2 - the matrix to interpolate with.
605 * @param {number} t - interpolation [0.0, 1.0]
606 * @param {CanvasRenderingContext2D} [context] - optional context to affect
607 * @param {HTMLElement} [dom] - optional DOM element to use for the matrix
608 * @returns {Matrix} - new Matrix instance with the interpolated result
609 */
610 interpolate: function (m2, t, context, dom) {
611 var me = this,
612 m = new Matrix(context, dom);
613
614 m.a = me.a + (m2.a - me.a) * t;
615 m.b = me.b + (m2.b - me.b) * t;
616 m.c = me.c + (m2.c - me.c) * t;
617 m.d = me.d + (m2.d - me.d) * t;
618 m.e = me.e + (m2.e - me.e) * t;
619 m.f = me.f + (m2.f - me.f) * t;
620
621 return m._x();
622 },
623
624 /**
625 * Interpolate this matrix with another and produce a new matrix.
626 * `t` is a value in the range [0.0, 1.0] where 0 is this instance and
627 * 1 is equal to the second matrix. The `t` value is not constrained.
628 *
629 * Context from parent matrix is not applied to the returned matrix.
630 *
631 * To obtain easing `t` can be preprocessed using easing-functions
632 * before being passed to this method.
633 *
634 * Note: this interpolation method uses decomposition which makes
635 * it suitable for animations (in particular where rotation takes
636 * places).
637 *
638 * @param {Matrix} m2 - the matrix to interpolate with.
639 * @param {number} t - interpolation [0.0, 1.0]
640 * @param {CanvasRenderingContext2D} [context] - optional context to affect
641 * @param {HTMLElement} [dom] - optional DOM element to use for the matrix
642 * @returns {Matrix} - new Matrix instance with the interpolated result
643 */
644 interpolateAnim: function (m2, t, context, dom) {
645 var m = new Matrix(context, dom),
646 d1 = this.decompose(),
647 d2 = m2.decompose(),
648 t1 = d1.translate,
649 t2 = d2.translate,
650 s1 = d1.scale;
651
652 // QR order (t-r-s-sk)
653 m.translate(t1.x + (t2.x - t1.x) * t, t1.y + (t2.y - t1.y) * t);
654 m.rotate(d1.rotation + (d2.rotation - d1.rotation) * t);
655 m.scale(s1.x + (d2.scale.x - s1.x) * t, s1.y + (d2.scale.y - s1.y) * t);
656 //todo test skew scenarios
657
658 return m._x();
659 },
660
661 /**
662 * Decompose the current matrix into simple transforms using either
663 * QR (default) or LU decomposition.
664 *
665 * @param {boolean} [useLU=false] - set to true to use LU rather than QR decomposition
666 * @returns {*} - an object containing current decomposed values (translate, rotation, scale, skew)
667 * @see {@link https://en.wikipedia.org/wiki/QR_decomposition|More on QR decomposition}
668 * @see {@link https://en.wikipedia.org/wiki/LU_decomposition|More on LU decomposition}
669 */
670 decompose: function (useLU) {
671 var me = this,
672 a = me.a,
673 b = me.b,
674 c = me.c,
675 d = me.d,
676 acos = Math.acos,
677 atan = Math.atan,
678 sqrt = Math.sqrt,
679 pi = Math.PI,
680 translate = { x: me.e, y: me.f },
681 rotation = 0,
682 scale = { x: 1, y: 1 },
683 skew = { x: 0, y: 0 },
684 determ = a * d - b * c, // determinant(), skip DRY here...
685 r,
686 s;
687
688 if (useLU) {
689 if (a) {
690 skew = { x: atan(c / a), y: atan(b / a) };
691 scale = { x: a, y: determ / a };
692 } else if (b) {
693 rotation = pi * 0.5;
694 scale = { x: b, y: determ / b };
695 skew.x = atan(d / b);
696 } else {
697 // a = b = 0
698 scale = { x: c, y: d };
699 skew.x = pi * 0.25;
700 }
701 } else {
702 // Apply the QR-like decomposition.
703 if (a || b) {
704 r = sqrt(a * a + b * b);
705 rotation = b > 0 ? acos(a / r) : -acos(a / r);
706 scale = { x: r, y: determ / r };
707 skew.x = atan((a * c + b * d) / (r * r));
708 } else if (c || d) {
709 s = sqrt(c * c + d * d);
710 rotation = pi * 0.5 - (d > 0 ? acos(-c / s) : -acos(c / s));
711 scale = { x: determ / s, y: s };
712 skew.y = atan((a * c + b * d) / (s * s));
713 } else {
714 // a = b = c = d = 0
715 scale = { x: 0, y: 0 };
716 }
717 }
718
719 return {
720 translate: translate,
721 rotation: rotation,
722 scale: scale,
723 skew: skew,
724 };
725 },
726
727 /**
728 * Returns the determinant of the current matrix.
729 * @returns {number}
730 */
731 determinant: function () {
732 return this.a * this.d - this.b * this.c;
733 },
734
735 /**
736 * Apply current matrix to `x` and `y` of a point.
737 * Returns a point object.
738 *
739 * @param {number} x - value for x
740 * @param {number} y - value for y
741 * @returns {{x: number, y: number}} A new transformed point object
742 */
743 applyToPoint: function (x, y) {
744 var me = this;
745 return {
746 x: x * me.a + y * me.c + me.e,
747 y: x * me.b + y * me.d + me.f,
748 };
749 },
750
751 /**
752 * Apply current matrix to array with point objects or point pairs.
753 * Returns a new array with points in the same format as the input array.
754 *
755 * A point object is an object literal:
756 *
757 * {x: x, y: y}
758 *
759 * so an array would contain either:
760 *
761 * [{x: x1, y: y1}, {x: x2, y: y2}, ... {x: xn, y: yn}]
762 *
763 * or
764 *
765 * [x1, y1, x2, y2, ... xn, yn]
766 *
767 * @param {Array} points - array with point objects or pairs
768 * @returns {Array} A new array with transformed points
769 */
770 applyToArray: function (points) {
771 var i = 0,
772 p,
773 l,
774 mxPoints = [];
775
776 if (typeof points[0] === 'number') {
777 l = points.length;
778
779 while (i < l) {
780 p = this.applyToPoint(points[i++], points[i++]);
781 mxPoints.push(p.x, p.y);
782 }
783 } else {
784 while ((p = points[i++])) {
785 mxPoints.push(this.applyToPoint(p.x, p.y));
786 }
787 }
788
789 return mxPoints;
790 },
791
792 /**
793 * Apply current matrix to a typed array with point pairs. Although
794 * the input array may be an ordinary array, this method is intended
795 * for more performant use where typed arrays are used. The returned
796 * array is regardless always returned as a `Float32Array`.
797 *
798 * @param {*} points - (typed) array with point pairs [x1, y1, ..., xn, yn]
799 * @param {boolean} [use64=false] - use Float64Array instead of Float32Array
800 * @returns {*} A new typed array with transformed points
801 */
802 applyToTypedArray: function (points, use64) {
803 var i = 0,
804 p,
805 l = points.length,
806 mxPoints = use64 ? new Float64Array(l) : new Float32Array(l);
807
808 while (i < l) {
809 p = this.applyToPoint(points[i], points[i + 1]);
810 mxPoints[i++] = p.x;
811 mxPoints[i++] = p.y;
812 }
813
814 return mxPoints;
815 },
816
817 /**
818 * Apply to any canvas 2D context object. This does not affect the
819 * context that optionally was referenced in constructor unless it is
820 * the same context.
821 *
822 * @param {CanvasRenderingContext2D} context - target context
823 * @returns {Matrix}
824 */
825 applyToContext: function (context) {
826 var me = this;
827 context.setTransform(me.a, me.b, me.c, me.d, me.e, me.f);
828 return me;
829 },
830
831 /**
832 * Apply to any DOM element. This does not affect the DOM element
833 * that optionally was referenced in constructor unless it is
834 * the same element.
835 *
836 * The method will auto-detect the correct browser prefix if any.
837 *
838 * @param {HTMLElement} element - target DOM element
839 * @param {boolean} [use3D=false] - use 3D transformation matrix instead of 2D
840 * @returns {Matrix}
841 */
842 applyToElement: function (element, use3D) {
843 var me = this;
844 if (!me._px) me._px = me._getPX();
845 element.style[me._px] = use3D ? me.toCSS3D() : me.toCSS();
846 return me;
847 },
848
849 /**
850 * Instead of creating a new instance of a Matrix, DOMMatrix or SVGMatrix
851 * the current settings of this instance can be applied to an external
852 * object of a different (or same) type. You can also pass in an
853 * empty literal object.
854 *
855 * Note that the properties a-f will be set regardless of if they
856 * already exist or not.
857 *
858 * @param {*} obj - target object.
859 * @returns {Matrix}
860 */
861 applyToObject: function (obj) {
862 var me = this;
863 obj.a = me.a;
864 obj.b = me.b;
865 obj.c = me.c;
866 obj.d = me.d;
867 obj.e = me.e;
868 obj.f = me.f;
869 return me;
870 },
871
872 /**
873 * Returns true if matrix is an identity matrix (no transforms applied).
874 * @returns {boolean}
875 */
876 isIdentity: function () {
877 var me = this;
878 return me.a === 1 && !me.b && !me.c && me.d === 1 && !me.e && !me.f;
879 },
880
881 /**
882 * Returns true if matrix is invertible
883 * @returns {boolean}
884 */
885 isInvertible: function () {
886 return !this._q(this.determinant(), 0);
887 },
888
889 /**
890 * The method is intended for situations where scale is accumulated
891 * via multiplications, to detect situations where scale becomes
892 * "trapped" with a value of zero. And in which case scale must be
893 * set explicitly to a non-zero value.
894 *
895 * @returns {boolean}
896 */
897 isValid: function () {
898 return !(this.a * this.d);
899 },
900
901 /**
902 * Compares current matrix with another matrix. Returns true if equal
903 * (within epsilon tolerance).
904 * @param {Matrix|SVGMatrix} m - matrix to compare this matrix with
905 * @returns {boolean}
906 */
907 isEqual: function (m) {
908 var me = this,
909 q = me._q;
910
911 return (
912 q(me.a, m.a) &&
913 q(me.b, m.b) &&
914 q(me.c, m.c) &&
915 q(me.d, m.d) &&
916 q(me.e, m.e) &&
917 q(me.f, m.f)
918 );
919 },
920
921 /**
922 * Clones current instance and returning a new matrix.
923 * @param {boolean} [noContext=false] don't clone context reference if true
924 * @returns {Matrix} - a new Matrix instance with identical transformations as this instance
925 */
926 clone: function (noContext) {
927 return new Matrix(noContext ? null : this.context).multiply(this);
928 },
929
930 /**
931 * Returns an array with current matrix values.
932 * @returns {Array}
933 */
934 toArray: function () {
935 var me = this;
936 return [me.a, me.b, me.c, me.d, me.e, me.f];
937 },
938
939 /**
940 * Returns a binary 32-bit floating point typed array.
941 * @returns {*}
942 */
943 toTypedArray: function () {
944 var me = this;
945 return new Float32Array([me.a, me.b, me.c, me.d, me.e, me.f]);
946 },
947
948 /**
949 * Generates a string that can be used with CSS `transform`.
950 * @example
951 * element.style.transform = m.toCSS();
952 * @returns {string}
953 */
954 toCSS: function () {
955 return 'matrix(' + this.toArray() + ')';
956 },
957
958 /**
959 * Generates a `matrix3d()` string that can be used with CSS `transform`.
960 * Although the matrix is for 2D use you may see performance benefits
961 * on some devices using a 3D CSS transform instead of a 2D.
962 * @example
963 * element.style.transform = m.toCSS3D();
964 * @returns {string}
965 */
966 toCSS3D: function () {
967 var me = this,
968 n2 = ',0,0,';
969 return (
970 'matrix3d(' +
971 me.a +
972 ',' +
973 me.b +
974 n2 +
975 me.c +
976 ',' +
977 me.d +
978 n2 +
979 n2 +
980 ',1,0,' +
981 me.e +
982 ',' +
983 me.f +
984 ',0,1)'
985 );
986 },
987
988 /**
989 * Returns a JSON compatible string of current matrix.
990 * @returns {string}
991 */
992 toJSON: function () {
993 var me = this;
994 return (
995 '{"a":' +
996 me.a +
997 ',"b":' +
998 me.b +
999 ',"c":' +
1000 me.c +
1001 ',"d":' +
1002 me.d +
1003 ',"e":' +
1004 me.e +
1005 ',"f":' +
1006 me.f +
1007 '}'
1008 );
1009 },
1010
1011 /**
1012 * Returns a string with current matrix as comma-separated list.
1013 * @param {number} [fixLen=4] - truncate decimal values to number of digits
1014 * @returns {string}
1015 */
1016 toString: function (fixLen) {
1017 var me = this;
1018 fixLen = fixLen || 4;
1019 return (
1020 'a=' +
1021 me.a.toFixed(fixLen) +
1022 ' b=' +
1023 me.b.toFixed(fixLen) +
1024 ' c=' +
1025 me.c.toFixed(fixLen) +
1026 ' d=' +
1027 me.d.toFixed(fixLen) +
1028 ' e=' +
1029 me.e.toFixed(fixLen) +
1030 ' f=' +
1031 me.f.toFixed(fixLen)
1032 );
1033 },
1034
1035 /**
1036 * Returns a string with current matrix as comma-separated values
1037 * string with line-end (CR+LF).
1038 * @returns {string}
1039 */
1040 toCSV: function () {
1041 return this.toArray().join() + '\r\n';
1042 },
1043
1044 /**
1045 * Convert current matrix into a `DOMMatrix`. If `DOMMatrix` is not
1046 * supported, a `null` is returned.
1047 *
1048 * @returns {DOMMatrix}
1049 * @see {@link https://drafts.fxtf.org/geometry/#dommatrix|MDN / SVGMatrix}
1050 */
1051 toDOMMatrix: function () {
1052 var m = null;
1053 if ('DOMMatrix' in window) {
1054 m = new DOMMatrix();
1055 m.a = this.a;
1056 m.b = this.b;
1057 m.c = this.c;
1058 m.d = this.d;
1059 m.e = this.e;
1060 m.f = this.f;
1061 }
1062 return m;
1063 },
1064
1065 /**
1066 * Convert current matrix into a `SVGMatrix`. If `SVGMatrix` is not
1067 * supported, a `null` is returned.
1068 *
1069 * @returns {SVGMatrix}
1070 * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/SVGMatrix|MDN / SVGMatrix}
1071 */
1072 toSVGMatrix: function () {
1073 var me = this,
1074 svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'),
1075 svgMatrix = null;
1076
1077 if (svg) {
1078 svgMatrix = svg.createSVGMatrix();
1079 svgMatrix.a = me.a;
1080 svgMatrix.b = me.b;
1081 svgMatrix.c = me.c;
1082 svgMatrix.d = me.d;
1083 svgMatrix.e = me.e;
1084 svgMatrix.f = me.f;
1085 }
1086
1087 return svgMatrix;
1088 },
1089
1090 /**
1091 * Compares floating point values with some tolerance (epsilon)
1092 * @param {number} f1 - float 1
1093 * @param {number} f2 - float 2
1094 * @returns {boolean}
1095 * @private
1096 */
1097 _q: function (f1, f2) {
1098 return Math.abs(f1 - f2) < 1e-14;
1099 },
1100
1101 /**
1102 * Apply current absolute matrix to context if defined, to sync it.
1103 * Apply current absolute matrix to element if defined, to sync it.
1104 * @returns {Matrix}
1105 * @private
1106 */
1107 _x: function () {
1108 var me = this;
1109
1110 if (me.context) me.context.setTransform(me.a, me.b, me.c, me.d, me.e, me.f);
1111
1112 if (me._st) me._st[me._px] = me.useCSS3D ? me.toCSS3D() : me.toCSS(); // can be optimized pre-storing func ref.
1113
1114 return me;
1115 },
1116};
1117
1118// Node support
1119if (typeof exports !== 'undefined') exports.Matrix = Matrix;