diff --git a/CHANGELOG.md b/CHANGELOG.md index 4c8df5306a9..0be4ecfc020 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ # Changelog ## [next] + +- fix(util): restore old composeMatrix code for performances improvement [#9851](https://github.com/fabricjs/fabric.js/pull/9851) - fix(Control): corner coords definition order [#9884](https://github.com/fabricjs/fabric.js/pull/9884) - fix(Polyline): safeguard points arg from options [#9855](https://github.com/fabricjs/fabric.js/pull/9855) - feat(IText): Adjust cursor blinking for better feedback [#9823](https://github.com/fabricjs/fabric.js/pull/9823) diff --git a/src/benchmarks/calcTransformMatrix.mjs b/src/benchmarks/calcTransformMatrix.mjs new file mode 100644 index 00000000000..600a4e688d9 --- /dev/null +++ b/src/benchmarks/calcTransformMatrix.mjs @@ -0,0 +1,104 @@ +import { util } from '../../dist/index.mjs'; + +// perf(composeMatrix): 25% improv by restoring v5 implementation #9851 + +// OLD CODE FOR REFERENCE AND IMPLEMENTATION TEST + +const util2 = { ...util }; + +util2.calcDimensionsMatrix = ({ + scaleX = 1, + scaleY = 1, + flipX = false, + flipY = false, + skewX = 0, + skewY = 0, +}) => { + return util2.multiplyTransformMatrixArray( + [ + util2.createScaleMatrix( + flipX ? -scaleX : scaleX, + flipY ? -scaleY : scaleY + ), + skewX && util2.createSkewXMatrix(skewX), + skewY && util2.createSkewYMatrix(skewY), + ], + true + ); +}; + +util2.composeMatrix = ({ + translateX = 0, + translateY = 0, + angle = 0, + ...otherOptions +}) => { + return util2.multiplyTransformMatrixArray([ + util2.createTranslateMatrix(translateX, translateY), + angle && util2.createRotateMatrix({ angle }), + util2.calcDimensionsMatrix(otherOptions), + ]); +}; + +// END OF OLD CODE + +const benchmark = (callback) => { + const start = Date.now(); + callback(); + return Date.now() - start; +}; + +const optionsComplex = { + skewY: 10, + skewX: 4, + scaleX: 5, + scaleY: 4, + angle: 20, + flipY: true, +}; + +const simpleCase = { + scaleX: 5, + scaleY: 4, + angle: 20, +}; + +const complexOld = benchmark(() => { + for (let i = 0; i < 1_000_000; i++) { + util2.composeMatrix(optionsComplex); + } +}); + +const complexNew = benchmark(() => { + for (let i = 0; i < 1_000_000; i++) { + util.composeMatrix(optionsComplex); + } +}); + +console.log({ complexOld, complexNew }); + +const simpleOld = benchmark(() => { + for (let i = 0; i < 1_000_000; i++) { + util2.composeMatrix(simpleCase); + } +}); + +const simpleNew = benchmark(() => { + for (let i = 0; i < 1_000_000; i++) { + util.composeMatrix(simpleCase); + } +}); + +console.log({ simpleOld, simpleNew }); + +/** + * On Node 18.17 + * { complexOld: 749, complexNew: 627 } + * { simpleOld: 537, simpleNew: 374 } + */ + +/** + * After removing the spread operator + * { complexOld: 761, complexNew: 446 } + * { simpleOld: 526, simpleNew: 271 } + */ diff --git a/src/util/misc/matrix.ts b/src/util/misc/matrix.ts index 3164ab0fc77..ac5d1c109c3 100644 --- a/src/util/misc/matrix.ts +++ b/src/util/misc/matrix.ts @@ -277,20 +277,24 @@ export const calcDimensionsMatrix = ({ skewX = 0 as TDegree, skewY = 0 as TDegree, }: TScaleMatrixArgs) => { - return multiplyTransformMatrixArray( - [ - createScaleMatrix(flipX ? -scaleX : scaleX, flipY ? -scaleY : scaleY), - skewX && createSkewXMatrix(skewX), - skewY && createSkewYMatrix(skewY), - ], - true + let matrix = createScaleMatrix( + flipX ? -scaleX : scaleX, + flipY ? -scaleY : scaleY ); + if (skewX) { + matrix = multiplyTransformMatrices(matrix, createSkewXMatrix(skewX), true); + } + if (skewY) { + matrix = multiplyTransformMatrices(matrix, createSkewYMatrix(skewY), true); + } + return matrix; }; /** * Returns a transform matrix starting from an object of the same kind of * the one returned from qrDecompose, useful also if you want to calculate some * transformations from an object that is not enlived yet + * Before changing this function look at: src/benchmarks/calcTransformMatrix.mjs * @param {Object} options * @param {Number} [options.angle] * @param {Number} [options.scaleX] @@ -303,15 +307,15 @@ export const calcDimensionsMatrix = ({ * @param {Number} [options.translateY] * @return {Number[]} transform matrix */ -export const composeMatrix = ({ - translateX = 0, - translateY = 0, - angle = 0 as TDegree, - ...otherOptions -}: TComposeMatrixArgs): TMat2D => { - return multiplyTransformMatrixArray([ - createTranslateMatrix(translateX, translateY), - angle && createRotateMatrix({ angle }), - calcDimensionsMatrix(otherOptions), - ]); +export const composeMatrix = (options: TComposeMatrixArgs): TMat2D => { + const { translateX = 0, translateY = 0, angle = 0 as TDegree } = options; + let matrix = createTranslateMatrix(translateX, translateY); + if (angle) { + matrix = multiplyTransformMatrices(matrix, createRotateMatrix({ angle })); + } + const scaleMatrix = calcDimensionsMatrix(options); + if (!isIdentityMatrix(scaleMatrix)) { + matrix = multiplyTransformMatrices(matrix, scaleMatrix); + } + return matrix; };