new changes

This commit is contained in:
Niranjan
2026-04-07 05:05:28 +05:30
parent 7c070224bd
commit a18bba15f2
29975 changed files with 3247495 additions and 2761 deletions

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2017 https://github.com/chrvadala
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,161 @@
# transformation-matrix
Javascript isomorphic 2D affine transformations written in ES6 syntax. Manipulate transformation matrices with this totally tested library!
[![chrvadala](https://img.shields.io/badge/website-chrvadala-orange.svg)](https://chrvadala.github.io)
[![Donate](https://img.shields.io/badge/donate-Paypal-lightgrey.svg)](https://www.paypal.com/paypalme/chrvadala/15)
[![Test](https://github.com/chrvadala/transformation-matrix/actions/workflows/test.yml/badge.svg?branch=main)](https://github.com/chrvadala/transformation-matrix/actions/workflows/test.yml)
[![Coverage Status](https://coveralls.io/repos/github/chrvadala/transformation-matrix/badge.svg?branch=master)](https://coveralls.io/github/chrvadala/transformation-matrix?branch=master)
[![npm](https://img.shields.io/npm/v/transformation-matrix.svg?maxAge=2592000?style=plastic)](https://www.npmjs.com/package/transformation-matrix)
[![Downloads](https://img.shields.io/npm/dm/transformation-matrix.svg)](https://www.npmjs.com/package/transformation-matrix)
# Features
Transformations, i.e. *linear invertible automorphisms*, are used to map a picture into another one with different size, position and orientation. Given a basis, transformations are represented by means of squared invertible matrices, called **transformation matrices**.
A geometric transformation is defined as a one-to-one mapping of a point space to itself, which preservers some geometric relations of figures. - [Geometric Programming for Computer Aided Design](https://books.google.it/books?vid=ISBN9780471899426)
This library allows us to:
- generate transformation matrices for the following operations: **translation**, **rotation**, **scale**, **shear**, **skew**
- merge multiple transformation matrices in a single matrix that is the **composition of multiple matrices**
- work with strings in both directions: **parse**, **render**
- **apply a transformation matrix to point(s)**
- **decompose a matrix into translation, scaling and rotation components, with flip decomposition support**
# Documentation
- [How to handle gestures with transformation-matrix](https://github.com/chrvadala/transformation-matrix/blob/main/docs/gestures.md)
- [APIs](https://github.com/chrvadala/transformation-matrix/blob/main/docs/api.md)
- Basic Operations
- [`identity()`](https://github.com/chrvadala/transformation-matrix/blob/main/docs/api.md#identity)
- [`flipX()`](https://github.com/chrvadala/transformation-matrix/blob/main/docs/api.md#flipX)
- [`flipY()`](https://github.com/chrvadala/transformation-matrix/blob/main/docs/api.md#flipY)
- [`flipOrigin()`](https://github.com/chrvadala/transformation-matrix/blob/main/docs/api.md#flipOrigin)
- [`rotate(angle, [cx], [cy])`](https://github.com/chrvadala/transformation-matrix/blob/main/docs/api.md#rotate)
- [`rotateDEG(angle, [cx], [cy])`](https://github.com/chrvadala/transformation-matrix/blob/main/docs/api.md#rotateDEG)
- [`scale(sx, [sy], [cx], [cy])`](https://github.com/chrvadala/transformation-matrix/blob/main/docs/api.md#scale)
- [`shear(shx, shy)`](https://github.com/chrvadala/transformation-matrix/blob/main/docs/api.md#shear)
- [`skew(ax, ay)`](https://github.com/chrvadala/transformation-matrix/blob/main/docs/api.md#skew)
- [`skewDEG(ax, ay)`](https://github.com/chrvadala/transformation-matrix/blob/main/docs/api.md#skewDEG)
- [`smoothMatrix(matrix, [precision])`](https://github.com/chrvadala/transformation-matrix/blob/main/docs/api.md#smoothMatrix)
- [`translate(tx, [ty])`](https://github.com/chrvadala/transformation-matrix/blob/main/docs/api.md#translate)
- [`inverse(matrix)`](https://github.com/chrvadala/transformation-matrix/blob/main/docs/api.md#inverse)
- Calculate
- [`fromDefinition(definitionOrArrayOfDefinition)`](https://github.com/chrvadala/transformation-matrix/blob/main/docs/api.md#fromDefinition)
- [`fromObject(object)`](https://github.com/chrvadala/transformation-matrix/blob/main/docs/api.md#fromObject)
- [`fromString(string)`](https://github.com/chrvadala/transformation-matrix/blob/main/docs/api.md#fromString)
- [`fromTransformAttribute(transformString)`](https://github.com/chrvadala/transformation-matrix/blob/main/docs/api.md#fromTransformAttribute)
- [`fromTriangles(t1, t2)`](https://github.com/chrvadala/transformation-matrix/blob/main/docs/api.md#fromTriangles)
- Validate
- [`isAffineMatrix(object)`](https://github.com/chrvadala/transformation-matrix/blob/main/docs/api.md#isAffineMatrix)
- Apply
- [`applyToPoint(matrix, point)`](https://github.com/chrvadala/transformation-matrix/blob/main/docs/api.md#applyToPoint)
- [`applyToPoints(matrix, points)`](https://github.com/chrvadala/transformation-matrix/blob/main/docs/api.md#applyToPoints)
- Stringify
- [`toCSS(matrix)`](https://github.com/chrvadala/transformation-matrix/blob/main/docs/api.md#toCSS)
- [`toSVG(matrix)`](https://github.com/chrvadala/transformation-matrix/blob/main/docs/api.md#toSVG)
- [`toString(matrix)`](https://github.com/chrvadala/transformation-matrix/blob/main/docs/api.md#toString)
- Compose
- [`transform(matrices)`](https://github.com/chrvadala/transformation-matrix/blob/main/docs/api.md#transform)
- [`compose(matrices)`](https://github.com/chrvadala/transformation-matrix/blob/main/docs/api.md#compose)
- Decompose
- [`decomposeTSR(matrix, flipX, flipY)`](https://github.com/chrvadala/transformation-matrix/blob/main/docs/api.md#decomposeTSR)
- Moving points (gestures)
- [`fromOneMovingPoint (startingPoint, endingPoint)`](https://github.com/chrvadala/transformation-matrix/blob/main/docs/api.md#fromonemovingpointstartingpoint-endingpoint)
- [`fromTwoMovingPoints (startingPoint1, startingPoint2, endingPoint1, endingPoint2)`](https://github.com/chrvadala/transformation-matrix/blob/main/docs/api.md#fromTwoMovingPoints)
# Install
```sh
npm install transformation-matrix
```
or
```html
<script src="https://unpkg.com/transformation-matrix@2"></script>
```
# Example
```js
import {scale, rotate, translate, compose, applyToPoint} from 'transformation-matrix';
let {scale, rotate, translate, compose, applyToPoint} = window.TransformationMatrix;
let {scale, rotate, translate, compose, applyToPoint} = require('transformation-matrix')
let matrix = compose(
translate(40,40),
rotate(Math.PI/2),
scale(2, 4)
);
applyToPoint(matrix, {x: 42, y: 42});
// { x: -128, y: 124.00000000000001 }
applyToPoint(matrix, [16, 24]);
// [ -56, 72 ]
```
# Changelog
- **0.0** - Preview version
- **1.0** - First public version
- **1.1** - Splits lib into different files
- **1.2** - Adds shear operation
- **1.3** - Adds umd support
- **1.4** - Adds typescript definitions
- **1.5** - Upgrades deps
- **1.6** - Adds optional parameter support on `translate(tx)`, `scale(sx)`, `rotate(angle, cx, cy)`
- **1.7** - Upgrades deps
- **1.8** - Fixes [#12](https://github.com/chrvadala/transformation-matrix/issues/12), Adds `fromTransformAttribute`, Discontinues node 4 support
- **1.9** - Adds `skew(ax, ay)`, Upgrades deps, Improves `fromTransformAttribute`
- **1.10**- Updates typescript definitions [#15](https://github.com/chrvadala/transformation-matrix/pull/15)
- **1.11**- Upgrades deps
- **1.12**- Migrates tests on [Jest](https://jestjs.io/), Integrates [standard.js](https://standardjs.com/), Upgrades deps
- **1.13**- Adds `compose` function, Upgrades deps, Exposes skew operation [#37](https://github.com/chrvadala/transformation-matrix/pull/37)
- **1.14**- Adds support for points defined as `Array` in the form `[x, y]` [#38](https://github.com/chrvadala/transformation-matrix/pull/38)
- **1.15**- Adds `fromTriangle` and `smoothMatrix` functions [#41](https://github.com/chrvadala/transformation-matrix/issues/41)
- **2.0**- Migrates to Babel 7 and updates dependencies; introduces `fromDefinition` function; breaking changes on `fromTransformAttribute` function; improves docs
- **2.1**- Upgrades deps; Adds Node.js v12 to CI
- **2.2**- Upgrades deps; Improves typescript definition [#66](https://github.com/chrvadala/transformation-matrix/pull/66)
- **2.3**- Adds `(cx,cy)` on `scale` function [#62](https://github.com/chrvadala/transformation-matrix/pull/62); Improves typescript definition [#66](https://github.com/chrvadala/transformation-matrix/pull/67); Upgrades deps
- **2.4**- Improves typescript definition [#75](https://github.com/chrvadala/transformation-matrix/pull/75)
- **2.5**- Upgrades deps; Deprecates NodeJS 8; Adds NodeJs 14 support
- **2.6**- Upgrades deps; Fixes fromTransformAttribute function [#84](https://github.com/chrvadala/transformation-matrix/pull/84)
- **2.7**- Upgrades deps;
- **2.8**- Upgrades deps;
- **2.9**- Adds `flipX()`, `flipY()`, `flipOrigin()` functions; Deprecates NodeJS 12 and adds NodeJS 16 support; Upgrades deps;
- **2.10** - Adds `decomposeTSR()` function [#88](https://github.com/chrvadala/transformation-matrix/pull/88); Upgrades deps;
- **2.11** - Migrates from yarn to npm; Upgrades deps; New [APIs documentation](https://github.com/chrvadala/transformation-matrix/blob/main/docs/api.md); Integrates [chrvadala/github-actions](https://github.com/chrvadala/github-actions);
- **2.12** - Migrates from PEG.js to Peggy [#89](https://github.com/chrvadala/transformation-matrix/pull/89); Upgrades deps;
- **2.13** - Upgrades deps; Improves typescript definition; Upgrades gh-actions deps;
- **2.14** - Upgrades deps; Adds `fromOneMovingPoint` and `fromTwoMovingPoints` functions [#95](https://github.com/chrvadala/transformation-matrix/pull/95)
- **2.15** - Removes circular dependencies [#97](https://github.com/chrvadala/transformation-matrix/pull/97); Upgrades gh-actions and deps
- **2.16** - Upgrades deps; Upgrades gh-actions deps;
- **3.0** - Refactors fromString [#107](https://github.com/chrvadala/transformation-matrix/pull/107) (⚠️ Breaking Changes described [here](https://github.com/chrvadala/transformation-matrix/pull/107#issue-2840194716)); Upgrades gh-actions and deps;
- **3.1** - Upgrades gh-actions and deps; Deprecates NodeJS 16,18 and adds 22
# Contributors
- [chrvadala](https://github.com/chrvadala) (author)
- [forabi](https://github.com/forabi)
- [nidu](https://github.com/nidu) (PEG.js descriptor)
- [aubergene](https://github.com/aubergene)
- [SophiaLi1](https://github.com/SophiaLi1)
- [Shuhei-Tsunoda](https://github.com/Shuhei-Tsunoda)
- [antonyRoberts](https://github.com/antonyRoberts)
- [mcwebb](https://github.com/mcwebb)
- [signalwerk](https://github.com/signalwerk)
- [estollnitz](https://github.com/estollnitz)
- [rodrigoapereira](https://github.com/rodrigoapereira)
- [formatlos](https://github.com/formatlos)
- [benhjames](https://github.com/benhjames)
- [hillin](https://github.com/hillin)
- [jedrzejchalubek](https://github.com/jedrzejchalubek)
# Some projects using transformation-matrix
- [**React Planner**](https://github.com/cvdlab/react-planner)
- [**React SVG Pan Zoom**](https://github.com/chrvadala/react-svg-pan-zoom)
- [**ngx-graph**](https://github.com/swimlane/ngx-graph)
- [**learn-anything**](https://github.com/learn-anything/learn-anything)
- [**react-supergrid**](https://github.com/tscircuit/supergrid)
- [**Others...**](https://github.com/chrvadala/transformation-matrix/network/dependents)
- Pull request your project!

View File

@@ -0,0 +1,17 @@
module.exports = function (api) {
api.cache(true)
return {
presets: [
[
'@babel/preset-env',
{
targets: {
node: 8
}
}
]
],
plugins: []
}
}

View File

@@ -0,0 +1,29 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.applyToPoint = applyToPoint;
exports.applyToPoints = applyToPoints;
/**
* Calculate a point transformed with an affine matrix
* @param matrix {Matrix} Affine Matrix
* @param point {Point} Point
* @returns {Point} Point
*/
function applyToPoint(matrix, point) {
return Array.isArray(point) ? [matrix.a * point[0] + matrix.c * point[1] + matrix.e, matrix.b * point[0] + matrix.d * point[1] + matrix.f] : {
x: matrix.a * point.x + matrix.c * point.y + matrix.e,
y: matrix.b * point.x + matrix.d * point.y + matrix.f
};
}
/**
* Calculate an array of points transformed with an affine matrix
* @param matrix {Matrix} Affine Matrix
* @param points {Point[]} Array of point
* @returns {Point[]} Array of point
*/
function applyToPoints(matrix, points) {
return points.map(point => applyToPoint(matrix, point));
}

View File

@@ -0,0 +1,78 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.decomposeTSR = decomposeTSR;
var _scale = require("./scale");
var _transform = require("./transform");
/**
* Decompose a matrix into translation, scaling and rotation components, optionally
* take horizontal and vertical flip in to consideration.
* Note this function decomposes a matrix in rotation -> scaling -> translation order. I.e. for
* certain translation T {tx, ty}, rotation R and scaling S { sx, sy }, it's only true for:
* decomposeTSR(compose(T, S, R)) === { translate: T, rotation: R, scale: S }
* composing in a different order may yield a different decomposition result.
* @param matrix {Matrix} Affine Matrix
* @param flipX {boolean} Whether the matrix contains vertical flip, i.e. mirrors on x-axis
* @param flipY {boolean} Whether the matrix contains horizontal flip, i.e. mirrors on y-axis
* @returns {Transform} A transform object consisted by its translation, scaling
* and rotation components.
*/
function decomposeTSR(matrix, flipX = false, flipY = false) {
// Remove flip from the matrix first - flip could be incorrectly interpreted as
// rotations (e.g. flipX + flipY = rotate by 180 degrees).
// Note flipX is a vertical flip, and flipY is a horizontal flip.
if (flipX) {
if (flipY) {
matrix = (0, _transform.compose)(matrix, (0, _scale.scale)(-1, -1));
} else {
matrix = (0, _transform.compose)(matrix, (0, _scale.scale)(1, -1));
}
} else if (flipY) {
matrix = (0, _transform.compose)(matrix, (0, _scale.scale)(-1, 1));
}
const a = matrix.a;
const b = matrix.b;
const c = matrix.c;
const d = matrix.d;
let scaleX, scaleY, rotation;
if (a !== 0 || c !== 0) {
const hypotAc = Math.hypot(a, c);
scaleX = hypotAc;
scaleY = (a * d - b * c) / hypotAc;
const acos = Math.acos(a / hypotAc);
rotation = c > 0 ? -acos : acos;
} else if (b !== 0 || d !== 0) {
const hypotBd = Math.hypot(b, d);
scaleX = (a * d - b * c) / hypotBd;
scaleY = hypotBd;
const acos = Math.acos(b / hypotBd);
rotation = Math.PI / 2 + (d > 0 ? -acos : acos);
} else {
scaleX = 0;
scaleY = 0;
rotation = 0;
}
// put the flip factors back
if (flipY) {
scaleX = -scaleX;
}
if (flipX) {
scaleY = -scaleY;
}
return {
translate: {
tx: matrix.e,
ty: matrix.f
},
scale: {
sx: scaleX,
sy: scaleY
},
rotation: {
angle: rotation
}
};
}

View File

@@ -0,0 +1,52 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.flipOrigin = flipOrigin;
exports.flipX = flipX;
exports.flipY = flipY;
/**
* Tranformation matrix that mirrors on x-axis
* @returns {Matrix} Affine Matrix
*/
function flipX() {
return {
a: 1,
c: 0,
e: 0,
b: 0,
d: -1,
f: 0
};
}
/**
* Tranformation matrix that mirrors on y-axis
* @returns {Matrix} Affine Matrix
*/
function flipY() {
return {
a: -1,
c: 0,
e: 0,
b: 0,
d: 1,
f: 0
};
}
/**
* Tranformation matrix that mirrors on origin
* @returns {Matrix} Affine Matrix
*/
function flipOrigin() {
return {
a: -1,
c: 0,
e: 0,
b: 0,
d: -1,
f: 0
};
}

View File

@@ -0,0 +1,76 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.fromDefinition = fromDefinition;
var _fromObject = require("./fromObject");
var _translate = require("./translate");
var _scale = require("./scale");
var _rotate = require("./rotate");
var _skew = require("./skew");
var _shear = require("./shear");
/**
* Converts array of matrix descriptor to array of matrix
* @param definitionOrArrayOfDefinition {Object[]} Array of object describing the matrix
* @returns {Matrix[]} Array of matrix
*
* @example
* > fromDefinition([
* { type: 'matrix', a:1, b:2, c:3, d:4, e:5, f:6 },
* { type: 'translate', tx: 10, ty: 20 },
* { type: 'scale', sx: 2, sy: 4 },
* { type: 'rotate', angle: 90, cx: 50, cy: 25 },
* { type: 'skewX', angle: 45 },
* { type: 'skewY', angle: 45 },
* { type: 'shear', shx: 10, shy: 20}
* ])
*
* [
* { a: 1, b: 2, c: 3, d: 4, e: 5, f: 6 },
* { a: 1, c: 0, e: 10, b: 0, d: 1, f: 20 },
* { a: 2, c: 0, e: 0, b: 0, d: 4, f: 0 },
* { a: 6.123, c: -1, e: 0, b: 1, d: 6.123, f: 0 },
* { a: 1, c: 0.99.., e: 0, b: 0, d: 1, f: 0 },
* { a: 1, c: 0, e: 0, b: 0.99, d: 1, f: 0 },
* { a: 1, c: 10, e: 0, b: 20, d: 1, f: 0 }
* ]
**/
function fromDefinition(definitionOrArrayOfDefinition) {
return Array.isArray(definitionOrArrayOfDefinition) ? definitionOrArrayOfDefinition.map(mapper) : mapper(definitionOrArrayOfDefinition);
function mapper(descriptor) {
switch (descriptor.type) {
case 'matrix':
if ('a' in descriptor && 'b' in descriptor && 'c' in descriptor && 'd' in descriptor && 'e' in descriptor && 'f' in descriptor) {
return (0, _fromObject.fromObject)(descriptor);
} else {
throw new Error('MISSING_MANDATORY_PARAM');
}
case 'translate':
if (!('tx' in descriptor)) throw new Error('MISSING_MANDATORY_PARAM');
if ('ty' in descriptor) return (0, _translate.translate)(descriptor.tx, descriptor.ty);
return (0, _translate.translate)(descriptor.tx);
case 'scale':
if (!('sx' in descriptor)) throw new Error('MISSING_MANDATORY_PARAM');
if ('sy' in descriptor) return (0, _scale.scale)(descriptor.sx, descriptor.sy);
return (0, _scale.scale)(descriptor.sx);
case 'rotate':
if (!('angle' in descriptor)) throw new Error('MISSING_MANDATORY_PARAM');
if ('cx' in descriptor && 'cy' in descriptor) {
return (0, _rotate.rotateDEG)(descriptor.angle, descriptor.cx, descriptor.cy);
}
return (0, _rotate.rotateDEG)(descriptor.angle);
case 'skewX':
if (!('angle' in descriptor)) throw new Error('MISSING_MANDATORY_PARAM');
return (0, _skew.skewDEG)(descriptor.angle, 0);
case 'skewY':
if (!('angle' in descriptor)) throw new Error('MISSING_MANDATORY_PARAM');
return (0, _skew.skewDEG)(0, descriptor.angle);
case 'shear':
if (!('shx' in descriptor && 'shy' in descriptor)) throw new Error('MISSING_MANDATORY_PARAM');
return (0, _shear.shear)(descriptor.shx, descriptor.shy);
default:
throw new Error('UNSUPPORTED_DESCRIPTOR');
}
}
}

View File

@@ -0,0 +1,52 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.fromOneMovingPoint = fromOneMovingPoint;
exports.fromTwoMovingPoints = fromTwoMovingPoints;
var _translate = require("./translate");
var _applyToPoint = require("./applyToPoint");
var _rotate = require("./rotate");
var _scale = require("./scale");
var _transform = require("./transform");
// https://manivannan-ai.medium.com/find-the-angle-between-three-points-from-2d-using-python-348c513e2cd
/**
* Calculate a transformation matrix from a point that starts from A to A'
* This approach can be associated to a pointer that moves on a device
* @param {Point} startingPoint - Starting point (A)
* @param {Point} endingPoint - Ending point (A')
*/
function fromOneMovingPoint(startingPoint, endingPoint) {
const tx = endingPoint.x - startingPoint.x;
const ty = endingPoint.y - startingPoint.y;
return (0, _translate.translate)(tx, ty);
}
/**
* Calculate a transformation matrix about two points that move from positions A and B to A' and B'
* This approach can be associated to a two finger gesture on a touch device
* @param {Point} startingPoint1 - Starting Point (A)
* @param {Point} startingPoint2 - Starting Point (B)
* @param {Point} endingPoint1 - Ending point (A')
* @param {Point} endingPoint2 - Ending Point (B')
*/
function fromTwoMovingPoints(startingPoint1, startingPoint2, endingPoint1, endingPoint2) {
// finds translation
const translationMatrix = fromOneMovingPoint(startingPoint1, endingPoint1);
const pointA = (0, _applyToPoint.applyToPoint)(translationMatrix, startingPoint2); // I have to translate this point
const center = endingPoint1;
const pointB = endingPoint2;
// finds rotation matrix
const angle = Math.atan2(pointB.y - center.y, pointB.x - center.x) - Math.atan2(pointA.y - center.y, pointA.x - center.x);
const rotationMatrix = (0, _rotate.rotate)(angle, center.x, center.y);
// finds scale matrix
const d1 = Math.sqrt(Math.pow(pointA.x - center.x, 2) + Math.pow(pointA.y - center.y, 2));
const d2 = Math.sqrt(Math.pow(pointB.x - center.x, 2) + Math.pow(pointB.y - center.y, 2));
const scalingLevel = d2 / d1;
const scalingMatrix = (0, _scale.scale)(scalingLevel, scalingLevel, center.x, center.y);
return (0, _transform.compose)([translationMatrix, scalingMatrix, rotationMatrix]);
}

View File

@@ -0,0 +1,22 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.fromObject = fromObject;
/**
* Extract an affine matrix from an object that contains a,b,c,d,e,f keys
* Any value could be a float or a string that contains a float
* @param object {Object} Object that contains a,b,c,d,e,f keys
* @return {Matrix} Affine Matrix
*/
function fromObject(object) {
return {
a: parseFloat(object.a),
b: parseFloat(object.b),
c: parseFloat(object.c),
d: parseFloat(object.d),
e: parseFloat(object.e),
f: parseFloat(object.f)
};
}

View File

@@ -0,0 +1,68 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.fromString = fromString;
exports.fromStringLegacy = fromStringLegacy;
/**
* @ignore
* @type {RegExp}
*/
const matrixRegex = /^matrix\(\s*([0-9_+-.e]+)\s*,\s*([0-9_+-.e]+)\s*,\s*([0-9_+-.e]+)\s*,\s*([0-9_+-.e]+)\s*,\s*([0-9_+-.e]+)\s*,\s*([0-9_+-.e]+)\s*\)$/i;
/**
* Parse a string formatted as matrix(a,b,c,d,e,f)
* @param string {string} String with an affine matrix
* @returns {Matrix} Affine Matrix
*
* @example
* > fromString('matrix(1,2,3,4,5,6)')
* {a: 1, b: 2, c: 3, d: 4, c: 5, e: 6}
*/
function fromString(string) {
const parseFloatOrThrow = number => {
const n = parseFloat(number);
if (Number.isFinite(n)) return n; // excludes NaN, +Infinite, -Infinite
throw new Error(`'${string}' is not a matrix`);
};
const prefix = string.substring(0, 7).toLowerCase();
const suffix = string.substring(string.length - 1);
const body = string.substring(7, string.length - 1);
const elements = body.split(',');
if (prefix === 'matrix(' && suffix === ')' && elements.length === 6) {
return {
a: parseFloatOrThrow(elements[0]),
b: parseFloatOrThrow(elements[1]),
c: parseFloatOrThrow(elements[2]),
d: parseFloatOrThrow(elements[3]),
e: parseFloatOrThrow(elements[4]),
f: parseFloatOrThrow(elements[5])
};
}
throw new Error(`'${string}' is not a matrix`);
}
/**
* Parse a string formatted as matrix(a,b,c,d,e,f) - Legacy implementation of `fromString(matrix)`;
* Read this PR for details {@link https://github.com/chrvadala/transformation-matrix/pull/107}
* @param string {string} String with an affine matrix
* @deprecated
* @returns {Matrix} Affine Matrix
*
* @example
* > fromStringLegacy('matrix(1,2,3,4,5,6)')
* {a: 1, b: 2, c: 3, d: 4, c: 5, e: 6}
*/
function fromStringLegacy(string) {
const parsed = string.match(matrixRegex);
if (parsed === null || parsed.length < 7) throw new Error(`'${string}' is not a matrix`);
return {
a: parseFloat(parsed[1]),
b: parseFloat(parsed[2]),
c: parseFloat(parsed[3]),
d: parseFloat(parsed[4]),
e: parseFloat(parsed[5]),
f: parseFloat(parsed[6])
};
}

View File

@@ -0,0 +1,26 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.fromTransformAttribute = fromTransformAttribute;
var _fromTransformAttribute = require("./fromTransformAttribute.autogenerated");
/**
* Parser for SVG Trasform Attribute http://www.w3.org/TR/SVG/coords.html#TransformAttribute
* @param transformString {string} Transform string as defined by w3 Consortium
* @returns {MatrixDescriptor[]} Array of MatrixDescriptor
*
* @example
* > fromTransformAttribute('translate(-10,-10) scale(2,2) translate(10,10)')
* [
* { type: 'translate', tx: -10, ty: -10},
* { type: 'scale', sx: 2, sy: 2 },
* { type: 'translate', tx: 10, ty: 10}
* ]
*
* > compose(fromDefinition(fromTransformAttribute('translate(-10, -10) scale(10, 10)')))
* { a: 10, c: 0, e: -10, b: 0, d: 10, f: -10 }
*/
function fromTransformAttribute(transformString) {
return (0, _fromTransformAttribute.parse)(transformString);
}

View File

@@ -0,0 +1,56 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.fromTriangles = fromTriangles;
var _inverse = require("./inverse");
var _transform = require("./transform");
var _smoothMatrix = require("./smoothMatrix");
/**
* Returns a matrix that transforms a triangle t1 into another triangle t2, or throws an exception if it is impossible.
* @param t1 {Point[]} Array of points containing the three points for the first triangle
* @param t2 {Point[]} Array of points containing the three points for the second triangle
* @returns {Matrix} Matrix which transforms t1 to t2
* @throws Exception if the matrix becomes not invertible
*/
function fromTriangles(t1, t2) {
// point p = first point of the triangle
const px1 = t1[0].x != null ? t1[0].x : t1[0][0];
const py1 = t1[0].y != null ? t1[0].y : t1[0][1];
const px2 = t2[0].x != null ? t2[0].x : t2[0][0];
const py2 = t2[0].y != null ? t2[0].y : t2[0][1];
// point q = second point of the triangle
const qx1 = t1[1].x != null ? t1[1].x : t1[1][0];
const qy1 = t1[1].y != null ? t1[1].y : t1[1][1];
const qx2 = t2[1].x != null ? t2[1].x : t2[1][0];
const qy2 = t2[1].y != null ? t2[1].y : t2[1][1];
// point r = third point of the triangle
const rx1 = t1[2].x != null ? t1[2].x : t1[2][0];
const ry1 = t1[2].y != null ? t1[2].y : t1[2][1];
const rx2 = t2[2].x != null ? t2[2].x : t2[2][0];
const ry2 = t2[2].y != null ? t2[2].y : t2[2][1];
const r1 = {
a: px1 - rx1,
b: py1 - ry1,
c: qx1 - rx1,
d: qy1 - ry1,
e: rx1,
f: ry1
};
const r2 = {
a: px2 - rx2,
b: py2 - ry2,
c: qx2 - rx2,
d: qy2 - ry2,
e: rx2,
f: ry2
};
const inverseR1 = (0, _inverse.inverse)(r1);
const affineMatrix = (0, _transform.transform)([r2, inverseR1]);
// round the matrix elements to smooth the finite inversion
return (0, _smoothMatrix.smoothMatrix)(affineMatrix);
}

View File

@@ -0,0 +1,20 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.identity = identity;
/**
* Identity matrix
* @returns {Matrix} Affine Matrix
*/
function identity() {
return {
a: 1,
c: 0,
e: 0,
b: 0,
d: 1,
f: 0
};
}

View File

@@ -0,0 +1,225 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
var _applyToPoint = require("./applyToPoint");
Object.keys(_applyToPoint).forEach(function (key) {
if (key === "default" || key === "__esModule") return;
if (key in exports && exports[key] === _applyToPoint[key]) return;
Object.defineProperty(exports, key, {
enumerable: true,
get: function () {
return _applyToPoint[key];
}
});
});
var _fromObject = require("./fromObject");
Object.keys(_fromObject).forEach(function (key) {
if (key === "default" || key === "__esModule") return;
if (key in exports && exports[key] === _fromObject[key]) return;
Object.defineProperty(exports, key, {
enumerable: true,
get: function () {
return _fromObject[key];
}
});
});
var _fromString = require("./fromString");
Object.keys(_fromString).forEach(function (key) {
if (key === "default" || key === "__esModule") return;
if (key in exports && exports[key] === _fromString[key]) return;
Object.defineProperty(exports, key, {
enumerable: true,
get: function () {
return _fromString[key];
}
});
});
var _identity = require("./identity");
Object.keys(_identity).forEach(function (key) {
if (key === "default" || key === "__esModule") return;
if (key in exports && exports[key] === _identity[key]) return;
Object.defineProperty(exports, key, {
enumerable: true,
get: function () {
return _identity[key];
}
});
});
var _inverse = require("./inverse");
Object.keys(_inverse).forEach(function (key) {
if (key === "default" || key === "__esModule") return;
if (key in exports && exports[key] === _inverse[key]) return;
Object.defineProperty(exports, key, {
enumerable: true,
get: function () {
return _inverse[key];
}
});
});
var _isAffineMatrix = require("./isAffineMatrix");
Object.keys(_isAffineMatrix).forEach(function (key) {
if (key === "default" || key === "__esModule") return;
if (key in exports && exports[key] === _isAffineMatrix[key]) return;
Object.defineProperty(exports, key, {
enumerable: true,
get: function () {
return _isAffineMatrix[key];
}
});
});
var _rotate = require("./rotate");
Object.keys(_rotate).forEach(function (key) {
if (key === "default" || key === "__esModule") return;
if (key in exports && exports[key] === _rotate[key]) return;
Object.defineProperty(exports, key, {
enumerable: true,
get: function () {
return _rotate[key];
}
});
});
var _scale = require("./scale");
Object.keys(_scale).forEach(function (key) {
if (key === "default" || key === "__esModule") return;
if (key in exports && exports[key] === _scale[key]) return;
Object.defineProperty(exports, key, {
enumerable: true,
get: function () {
return _scale[key];
}
});
});
var _shear = require("./shear");
Object.keys(_shear).forEach(function (key) {
if (key === "default" || key === "__esModule") return;
if (key in exports && exports[key] === _shear[key]) return;
Object.defineProperty(exports, key, {
enumerable: true,
get: function () {
return _shear[key];
}
});
});
var _skew = require("./skew");
Object.keys(_skew).forEach(function (key) {
if (key === "default" || key === "__esModule") return;
if (key in exports && exports[key] === _skew[key]) return;
Object.defineProperty(exports, key, {
enumerable: true,
get: function () {
return _skew[key];
}
});
});
var _toString = require("./toString");
Object.keys(_toString).forEach(function (key) {
if (key === "default" || key === "__esModule") return;
if (key in exports && exports[key] === _toString[key]) return;
Object.defineProperty(exports, key, {
enumerable: true,
get: function () {
return _toString[key];
}
});
});
var _transform = require("./transform");
Object.keys(_transform).forEach(function (key) {
if (key === "default" || key === "__esModule") return;
if (key in exports && exports[key] === _transform[key]) return;
Object.defineProperty(exports, key, {
enumerable: true,
get: function () {
return _transform[key];
}
});
});
var _translate = require("./translate");
Object.keys(_translate).forEach(function (key) {
if (key === "default" || key === "__esModule") return;
if (key in exports && exports[key] === _translate[key]) return;
Object.defineProperty(exports, key, {
enumerable: true,
get: function () {
return _translate[key];
}
});
});
var _fromTriangles = require("./fromTriangles");
Object.keys(_fromTriangles).forEach(function (key) {
if (key === "default" || key === "__esModule") return;
if (key in exports && exports[key] === _fromTriangles[key]) return;
Object.defineProperty(exports, key, {
enumerable: true,
get: function () {
return _fromTriangles[key];
}
});
});
var _smoothMatrix = require("./smoothMatrix");
Object.keys(_smoothMatrix).forEach(function (key) {
if (key === "default" || key === "__esModule") return;
if (key in exports && exports[key] === _smoothMatrix[key]) return;
Object.defineProperty(exports, key, {
enumerable: true,
get: function () {
return _smoothMatrix[key];
}
});
});
var _fromDefinition = require("./fromDefinition");
Object.keys(_fromDefinition).forEach(function (key) {
if (key === "default" || key === "__esModule") return;
if (key in exports && exports[key] === _fromDefinition[key]) return;
Object.defineProperty(exports, key, {
enumerable: true,
get: function () {
return _fromDefinition[key];
}
});
});
var _fromTransformAttribute = require("./fromTransformAttribute");
Object.keys(_fromTransformAttribute).forEach(function (key) {
if (key === "default" || key === "__esModule") return;
if (key in exports && exports[key] === _fromTransformAttribute[key]) return;
Object.defineProperty(exports, key, {
enumerable: true,
get: function () {
return _fromTransformAttribute[key];
}
});
});
var _decompose = require("./decompose");
Object.keys(_decompose).forEach(function (key) {
if (key === "default" || key === "__esModule") return;
if (key in exports && exports[key] === _decompose[key]) return;
Object.defineProperty(exports, key, {
enumerable: true,
get: function () {
return _decompose[key];
}
});
});
var _flip = require("./flip");
Object.keys(_flip).forEach(function (key) {
if (key === "default" || key === "__esModule") return;
if (key in exports && exports[key] === _flip[key]) return;
Object.defineProperty(exports, key, {
enumerable: true,
get: function () {
return _flip[key];
}
});
});
var _fromMovingPoints = require("./fromMovingPoints");
Object.keys(_fromMovingPoints).forEach(function (key) {
if (key === "default" || key === "__esModule") return;
if (key in exports && exports[key] === _fromMovingPoints[key]) return;
Object.defineProperty(exports, key, {
enumerable: true,
get: function () {
return _fromMovingPoints[key];
}
});
});

View File

@@ -0,0 +1,32 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.inverse = inverse;
/**
* Calculate a matrix that is the inverse of the provided matrix
* @param matrix {Matrix} Affine Matrix
* @returns {Matrix} Inverted Affine Matrix
*/
function inverse(matrix) {
// http://www.wolframalpha.com/input/?i=Inverse+%5B%7B%7Ba,c,e%7D,%7Bb,d,f%7D,%7B0,0,1%7D%7D%5D
const {
a,
b,
c,
d,
e,
f
} = matrix;
const denom = a * d - b * c;
return {
a: d / denom,
b: b / -denom,
c: c / -denom,
d: a / denom,
e: (d * e - c * f) / -denom,
f: (b * e - a * f) / denom
};
}

View File

@@ -0,0 +1,16 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.isAffineMatrix = isAffineMatrix;
var _utils = require("./utils");
/**
* Check if the object contain an affine matrix
* @param object {Object} Generic Plain Object
* @return {boolean} True if is an object and contains an affine matrix
*/
function isAffineMatrix(object) {
return (0, _utils.isObject)(object) && 'a' in object && (0, _utils.isNumeric)(object.a) && 'b' in object && (0, _utils.isNumeric)(object.b) && 'c' in object && (0, _utils.isNumeric)(object.c) && 'd' in object && (0, _utils.isNumeric)(object.d) && 'e' in object && (0, _utils.isNumeric)(object.e) && 'f' in object && (0, _utils.isNumeric)(object.f);
}

View File

@@ -0,0 +1,49 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.rotate = rotate;
exports.rotateDEG = rotateDEG;
var _utils = require("./utils");
var _translate = require("./translate");
var _transform = require("./transform");
const {
cos,
sin,
PI
} = Math;
/**
* Calculate a rotation matrix
* @param angle {number} Angle in radians
* @param [cx] {number} If (cx,cy) are supplied the rotate is about this point
* @param [cy] {number} If (cx,cy) are supplied the rotate is about this point
* @returns {Matrix} Affine Matrix
*/
function rotate(angle, cx, cy) {
const cosAngle = cos(angle);
const sinAngle = sin(angle);
const rotationMatrix = {
a: cosAngle,
c: -sinAngle,
e: 0,
b: sinAngle,
d: cosAngle,
f: 0
};
if ((0, _utils.isUndefined)(cx) || (0, _utils.isUndefined)(cy)) {
return rotationMatrix;
}
return (0, _transform.transform)([(0, _translate.translate)(cx, cy), rotationMatrix, (0, _translate.translate)(-cx, -cy)]);
}
/**
* Calculate a rotation matrix with a DEG angle
* @param angle {number} Angle in degree
* @param [cx] {number} If (cx,cy) are supplied the rotate is about this point
* @param [cy] {number} If (cx,cy) are supplied the rotate is about this point
* @returns {Matrix} Affine Matrix
*/
function rotateDEG(angle, cx = undefined, cy = undefined) {
return rotate(angle * PI / 180, cx, cy);
}

View File

@@ -0,0 +1,32 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.scale = scale;
var _utils = require("./utils");
var _translate = require("./translate");
var _transform = require("./transform");
/**
* Calculate a scaling matrix
* @param sx {number} Scaling on axis x
* @param [sy = sx] {number} Scaling on axis y (default sx)
* @param [cx] {number} If (cx,cy) are supplied the scaling is about this point
* @param [cy] {number} If (cx,cy) are supplied the scaling is about this point
* @returns {Matrix} Affine Matrix
*/
function scale(sx, sy = undefined, cx = undefined, cy = undefined) {
if ((0, _utils.isUndefined)(sy)) sy = sx;
const scaleMatrix = {
a: sx,
c: 0,
e: 0,
b: 0,
d: sy,
f: 0
};
if ((0, _utils.isUndefined)(cx) || (0, _utils.isUndefined)(cy)) {
return scaleMatrix;
}
return (0, _transform.transform)([(0, _translate.translate)(cx, cy), scaleMatrix, (0, _translate.translate)(-cx, -cy)]);
}

View File

@@ -0,0 +1,22 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.shear = shear;
/**
* Calculate a shear matrix
* @param shx {number} Shear on axis x
* @param shy {number} Shear on axis y
* @returns {Matrix} Affine Matrix
*/
function shear(shx, shy) {
return {
a: 1,
c: shx,
e: 0,
b: shy,
d: 1,
f: 0
};
}

View File

@@ -0,0 +1,38 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.skew = skew;
exports.skewDEG = skewDEG;
// https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/skew
const {
tan
} = Math;
/**
* Calculate a skew matrix
* @param ax {number} Skew on axis x
* @param ay {number} Skew on axis y
* @returns {Matrix} Affine Matrix
*/
function skew(ax, ay) {
return {
a: 1,
c: tan(ax),
e: 0,
b: tan(ay),
d: 1,
f: 0
};
}
/**
* Calculate a skew matrix using DEG angles
* @param ax {number} Skew on axis x
* @param ay {number} Skew on axis y
* @returns {Matrix} Affine Matrix
*/
function skewDEG(ax, ay) {
return skew(ax * Math.PI / 180, ay * Math.PI / 180);
}

View File

@@ -0,0 +1,22 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.smoothMatrix = smoothMatrix;
/**
* Rounds all elements of the given matrix using the given precision
* @param matrix {Matrix} An affine matrix to round
* @param [precision] {number} A precision to use for Math.round. Defaults to 10000000000 (meaning which rounds to the 10th digit after the comma).
* @returns {Matrix} The rounded Affine Matrix
*/
function smoothMatrix(matrix, precision = 10000000000) {
return {
a: Math.round(matrix.a * precision) / precision,
b: Math.round(matrix.b * precision) / precision,
c: Math.round(matrix.c * precision) / precision,
d: Math.round(matrix.d * precision) / precision,
e: Math.round(matrix.e * precision) / precision,
f: Math.round(matrix.f * precision) / precision
};
}

View File

@@ -0,0 +1,34 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.toCSS = toCSS;
exports.toSVG = toSVG;
exports.toString = toString;
/**
* Serialize an affine matrix to a string that can be used with CSS or SVG
* @param matrix {Matrix} Affine Matrix
* @returns {string} String that contains an affine matrix formatted as matrix(a,b,c,d,e,f)
*/
function toCSS(matrix) {
return toString(matrix);
}
/**
* Serialize an affine matrix to a string that can be used with CSS or SVG
* @param matrix {Matrix} Affine Matrix
* @returns {string} String that contains an affine matrix formatted as matrix(a,b,c,d,e,f)
*/
function toSVG(matrix) {
return toString(matrix);
}
/**
* Serialize an affine matrix to a string that can be used with CSS or SVG
* @param matrix {Matrix} Affine Matrix
* @returns {string} String that contains an affine matrix formatted as matrix(a,b,c,d,e,f)
*/
function toString(matrix) {
return `matrix(${matrix.a},${matrix.b},${matrix.c},${matrix.d},${matrix.e},${matrix.f})`;
}

View File

@@ -0,0 +1,48 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.compose = compose;
exports.transform = transform;
/**
* Merge multiple matrices into one
* @param matrices {...Matrix | Matrix[]} Matrices listed as separate parameters or in an array
* @returns {Matrix} Affine Matrix
*/
function transform(...matrices) {
matrices = Array.isArray(matrices[0]) ? matrices[0] : matrices;
const multiply = (m1, m2) => {
return {
a: m1.a * m2.a + m1.c * m2.b,
c: m1.a * m2.c + m1.c * m2.d,
e: m1.a * m2.e + m1.c * m2.f + m1.e,
b: m1.b * m2.a + m1.d * m2.b,
d: m1.b * m2.c + m1.d * m2.d,
f: m1.b * m2.e + m1.d * m2.f + m1.f
};
};
switch (matrices.length) {
case 0:
throw new Error('no matrices provided');
case 1:
return matrices[0];
case 2:
return multiply(matrices[0], matrices[1]);
default:
{
const [m1, m2, ...rest] = matrices;
const m = multiply(m1, m2);
return transform(m, ...rest);
}
}
}
/**
* Merge multiple matrices into one
* @param matrices {...Matrix | Matrix[]} Matrices listed as separate parameters or in an array
* @returns {Matrix} Affine Matrix
*/
function compose(...matrices) {
return transform(...matrices);
}

View File

@@ -0,0 +1,22 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.translate = translate;
/**
* Calculate a translate matrix
* @param tx {number} Translation on axis x
* @param [ty = 0] {number} Translation on axis y
* @returns {Matrix} Affine Matrix
*/
function translate(tx, ty = 0) {
return {
a: 1,
c: 0,
e: tx,
b: 0,
d: 1,
f: ty
};
}

View File

@@ -0,0 +1,21 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.isNumeric = isNumeric;
exports.isObject = isObject;
exports.isUndefined = isUndefined;
exports.matchesShape = matchesShape;
function isUndefined(val) {
return typeof val === 'undefined';
}
function isNumeric(n) {
return typeof n === 'number' && !Number.isNaN(n) && Number.isFinite(n);
}
function isObject(obj) {
return typeof obj === 'object' && obj !== null && !Array.isArray(obj);
}
function matchesShape(obj, keys) {
return keys.every(key => key in obj);
}

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,77 @@
{
"name": "transformation-matrix",
"version": "3.1.0",
"description": "2d transformation matrix functions written in ES6 syntax. Tree shaking ready!",
"main": "./build-commonjs/index.js",
"typings": "./src/transformation-matrix.d.ts",
"module": "./src/index.js",
"jsnext:main": "./src/index.js",
"unpkg": "./build-umd/transformation-matrix.min.js",
"files": [
"README.md",
"build-commonjs",
"build-umd",
"src",
"tests",
"transformation-matrix.d.ts",
"babel.config.js"
],
"scripts": {
"build": "npm run docs:api && npm run build:parser && npm run build:lib",
"test": "npm run test:standard && npm run test:coverage && npm run test:typescript",
"clean": "del build-* coverage",
"build:parser": "peggy --format es -o src/fromTransformAttribute.autogenerated.js src/fromTransformAttribute.peggy",
"build:lib": "npm run clean && npm run build:commonjs && npm run build:umd && npm run build:umd-min",
"build:commonjs": "babel src --out-dir build-commonjs",
"build:umd": "webpack --config ./webpack-umd.config.js",
"build:umd-min": "webpack --config ./webpack-umd.config.js --env minimize",
"test:jest": "jest",
"test:coverage": "jest --coverage",
"test:standard": "standard",
"test:typescript": "tsc --strict src/*.ts",
"docs:api": "jsdoc2md --template docs/templates/api.hbs --example-lang js --heading-depth 2 src/*.js > docs/api.md"
},
"repository": {
"type": "git",
"url": "git+https://github.com/chrvadala/transformation-matrix.git"
},
"keywords": [
"transformation-matrix",
"2d-transformations",
"three-shaking",
"scale",
"zoom",
"translate",
"transform"
],
"author": "chrvadala",
"license": "MIT",
"bugs": {
"url": "https://github.com/chrvadala/transformation-matrix/issues"
},
"homepage": "https://github.com/chrvadala/transformation-matrix#readme",
"devDependencies": {
"@babel/cli": "^7.28.3",
"@babel/core": "^7.28.3",
"@babel/plugin-proposal-object-rest-spread": "^7.20.7",
"@babel/plugin-transform-runtime": "^7.28.3",
"@babel/preset-env": "^7.28.3",
"@types/jest": "^30.0.0",
"babel-loader": "^10.0.0",
"del-cli": "^6.0.0",
"jest": "^30.0.5",
"jsdoc-to-markdown": "^9.1.2",
"peggy": "^5.0.6",
"standard": "^17.1.2",
"typescript": "^5.9.2",
"webpack": "^5.101.3",
"webpack-cli": "^6.0.1"
},
"standard": {
"ignore": [
"src/*.autogenerated.js",
"example.js"
]
},
"funding": "https://github.com/sponsors/chrvadala"
}

View File

@@ -0,0 +1,27 @@
/**
* Calculate a point transformed with an affine matrix
* @param matrix {Matrix} Affine Matrix
* @param point {Point} Point
* @returns {Point} Point
*/
export function applyToPoint (matrix, point) {
return Array.isArray(point)
? [
matrix.a * point[0] + matrix.c * point[1] + matrix.e,
matrix.b * point[0] + matrix.d * point[1] + matrix.f
]
: {
x: matrix.a * point.x + matrix.c * point.y + matrix.e,
y: matrix.b * point.x + matrix.d * point.y + matrix.f
}
}
/**
* Calculate an array of points transformed with an affine matrix
* @param matrix {Matrix} Affine Matrix
* @param points {Point[]} Array of point
* @returns {Point[]} Array of point
*/
export function applyToPoints (matrix, points) {
return points.map(point => applyToPoint(matrix, point))
}

View File

@@ -0,0 +1,66 @@
import { scale } from './scale'
import { compose } from './transform'
/**
* Decompose a matrix into translation, scaling and rotation components, optionally
* take horizontal and vertical flip in to consideration.
* Note this function decomposes a matrix in rotation -> scaling -> translation order. I.e. for
* certain translation T {tx, ty}, rotation R and scaling S { sx, sy }, it's only true for:
* decomposeTSR(compose(T, S, R)) === { translate: T, rotation: R, scale: S }
* composing in a different order may yield a different decomposition result.
* @param matrix {Matrix} Affine Matrix
* @param flipX {boolean} Whether the matrix contains vertical flip, i.e. mirrors on x-axis
* @param flipY {boolean} Whether the matrix contains horizontal flip, i.e. mirrors on y-axis
* @returns {Transform} A transform object consisted by its translation, scaling
* and rotation components.
*/
export function decomposeTSR (matrix, flipX = false, flipY = false) {
// Remove flip from the matrix first - flip could be incorrectly interpreted as
// rotations (e.g. flipX + flipY = rotate by 180 degrees).
// Note flipX is a vertical flip, and flipY is a horizontal flip.
if (flipX) {
if (flipY) {
matrix = compose(matrix, scale(-1, -1))
} else {
matrix = compose(matrix, scale(1, -1))
}
} else if (flipY) {
matrix = compose(matrix, scale(-1, 1))
}
const a = matrix.a; const b = matrix.b
const c = matrix.c; const d = matrix.d
let scaleX, scaleY, rotation
if (a !== 0 || c !== 0) {
const hypotAc = Math.hypot(a, c)
scaleX = hypotAc
scaleY = (a * d - b * c) / hypotAc
const acos = Math.acos(a / hypotAc)
rotation = c > 0 ? -acos : acos
} else if (b !== 0 || d !== 0) {
const hypotBd = Math.hypot(b, d)
scaleX = (a * d - b * c) / hypotBd
scaleY = hypotBd
const acos = Math.acos(b / hypotBd)
rotation = Math.PI / 2 + (d > 0 ? -acos : acos)
} else {
scaleX = 0
scaleY = 0
rotation = 0
}
// put the flip factors back
if (flipY) {
scaleX = -scaleX
}
if (flipX) {
scaleY = -scaleY
}
return {
translate: { tx: matrix.e, ty: matrix.f },
scale: { sx: scaleX, sy: scaleY },
rotation: { angle: rotation }
}
}

View File

@@ -0,0 +1,44 @@
/**
* Tranformation matrix that mirrors on x-axis
* @returns {Matrix} Affine Matrix
*/
export function flipX () {
return {
a: 1,
c: 0,
e: 0,
b: 0,
d: -1,
f: 0
}
}
/**
* Tranformation matrix that mirrors on y-axis
* @returns {Matrix} Affine Matrix
*/
export function flipY () {
return {
a: -1,
c: 0,
e: 0,
b: 0,
d: 1,
f: 0
}
}
/**
* Tranformation matrix that mirrors on origin
* @returns {Matrix} Affine Matrix
*/
export function flipOrigin () {
return {
a: -1,
c: 0,
e: 0,
b: 0,
d: -1,
f: 0
}
}

View File

@@ -0,0 +1,92 @@
import { fromObject } from './fromObject'
import { translate } from './translate'
import { scale } from './scale'
import { rotateDEG } from './rotate'
import { skewDEG } from './skew'
import { shear } from './shear'
/**
* Converts array of matrix descriptor to array of matrix
* @param definitionOrArrayOfDefinition {Object[]} Array of object describing the matrix
* @returns {Matrix[]} Array of matrix
*
* @example
* > fromDefinition([
* { type: 'matrix', a:1, b:2, c:3, d:4, e:5, f:6 },
* { type: 'translate', tx: 10, ty: 20 },
* { type: 'scale', sx: 2, sy: 4 },
* { type: 'rotate', angle: 90, cx: 50, cy: 25 },
* { type: 'skewX', angle: 45 },
* { type: 'skewY', angle: 45 },
* { type: 'shear', shx: 10, shy: 20}
* ])
*
* [
* { a: 1, b: 2, c: 3, d: 4, e: 5, f: 6 },
* { a: 1, c: 0, e: 10, b: 0, d: 1, f: 20 },
* { a: 2, c: 0, e: 0, b: 0, d: 4, f: 0 },
* { a: 6.123, c: -1, e: 0, b: 1, d: 6.123, f: 0 },
* { a: 1, c: 0.99.., e: 0, b: 0, d: 1, f: 0 },
* { a: 1, c: 0, e: 0, b: 0.99, d: 1, f: 0 },
* { a: 1, c: 10, e: 0, b: 20, d: 1, f: 0 }
* ]
**/
export function fromDefinition (definitionOrArrayOfDefinition) {
return Array.isArray(definitionOrArrayOfDefinition)
? definitionOrArrayOfDefinition.map(mapper)
: mapper(definitionOrArrayOfDefinition)
function mapper (descriptor) {
switch (descriptor.type) {
case 'matrix':
if ('a' in descriptor &&
'b' in descriptor &&
'c' in descriptor &&
'd' in descriptor &&
'e' in descriptor &&
'f' in descriptor
) {
return fromObject(descriptor)
} else {
throw new Error('MISSING_MANDATORY_PARAM')
}
case 'translate':
if (!('tx' in descriptor)) throw new Error('MISSING_MANDATORY_PARAM')
if ('ty' in descriptor) return translate(descriptor.tx, descriptor.ty)
return translate(descriptor.tx)
case 'scale':
if (!('sx' in descriptor)) throw new Error('MISSING_MANDATORY_PARAM')
if ('sy' in descriptor) return scale(descriptor.sx, descriptor.sy)
return scale(descriptor.sx)
case 'rotate':
if (!('angle' in descriptor)) throw new Error('MISSING_MANDATORY_PARAM')
if ('cx' in descriptor && 'cy' in descriptor) {
return rotateDEG(descriptor.angle, descriptor.cx, descriptor.cy)
}
return rotateDEG(descriptor.angle)
case 'skewX':
if (!('angle' in descriptor)) throw new Error('MISSING_MANDATORY_PARAM')
return skewDEG(descriptor.angle, 0)
case 'skewY':
if (!('angle' in descriptor)) throw new Error('MISSING_MANDATORY_PARAM')
return skewDEG(0, descriptor.angle)
case 'shear':
if (!('shx' in descriptor && 'shy' in descriptor)) throw new Error('MISSING_MANDATORY_PARAM')
return shear(descriptor.shx, descriptor.shy)
default:
throw new Error('UNSUPPORTED_DESCRIPTOR')
}
}
}

View File

@@ -0,0 +1,53 @@
import { translate } from './translate'
import { applyToPoint } from './applyToPoint'
import { rotate } from './rotate'
import { scale } from './scale'
import { compose } from './transform'
// https://manivannan-ai.medium.com/find-the-angle-between-three-points-from-2d-using-python-348c513e2cd
/**
* Calculate a transformation matrix from a point that starts from A to A'
* This approach can be associated to a pointer that moves on a device
* @param {Point} startingPoint - Starting point (A)
* @param {Point} endingPoint - Ending point (A')
*/
export function fromOneMovingPoint (startingPoint, endingPoint) {
const tx = endingPoint.x - startingPoint.x
const ty = endingPoint.y - startingPoint.y
return translate(tx, ty)
}
/**
* Calculate a transformation matrix about two points that move from positions A and B to A' and B'
* This approach can be associated to a two finger gesture on a touch device
* @param {Point} startingPoint1 - Starting Point (A)
* @param {Point} startingPoint2 - Starting Point (B)
* @param {Point} endingPoint1 - Ending point (A')
* @param {Point} endingPoint2 - Ending Point (B')
*/
export function fromTwoMovingPoints (startingPoint1, startingPoint2, endingPoint1, endingPoint2) {
// finds translation
const translationMatrix = fromOneMovingPoint(startingPoint1, endingPoint1)
const pointA = applyToPoint(translationMatrix, startingPoint2) // I have to translate this point
const center = endingPoint1
const pointB = endingPoint2
// finds rotation matrix
const angle = Math.atan2(pointB.y - center.y, pointB.x - center.x) - Math.atan2(pointA.y - center.y, pointA.x - center.x)
const rotationMatrix = rotate(angle, center.x, center.y)
// finds scale matrix
const d1 = Math.sqrt(Math.pow(pointA.x - center.x, 2) + Math.pow(pointA.y - center.y, 2))
const d2 = Math.sqrt(Math.pow(pointB.x - center.x, 2) + Math.pow(pointB.y - center.y, 2))
const scalingLevel = d2 / d1
const scalingMatrix = scale(scalingLevel, scalingLevel, center.x, center.y)
return compose([
translationMatrix,
scalingMatrix,
rotationMatrix
])
}

View File

@@ -0,0 +1,16 @@
/**
* Extract an affine matrix from an object that contains a,b,c,d,e,f keys
* Any value could be a float or a string that contains a float
* @param object {Object} Object that contains a,b,c,d,e,f keys
* @return {Matrix} Affine Matrix
*/
export function fromObject (object) {
return {
a: parseFloat(object.a),
b: parseFloat(object.b),
c: parseFloat(object.c),
d: parseFloat(object.d),
e: parseFloat(object.e),
f: parseFloat(object.f)
}
}

View File

@@ -0,0 +1,68 @@
/**
* @ignore
* @type {RegExp}
*/
const matrixRegex = /^matrix\(\s*([0-9_+-.e]+)\s*,\s*([0-9_+-.e]+)\s*,\s*([0-9_+-.e]+)\s*,\s*([0-9_+-.e]+)\s*,\s*([0-9_+-.e]+)\s*,\s*([0-9_+-.e]+)\s*\)$/i
/**
* Parse a string formatted as matrix(a,b,c,d,e,f)
* @param string {string} String with an affine matrix
* @returns {Matrix} Affine Matrix
*
* @example
* > fromString('matrix(1,2,3,4,5,6)')
* {a: 1, b: 2, c: 3, d: 4, c: 5, e: 6}
*/
export function fromString (string) {
const parseFloatOrThrow = number => {
const n = parseFloat(number)
if (Number.isFinite(n)) return n // excludes NaN, +Infinite, -Infinite
throw new Error(`'${string}' is not a matrix`)
}
const prefix = string.substring(0, 7).toLowerCase()
const suffix = string.substring(string.length - 1)
const body = string.substring(7, string.length - 1)
const elements = body.split(',')
if (
prefix === 'matrix(' &&
suffix === ')' &&
elements.length === 6
) {
return {
a: parseFloatOrThrow(elements[0]),
b: parseFloatOrThrow(elements[1]),
c: parseFloatOrThrow(elements[2]),
d: parseFloatOrThrow(elements[3]),
e: parseFloatOrThrow(elements[4]),
f: parseFloatOrThrow(elements[5])
}
}
throw new Error(`'${string}' is not a matrix`)
}
/**
* Parse a string formatted as matrix(a,b,c,d,e,f) - Legacy implementation of `fromString(matrix)`;
* Read this PR for details {@link https://github.com/chrvadala/transformation-matrix/pull/107}
* @param string {string} String with an affine matrix
* @deprecated
* @returns {Matrix} Affine Matrix
*
* @example
* > fromStringLegacy('matrix(1,2,3,4,5,6)')
* {a: 1, b: 2, c: 3, d: 4, c: 5, e: 6}
*/
export function fromStringLegacy (string) {
const parsed = string.match(matrixRegex)
if (parsed === null || parsed.length < 7) throw new Error(`'${string}' is not a matrix`)
return {
a: parseFloat(parsed[1]),
b: parseFloat(parsed[2]),
c: parseFloat(parsed[3]),
d: parseFloat(parsed[4]),
e: parseFloat(parsed[5]),
f: parseFloat(parsed[6])
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,21 @@
import { parse } from './fromTransformAttribute.autogenerated'
/**
* Parser for SVG Trasform Attribute http://www.w3.org/TR/SVG/coords.html#TransformAttribute
* @param transformString {string} Transform string as defined by w3 Consortium
* @returns {MatrixDescriptor[]} Array of MatrixDescriptor
*
* @example
* > fromTransformAttribute('translate(-10,-10) scale(2,2) translate(10,10)')
* [
* { type: 'translate', tx: -10, ty: -10},
* { type: 'scale', sx: 2, sy: 2 },
* { type: 'translate', tx: 10, ty: 10}
* ]
*
* > compose(fromDefinition(fromTransformAttribute('translate(-10, -10) scale(10, 10)')))
* { a: 10, c: 0, e: -10, b: 0, d: 10, f: -10 }
*/
export function fromTransformAttribute (transformString) {
return parse(transformString)
}

View File

@@ -0,0 +1,106 @@
/*
* Parser for SVG transform.
* Based on http://www.w3.org/TR/SVG/coords.html#TransformAttribute
*/
transformList
= wsp* ts:transforms? wsp* { return ts; }
transforms
= t:transform commaWsp+ ts:transforms { return t.concat(ts) }
/ transform
transform
= matrix
/ translate
/ scale
/ rotate
/ skewX
/ skewY
matrix
= "matrix" wsp* "(" wsp*
a:number commaWsp
b:number commaWsp
c:number commaWsp
d:number commaWsp
e:number commaWsp
f:number wsp* ")" {
return [{type: 'matrix', a: a, b: b, c: c, d: d, e: e, f: f}];
}
translate
= "translate" wsp* "(" wsp* tx:number ty:commaWspNumber? wsp* ")" {
var t = {type: 'translate', tx: tx};
if (ty) t.ty = ty;
return [t];
}
scale
= "scale" wsp* "(" wsp* sx:number sy:commaWspNumber? wsp* ")" {
var s = {type:'scale', sx: sx};
if (sy) s.sy = sy;
return [s];
}
rotate
= "rotate" wsp* "(" wsp* angle:number c:commaWspTwoNumbers? wsp* ")" {
var r = {type:'rotate', angle: angle};
if (c) {
r.cx = c[0];
r.cy = c[1];
}
return [r];
}
skewX
= "skewX" wsp* "(" wsp* angle:number wsp* ")" {
return [{type: 'skewX', angle: angle}];
}
skewY
= "skewY" wsp* "(" wsp* angle:number wsp* ")" {
return [{type: 'skewY', angle: angle}];
}
number
= f:(sign? floatingPointConstant) { return parseFloat(f.join("")); }
/ i:(sign? integerConstant) { return parseInt(i.join("")); }
commaWspNumber
= commaWsp n:number { return n; }
commaWspTwoNumbers
= commaWsp n1:number commaWsp n2:number { return [n1, n2]; }
commaWsp
= (wsp+ comma? wsp*) / (comma wsp*)
comma
= ","
integerConstant
= ds:digitSequence { return ds.join(""); }
floatingPointConstant
= f:fractionalConstant e:exponent? { return [f, e || null].join("")}
/ d:digitSequence e:exponent { return [d, e].join("")}
fractionalConstant "fractionalConstant"
= d1:digitSequence? "." d2:digitSequence { return [d1 ? d1.join("") : null, ".", d2.join("")].join(""); }
/ d:digitSequence "." { return d.join(""); }
exponent
= [eE] s:sign? d:digitSequence { return ['e', s, d.join("")].join("") }
sign
= [+-]
digitSequence
= digit+
digit
= [0-9]
wsp
= [\u0020\u0009\u000D\u000A]

View File

@@ -0,0 +1,53 @@
import { inverse } from './inverse'
import { transform } from './transform'
import { smoothMatrix } from './smoothMatrix'
/**
* Returns a matrix that transforms a triangle t1 into another triangle t2, or throws an exception if it is impossible.
* @param t1 {Point[]} Array of points containing the three points for the first triangle
* @param t2 {Point[]} Array of points containing the three points for the second triangle
* @returns {Matrix} Matrix which transforms t1 to t2
* @throws Exception if the matrix becomes not invertible
*/
export function fromTriangles (t1, t2) {
// point p = first point of the triangle
const px1 = t1[0].x != null ? t1[0].x : t1[0][0]
const py1 = t1[0].y != null ? t1[0].y : t1[0][1]
const px2 = t2[0].x != null ? t2[0].x : t2[0][0]
const py2 = t2[0].y != null ? t2[0].y : t2[0][1]
// point q = second point of the triangle
const qx1 = t1[1].x != null ? t1[1].x : t1[1][0]
const qy1 = t1[1].y != null ? t1[1].y : t1[1][1]
const qx2 = t2[1].x != null ? t2[1].x : t2[1][0]
const qy2 = t2[1].y != null ? t2[1].y : t2[1][1]
// point r = third point of the triangle
const rx1 = t1[2].x != null ? t1[2].x : t1[2][0]
const ry1 = t1[2].y != null ? t1[2].y : t1[2][1]
const rx2 = t2[2].x != null ? t2[2].x : t2[2][0]
const ry2 = t2[2].y != null ? t2[2].y : t2[2][1]
const r1 = {
a: px1 - rx1,
b: py1 - ry1,
c: qx1 - rx1,
d: qy1 - ry1,
e: rx1,
f: ry1
}
const r2 = {
a: px2 - rx2,
b: py2 - ry2,
c: qx2 - rx2,
d: qy2 - ry2,
e: rx2,
f: ry2
}
const inverseR1 = inverse(r1)
const affineMatrix = transform([r2, inverseR1])
// round the matrix elements to smooth the finite inversion
return smoothMatrix(affineMatrix)
}

View File

@@ -0,0 +1,14 @@
/**
* Identity matrix
* @returns {Matrix} Affine Matrix
*/
export function identity () {
return {
a: 1,
c: 0,
e: 0,
b: 0,
d: 1,
f: 0
}
}

View File

@@ -0,0 +1,20 @@
export * from './applyToPoint'
export * from './fromObject'
export * from './fromString'
export * from './identity'
export * from './inverse'
export * from './isAffineMatrix'
export * from './rotate'
export * from './scale'
export * from './shear'
export * from './skew'
export * from './toString'
export * from './transform'
export * from './translate'
export * from './fromTriangles'
export * from './smoothMatrix'
export * from './fromDefinition'
export * from './fromTransformAttribute'
export * from './decompose'
export * from './flip'
export * from './fromMovingPoints'

View File

@@ -0,0 +1,21 @@
/**
* Calculate a matrix that is the inverse of the provided matrix
* @param matrix {Matrix} Affine Matrix
* @returns {Matrix} Inverted Affine Matrix
*/
export function inverse (matrix) {
// http://www.wolframalpha.com/input/?i=Inverse+%5B%7B%7Ba,c,e%7D,%7Bb,d,f%7D,%7B0,0,1%7D%7D%5D
const { a, b, c, d, e, f } = matrix
const denom = a * d - b * c
return {
a: d / denom,
b: b / -denom,
c: c / -denom,
d: a / denom,
e: (d * e - c * f) / -denom,
f: (b * e - a * f) / denom
}
}

View File

@@ -0,0 +1,23 @@
import { isNumeric, isObject } from './utils'
/**
* Check if the object contain an affine matrix
* @param object {Object} Generic Plain Object
* @return {boolean} True if is an object and contains an affine matrix
*/
export function isAffineMatrix (object) {
return isObject(object) &&
'a' in object &&
isNumeric(object.a) &&
'b' in object &&
isNumeric(object.b) &&
'c' in object &&
isNumeric(object.c) &&
'd' in object &&
isNumeric(object.d) &&
'e' in object &&
isNumeric(object.e) &&
'f' in object &&
isNumeric(object.f)
}

View File

@@ -0,0 +1,44 @@
import { isUndefined } from './utils'
import { translate } from './translate'
import { transform } from './transform'
const { cos, sin, PI } = Math
/**
* Calculate a rotation matrix
* @param angle {number} Angle in radians
* @param [cx] {number} If (cx,cy) are supplied the rotate is about this point
* @param [cy] {number} If (cx,cy) are supplied the rotate is about this point
* @returns {Matrix} Affine Matrix
*/
export function rotate (angle, cx, cy) {
const cosAngle = cos(angle)
const sinAngle = sin(angle)
const rotationMatrix = {
a: cosAngle,
c: -sinAngle,
e: 0,
b: sinAngle,
d: cosAngle,
f: 0
}
if (isUndefined(cx) || isUndefined(cy)) {
return rotationMatrix
}
return transform([
translate(cx, cy),
rotationMatrix,
translate(-cx, -cy)
])
}
/**
* Calculate a rotation matrix with a DEG angle
* @param angle {number} Angle in degree
* @param [cx] {number} If (cx,cy) are supplied the rotate is about this point
* @param [cy] {number} If (cx,cy) are supplied the rotate is about this point
* @returns {Matrix} Affine Matrix
*/
export function rotateDEG (angle, cx = undefined, cy = undefined) {
return rotate(angle * PI / 180, cx, cy)
}

View File

@@ -0,0 +1,34 @@
import { isUndefined } from './utils'
import { translate } from './translate'
import { transform } from './transform'
/**
* Calculate a scaling matrix
* @param sx {number} Scaling on axis x
* @param [sy = sx] {number} Scaling on axis y (default sx)
* @param [cx] {number} If (cx,cy) are supplied the scaling is about this point
* @param [cy] {number} If (cx,cy) are supplied the scaling is about this point
* @returns {Matrix} Affine Matrix
*/
export function scale (sx, sy = undefined, cx = undefined, cy = undefined) {
if (isUndefined(sy)) sy = sx
const scaleMatrix = {
a: sx,
c: 0,
e: 0,
b: 0,
d: sy,
f: 0
}
if (isUndefined(cx) || isUndefined(cy)) {
return scaleMatrix
}
return transform([
translate(cx, cy),
scaleMatrix,
translate(-cx, -cy)
])
}

View File

@@ -0,0 +1,16 @@
/**
* Calculate a shear matrix
* @param shx {number} Shear on axis x
* @param shy {number} Shear on axis y
* @returns {Matrix} Affine Matrix
*/
export function shear (shx, shy) {
return {
a: 1,
c: shx,
e: 0,
b: shy,
d: 1,
f: 0
}
}

View File

@@ -0,0 +1,29 @@
// https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/skew
const { tan } = Math
/**
* Calculate a skew matrix
* @param ax {number} Skew on axis x
* @param ay {number} Skew on axis y
* @returns {Matrix} Affine Matrix
*/
export function skew (ax, ay) {
return {
a: 1,
c: tan(ax),
e: 0,
b: tan(ay),
d: 1,
f: 0
}
}
/**
* Calculate a skew matrix using DEG angles
* @param ax {number} Skew on axis x
* @param ay {number} Skew on axis y
* @returns {Matrix} Affine Matrix
*/
export function skewDEG (ax, ay) {
return skew(ax * Math.PI / 180, ay * Math.PI / 180)
}

View File

@@ -0,0 +1,16 @@
/**
* Rounds all elements of the given matrix using the given precision
* @param matrix {Matrix} An affine matrix to round
* @param [precision] {number} A precision to use for Math.round. Defaults to 10000000000 (meaning which rounds to the 10th digit after the comma).
* @returns {Matrix} The rounded Affine Matrix
*/
export function smoothMatrix (matrix, precision = 10000000000) {
return {
a: Math.round(matrix.a * precision) / precision,
b: Math.round(matrix.b * precision) / precision,
c: Math.round(matrix.c * precision) / precision,
d: Math.round(matrix.d * precision) / precision,
e: Math.round(matrix.e * precision) / precision,
f: Math.round(matrix.f * precision) / precision
}
}

View File

@@ -0,0 +1,26 @@
/**
* Serialize an affine matrix to a string that can be used with CSS or SVG
* @param matrix {Matrix} Affine Matrix
* @returns {string} String that contains an affine matrix formatted as matrix(a,b,c,d,e,f)
*/
export function toCSS (matrix) {
return toString(matrix)
}
/**
* Serialize an affine matrix to a string that can be used with CSS or SVG
* @param matrix {Matrix} Affine Matrix
* @returns {string} String that contains an affine matrix formatted as matrix(a,b,c,d,e,f)
*/
export function toSVG (matrix) {
return toString(matrix)
}
/**
* Serialize an affine matrix to a string that can be used with CSS or SVG
* @param matrix {Matrix} Affine Matrix
* @returns {string} String that contains an affine matrix formatted as matrix(a,b,c,d,e,f)
*/
export function toString (matrix) {
return `matrix(${matrix.a},${matrix.b},${matrix.c},${matrix.d},${matrix.e},${matrix.f})`
}

View File

@@ -0,0 +1,45 @@
/**
* Merge multiple matrices into one
* @param matrices {...Matrix | Matrix[]} Matrices listed as separate parameters or in an array
* @returns {Matrix} Affine Matrix
*/
export function transform (...matrices) {
matrices = Array.isArray(matrices[0]) ? matrices[0] : matrices
const multiply = (m1, m2) => {
return {
a: m1.a * m2.a + m1.c * m2.b,
c: m1.a * m2.c + m1.c * m2.d,
e: m1.a * m2.e + m1.c * m2.f + m1.e,
b: m1.b * m2.a + m1.d * m2.b,
d: m1.b * m2.c + m1.d * m2.d,
f: m1.b * m2.e + m1.d * m2.f + m1.f
}
}
switch (matrices.length) {
case 0:
throw new Error('no matrices provided')
case 1:
return matrices[0]
case 2:
return multiply(matrices[0], matrices[1])
default: {
const [m1, m2, ...rest] = matrices
const m = multiply(m1, m2)
return transform(m, ...rest)
}
}
}
/**
* Merge multiple matrices into one
* @param matrices {...Matrix | Matrix[]} Matrices listed as separate parameters or in an array
* @returns {Matrix} Affine Matrix
*/
export function compose (...matrices) {
return transform(...matrices)
}

View File

@@ -0,0 +1,343 @@
type PointObjectNotation = { x: number; y: number };
type PointArrayNotation = [number, number];
declare module 'transformation-matrix' {
type Matrix = {
a: number;
b: number;
c: number;
d: number;
e: number;
f: number;
};
type MatrixDescriptor =
| { type: 'matrix', a: number, b: number, c: number, d: number, e: number, f: number }
| { type: 'translate', tx: number, ty: number }
| { type: 'scale', sx: number, sy: number }
| { type: 'rotate', angle: number, sx: number, sy: number }
| { type: 'skewX', angle: number }
| { type: 'skewY', angle: number }
| { type: 'shear', shx: number, shy: number }
type Point = PointObjectNotation | PointArrayNotation;
export { Point, Matrix, MatrixDescriptor };
}
declare module 'transformation-matrix/applyToPoint' {
import { Point, Matrix } from 'transformation-matrix';
/** Calculate a point transformed with an affine matrix */
export function applyToPoint<P extends Point>(
matrix: Matrix,
point: P,
): P extends PointObjectNotation ? PointObjectNotation : PointArrayNotation;
/** Calculate an array of points transformed with an affine matrix */
export function applyToPoints<P extends Point>(
matrix: Matrix,
points: P[],
): P extends PointObjectNotation ? PointObjectNotation[] : PointArrayNotation[];
}
declare module 'transformation-matrix/fromString' {
import { Matrix } from 'transformation-matrix';
/** Parse a string matrix formatted as `matrix(a,b,c,d,e,f)` */
export function fromString(str: string): Matrix;
}
declare module 'transformation-matrix/fromObject' {
import { Point, Matrix } from 'transformation-matrix';
/**
* Extract an affine matrix from an object that contains a,b,c,d,e,f keys.
* Each value could be a float or a string that contains a float
*/
export function fromObject(object: {
a: string | number;
b: string | number;
c: string | number;
d: string | number;
e: string | number;
f: string | number;
}): Matrix;
}
declare module 'transformation-matrix/identity' {
import { Matrix } from 'transformation-matrix';
/** Identity matrix */
export function identity(): Matrix;
}
declare module 'transformation-matrix/inverse' {
import { Matrix } from 'transformation-matrix';
/** Calculate a matrix that is the inverse of the provided matrix */
export function inverse(matrix: Matrix): Matrix;
}
declare module 'transformation-matrix/isAffineMatrix' {
import { Matrix } from 'transformation-matrix';
/** Check if the object contain an affine matrix */
export function isAffineMatrix(obj: object): boolean;
}
declare module 'transformation-matrix/rotate' {
import { Matrix } from 'transformation-matrix';
/**
* Calculate a rotation matrix
* @param angle Angle in radians
* @param cx If (cx,cy) are supplied the rotate is about this point
* @param cy If (cx,cy) are supplied the rotate is about this point
*/
export function rotate(angle: number, cx?: number, cy?: number): Matrix;
/** Calculate a rotation matrix with a DEG angle
* @param angle Angle in degree
* @param cx If (cx,cy) are supplied the rotate is about this point
* @param cy If (cx,cy) are supplied the rotate is about this point*/
export function rotateDEG(angle: number, cx?: number, cy?: number): Matrix;
}
declare module 'transformation-matrix/scale' {
import { Matrix } from 'transformation-matrix';
/**
* Calculate a scaling matrix
* @param sx {number} Scaling on axis x
* @param [sy = sx] {number} Scaling on axis y (default `sx`)
* @param [cx] {number} If (cx,cy) are supplied the scale is relative to this point
* @param [cy] {number} If (cx,cy) are supplied the scale is relative to this point
* @returns {Matrix} Affine Matrix
*/
export function scale(sx: number, sy?: number, cx?: number, cy?: number): Matrix;
}
declare module 'transformation-matrix/shear' {
import { Matrix } from 'transformation-matrix';
/** Calculate a shear matrix */
export function shear(shx: number, shy: number): Matrix;
}
declare module 'transformation-matrix/skew' {
import { Matrix } from 'transformation-matrix';
/** Calculate a skew matrix */
export function skew(ax: number, ay: number): Matrix;
}
declare module 'transformation-matrix/toString' {
import { Matrix } from 'transformation-matrix';
/**
* Serialize the matrix to a string that can be used with CSS or SVG
* @returns {string} String that contains a matrix formatted as `matrix(a,b,c,d,e,f)`
*/
export function toSVG(matrix: Matrix): string;
/**
* Serialize the matrix to a string that can be used with CSS or SVG
* @returns {string} String that contains a matrix formatted as `matrix(a,b,c,d,e,f)`
*/
export function toCSS(matrix: Matrix): string;
/**
* Serialize the matrix to a string that can be used with CSS or SVG
* @returns {string} String that contains a matrix formatted as `matrix(a,b,c,d,e,f)`
*/
export function toString(matrix: Matrix): string;
}
declare module 'transformation-matrix/transform' {
import { Matrix } from 'transformation-matrix';
/** Merge multiple matrices into one */
export function transform(matrices: Matrix[]): Matrix;
export function transform(...matrices: Matrix[]): Matrix;
/** Merge multiple matrices into one */
export function compose(matrices: Matrix[]): Matrix;
export function compose(...matrices: Matrix[]): Matrix;
}
declare module 'transformation-matrix/translate' {
import { Matrix } from 'transformation-matrix';
/**
* Calculate a translate matrix
* @param tx Translation on axis x
* @param ty Translation on axis y (default `0`)
*/
export function translate(tx: number, ty?: number): Matrix;
}
declare module 'transformation-matrix/fromTriangles' {
import { Point, Matrix } from 'transformation-matrix';
/**
* Returns a matrix that transforms a triangle t1 into another triangle t2, or throws an exception if it is impossible.
* @param t1 an array of points containing the three points for the first triangle
* @param t2 an array of points containing the three points for the second triangle
* @returns Affine matrix which transforms t1 to t2
* @throws Exception if the matrix becomes not invertible
*/
export function fromTriangles(t1: Array<Point>, t2: Array<Point>): Matrix;
}
declare module 'transformation-matrix/smoothMatrix' {
import { Matrix } from 'transformation-matrix';
/**
* Rounds all elements of the given matrix using the given precision
* @param m a matrix to round
* @param precision precision to use for Math.round. Defaults to 10000000000 (meaning which rounds to the 10th digit after the comma).
* @returns the rounded matrix
*/
export function smoothMatrix(m: Matrix, precision?: number): Matrix;
}
declare module 'transformation-matrix/fromDefinition' {
import { Point, Matrix, MatrixDescriptor } from 'transformation-matrix';
/**
* Converts array of matrix descriptor to array of matrix
* @param definitionOrArrayOfDefinition {Object[]} Array of object describing the matrix
* @returns {Matrix[]} Array of matrix
*
* @example
* > fromDefinition([
* { type: 'matrix', a:1, b:2, c:3, d:4, e:5, f:6 },
* { type: 'translate', tx: 10, ty: 20 },
* { type: 'scale', sx: 2, sy: 4 },
* { type: 'rotate', angle: 90, sx: 50, sy: 25 },
* { type: 'skewX', angle: 45 },
* { type: 'skewY', angle: 45 },
* { type: 'shear', shx: 10, shy: 20}
* ])
*
* [
* { a: 1, b: 2, c: 3, d: 4, e: 5, f: 6 },
* { a: 1, c: 0, e: 10, b: 0, d: 1, f: 20 },
* { a: 2, c: 0, e: 0, b: 0, d: 4, f: 0 },
* { a: 6.123, c: -1, e: 0, b: 1, d: 6.123, f: 0 },
* { a: 1, c: 0.99.., e: 0, b: 0, d: 1, f: 0 },
* { a: 1, c: 0, e: 0, b: 0.99, d: 1, f: 0 },
* { a: 1, c: 10, e: 0, b: 20, d: 1, f: 0 }
* ]
**/
export function fromDefinition(definition: MatrixDescriptor): Matrix;
export function fromDefinition(
arrayOfDefinition: MatrixDescriptor[]
): Matrix[];
}
declare module 'transformation-matrix/fromTransformAttribute' {
import { MatrixDescriptor } from 'transformation-matrix';
/**
* Parser for SVG Trasform Attribute http://www.w3.org/TR/SVG/coords.html#TransformAttribute <br/>
* Warning: This should be considered BETA until it is released a stable version of pegjs.
* @param transformString {string} Transform string as defined by w3 Consortium
* @returns {MatrixDescriptor[]} Array of MatrixDescriptor
*
* @example
* > fromTransformAttribute('translate(-10,-10) scale(2,2) translate(10,10)')
* [
* { type: 'translate', tx: -10, ty: -10},
* { type: 'scale', sx: 2, sy: 2 },
* { type: 'translate', tx: 10, ty: 10}
* ]
*
* > compose(fromDefinition(fromTransformAttribute('translate(-10, -10) scale(10, 10)')))
* { a: 10, c: 0, e: -10, b: 0, d: 10, f: -10 }
*/
export function fromTransformAttribute(transformString: string): MatrixDescriptor[];
}
declare module 'transformation-matrix/decompose' {
import { Matrix } from 'transformation-matrix';
export interface Transform {
translate: {
tx: number,
ty: number
},
rotation: { angle: number },
scale: {
sx: number,
sy: number
}
}
/**
* Decompose a matrix into translation, scaling and rotation components, optionally
* take horizontal and vertical flip in to consideration.
* Note this function decomposes a matrix in rotation -> scaling -> translation order. I.e. for
* certain translation T {tx, ty}, rotation R and scaling S { sx, sy }, it's only true for:
* decomposeTSR(compose(T, S, R)) === { translate: T, rotation: R, scale: S }
* composing in a different order may yield a different decomposition result.
* @param matrix {Matrix} Affine Matrix
* @param flipX {boolean} Whether the matrix contains vertical flip, i.e. mirrors on x-axis
* @param flipY {boolean} Whether the matrix contains horizontal flip, i.e. mirrors on y-axis
* @returns {Transform} A transform object consisted by its translation, scaling
* and rotation components.
*/
export function decomposeTSR(matrix: Matrix, flipX?: boolean, flipY?: boolean): Transform;
}
declare module 'transformation-matrix/flip' {
import { Matrix } from 'transformation-matrix';
/**
* Tranformation matrix that mirrors on x-axis
*/
export function flipX(): Matrix;
/**
* Tranformation matrix that mirrors on y-axis
*/
export function flipY(): Matrix;
/**
* Tranformation matrix that mirrors on origin
*/
export function flipOrigin(): Matrix;
}
declare module 'transformation-matrix/fromMovingPoints' {
import { Point, Matrix } from 'transformation-matrix';
/**
* Calculate a transformation matrix from a point that starts from A to A' (e.g. desktop gesture)
*/
export function fromOneMovingPoint (startingPoint: Point, endingPoint: Point): Matrix;
/**
* Calculate a transformation matrix about two points that move from positions A and B to A' and B' (e.g. mobile gesture)
*/
export function fromTwoMovingPoints (startingPoint1: Point, startingPoint2: Point, endingPoint1: Point, endingPoint2: Point): Matrix;
}
declare module 'transformation-matrix' {
export * from 'transformation-matrix/applyToPoint';
export * from 'transformation-matrix/fromObject';
export * from 'transformation-matrix/fromString';
export * from 'transformation-matrix/identity';
export * from 'transformation-matrix/inverse';
export * from 'transformation-matrix/isAffineMatrix';
export * from 'transformation-matrix/rotate';
export * from 'transformation-matrix/scale';
export * from 'transformation-matrix/skew';
export * from 'transformation-matrix/shear';
export * from 'transformation-matrix/toString';
export * from 'transformation-matrix/transform';
export * from 'transformation-matrix/translate';
export * from 'transformation-matrix/fromTriangles';
export * from 'transformation-matrix/smoothMatrix';
export * from 'transformation-matrix/fromDefinition';
export * from 'transformation-matrix/fromTransformAttribute';
export * from 'transformation-matrix/decompose';
export * from 'transformation-matrix/flip';
export * from 'transformation-matrix/fromMovingPoints';
}

View File

@@ -0,0 +1,16 @@
/**
* Calculate a translate matrix
* @param tx {number} Translation on axis x
* @param [ty = 0] {number} Translation on axis y
* @returns {Matrix} Affine Matrix
*/
export function translate (tx, ty = 0) {
return {
a: 1,
c: 0,
e: tx,
b: 0,
d: 1,
f: ty
}
}

View File

@@ -0,0 +1,19 @@
export function isUndefined (val) {
return typeof val === 'undefined'
}
export function isNumeric (n) {
return typeof n === 'number' &&
!Number.isNaN(n) &&
Number.isFinite(n)
}
export function isObject (obj) {
return typeof obj === 'object' &&
obj !== null &&
!Array.isArray(obj)
}
export function matchesShape (obj, keys) {
return keys.every(key => key in obj)
}