Skip to content

Commit

Permalink
Add translate, translateSelf, scale and scaleSelf to DOMMatrix (#83)
Browse files Browse the repository at this point in the history
* feat: add scale and translate to dommatrix

* docs: update readme

* refactor: prettier fixes

* feat(dommatrix): added translateSelf

* refactor: converted translate to use translateSelf

* feat(dommatrix): added scaleSelf

* refactor: use scaleSelf in scale

* refactor: cleanup unused functions

* refactor: change the matrix multiplication

The calculation function now deals with arrays instead of the properties directly

* refactor: prettier
  • Loading branch information
YonatanKra authored Oct 20, 2021
1 parent 315d56b commit 38ffb8e
Show file tree
Hide file tree
Showing 6 changed files with 274 additions and 35 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ canvas.toDataURL.mockReturnValueOnce(
- [@jtenner](https://github.com/jtenner)
- [@evanoc0](https://github.com/evanoc0)
- [@lekha](https://github.com/lekha)
- [@yonatankra](https://github.com/yonatankra)

## License

Expand Down
171 changes: 155 additions & 16 deletions __tests__/classes/DOMMatrix.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,22 +32,7 @@ describe('DOMMatrix class', () => {

it('should accept an array of 16 length', () => {
const matrix = new DOMMatrix([
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
13,
14,
15,
16,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
]);
expect(matrix).toBeInstanceOf(DOMMatrix);
});
Expand Down Expand Up @@ -160,4 +145,158 @@ describe('DOMMatrix class', () => {
matrix.m42 = 2;
expect(matrix.f).toBe(2);
});

describe(`translate`, function () {
it(`should return a new DOMMatrix instance`, function () {
const matrix = new DOMMatrix();
const translatedMatrix = matrix.translate(100, 100);
expect(translatedMatrix instanceof DOMMatrix).toBeTruthy();
expect(translatedMatrix === matrix).toBeFalsy();
});

it(`should apply 2d changes`, function () {
const x = 100;
const y = 200;
const matrix = new DOMMatrix([4, 5, 1, 3, 10, 9]);
const expectedMatrix = new DOMMatrix([
4, 5, 0, 0, 1, 3, 0, 0, 0, 0, 1, 0, 610, 1109, 0, 1,
]);
const translatedMatrix = matrix.translate(x, y);
expect(translatedMatrix.toFloat32Array()).toEqual(
expectedMatrix.toFloat32Array()
);
expect(translatedMatrix.is2D).toEqual(true);
});

it(`should apply 3d changes`, function () {
const x = 100;
const y = 200;
const z = 300;
const matrix = new DOMMatrix();
const expectedMatrix = new DOMMatrix([
1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 100, 200, 300, 1,
]);
const translatedMatrix = matrix.translate(x, y, z);
expect(translatedMatrix).toEqual(expectedMatrix);
expect(translatedMatrix.is2D).toEqual(false);
});
});

describe(`scale`, function () {
it(`should return a new DOMMatrix instance`, function () {
const matrix = new DOMMatrix();
const scaledMatrix = matrix.scale(0.5, 0.7);
expect(scaledMatrix instanceof DOMMatrix).toBeTruthy();
expect(scaledMatrix === matrix).toBeFalsy();
});

it(`should apply 2d changes`, function () {
const scaleX = 0.75;
const scaleY = 0.5;
const matrix = new DOMMatrix([7, 8, 9, 20, 4, 7]);
const expectedMatrix = new DOMMatrix([5.25, 6, 4.5, 10, 4, 7]);
const scaledMatrix = matrix.scale(scaleX, scaleY);
expect(scaledMatrix).toEqual(expectedMatrix);
});

it(`should apply 3d changes`, function () {
const scaleX = 0.65;
const scaleY = 0.55;
const scaleZ = 0.9;
const matrix = new DOMMatrix();
const expectedMatrix = new DOMMatrix([
0.65, 0, 0, 0, 0, 0.55, 0, 0, 0, 0, 0.9, 0, 0, 0, 0, 1,
]);
const scaledMatrix = matrix.scale(scaleX, scaleY, scaleZ);
expect(scaledMatrix).toEqual(expectedMatrix);
});
});

describe(`translateSelf`, function () {
it(`should return dot product of a 2d matrix multiplication`, function () {
const matrix2D = new DOMMatrix([1, 2, 3, 4, 5, 6]);
const tx = 2,
ty = 3;
const expectedMatrix = new DOMMatrix([1, 2, 3, 4, 16, 22]);
matrix2D.translateSelf(tx, ty);
expect(matrix2D.toFloat32Array()).toEqual(
expectedMatrix.toFloat32Array()
);
expect(matrix2D.is2D).toEqual(true);
});

it(`should return do product of a 3d matrix`, function () {
const matrix3D = new DOMMatrix([
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
]);
const tx = 2,
ty = 3,
tz = 4;
const expectedMatrix = new DOMMatrix([
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 66, 76, 86, 96,
]);
matrix3D.translateSelf(tx, ty, tz);
expect(matrix3D.toFloat32Array()).toEqual(
expectedMatrix.toFloat32Array()
);
expect(matrix3D.is2D).toEqual(false);
});

it(`should convert 2d matrix to 3d matrix when sent tz`, function () {
const matrix2D = new DOMMatrix([1, 2, 3, 4, 5, 6]);
const tx = 2,
ty = 3,
tz = 4;
const expectedMatrix = new DOMMatrix([
1, 2, 0, 0, 3, 4, 0, 0, 0, 0, 1, 0, 16, 22, 4, 1,
]);
matrix2D.translateSelf(tx, ty, tz);
expect(matrix2D.toFloat32Array()).toEqual(
expectedMatrix.toFloat32Array()
);
expect(matrix2D.is2D).toEqual(false);
});
});

describe(`scaleSelf`, function () {
it(`should return dot product of a 2d translated matrix multiplication`, function () {
const matrix2D = new DOMMatrix([1, 2, 3, 4, 5, 6]);
const scaleX = 2,
scaleY = 3;
const expectedMatrix = new DOMMatrix([2, 4, 9, 12, 5, 6]);
matrix2D.scaleSelf(scaleX, scaleY);
expect(matrix2D).toEqual(expectedMatrix);
});

it(`should return dot product of a 3d translated matrix multiplication`, function () {
const matrix3D = new DOMMatrix([
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
]);
const sx = 2,
sy = 3,
sz = 4;
const expectedMatrix = new DOMMatrix([
2, 4, 6, 8, 15, 18, 21, 24, 36, 40, 44, 48, 13, 14, 15, 16,
]);
matrix3D.scaleSelf(sx, sy, sz);
expect(matrix3D).toEqual(expectedMatrix);
});

it(`should return dot product of a 3d translated matrix multiplication with origin`, function () {
const matrix3D = new DOMMatrix([
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
]);
const sx = 2,
sy = 3,
sz = 4,
ox = 5,
oy = 6,
oz = 7;
const expectedMatrix = new DOMMatrix([
2, 4, 6, 8, 15, 18, 21, 24, 36, 40, 44, 48, -241, -278, -315, -352,
]);
matrix3D.scaleSelf(sx, sy, sz, ox, oy, oz);
expect(matrix3D).toEqual(expectedMatrix);
});
});
});
1 change: 0 additions & 1 deletion __tests__/classes/Path2D.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,5 +72,4 @@ describe('Path2D', () => {
expect(path1._path[2]).toBe(path2._path[0]);
expect(path1._path[3]).toBe(path2._path[1]);
});

});
22 changes: 6 additions & 16 deletions src/classes/CanvasRenderingContext2D.js
Original file line number Diff line number Diff line change
Expand Up @@ -184,16 +184,8 @@ export default class CanvasRenderingContext2D {
}

addHitRegion(options = {}) {
const {
path,
fillRule,
id,
parentID,
cursor,
control,
label,
role,
} = options;
const { path, fillRule, id, parentID, cursor, control, label, role } =
options;
if (!path && !id)
throw new DOMException(
'ConstraintError',
Expand Down Expand Up @@ -1724,9 +1716,8 @@ export default class CanvasRenderingContext2D {
if (typeof value === 'string') {
try {
const result = new MooColor(value);
value = this._shadowColorStack[this._stackIndex] = serializeColor(
result
);
value = this._shadowColorStack[this._stackIndex] =
serializeColor(result);
} catch (e) {
return;
}
Expand Down Expand Up @@ -1829,9 +1820,8 @@ export default class CanvasRenderingContext2D {
try {
const result = new MooColor(value);
valid = true;
value = this._strokeStyleStack[this._stackIndex] = serializeColor(
result
);
value = this._strokeStyleStack[this._stackIndex] =
serializeColor(result);
} catch (e) {
return;
}
Expand Down
111 changes: 111 additions & 0 deletions src/classes/DOMMatrix.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,27 @@
function sumMultipleOfMatricesCells(matrix1Array, matrix2Array, { i, j }) {
let sum = 0;
for (let k = 0; k < 4; k++) {
const matrix1Index = j - 1 + k * 4;
const matrix2Index = (i - 1) * 4 + k;
sum += matrix1Array[matrix1Index] * matrix2Array[matrix2Index];
}
return sum;
}

function multiplyMatrices(leftMatrix, rightMatrix) {
const leftMatrixArray = leftMatrix.toFloat64Array();
const rightMatrixArray = rightMatrix.toFloat64Array();
for (let i = 1; i <= 4; i++) {
for (let j = 1; j <= 4; j++) {
leftMatrix[`m${i}${j}`] = sumMultipleOfMatricesCells(
leftMatrixArray,
rightMatrixArray,
{ i, j }
);
}
}
}

export default class DOMMatrix {
_is2D = true;
m11 = 1.0;
Expand Down Expand Up @@ -182,4 +206,91 @@ export default class DOMMatrix {
this.m44,
]);
}

translateSelf(x, y, z) {
const tx = Number(x),
ty = Number(y),
tz = isNaN(Number(z)) ? 0 : Number(z);

const translationMatrix = new DOMMatrix();
translationMatrix.m41 = tx;
translationMatrix.m42 = ty;
translationMatrix.m43 = tz;

multiplyMatrices(this, translationMatrix);

if (tz) {
this._is2D = false;
}
return this;
}

translate(x, y, z) {
let translatedMatrix;
if (this.is2D) {
translatedMatrix = new DOMMatrix([
this.a,
this.b,
this.c,
this.d,
this.e,
this.f,
]);
} else {
translatedMatrix = new DOMMatrix(this.toFloat32Array());
}

return translatedMatrix.translateSelf(x, y, z);
}

scaleSelf(scaleX, scaleY, scaleZ, originX, originY, originZ) {
const sx = Number(scaleX),
sy = isNaN(Number(scaleY)) ? sx : Number(scaleY),
sz = isNaN(Number(scaleZ)) ? 1 : Number(scaleZ);

const ox = isNaN(Number(originX)) ? 0 : Number(originX),
oy = isNaN(Number(originY)) ? 0 : Number(originY),
oz = isNaN(Number(originZ)) ? 0 : Number(originZ);

this.translateSelf(ox, oy, oz);

const scaleMatrix = new DOMMatrix();
scaleMatrix.m11 = sx;
scaleMatrix.m22 = sy;
scaleMatrix.m33 = sz;

multiplyMatrices(this, scaleMatrix);

this.translateSelf(-ox, -oy, -oz);

if (Math.abs(sz) !== 1) {
this._is2D = false;
}
return this;
}

scale(scaleX, scaleY, scaleZ, originX, originY, originZ) {
let scaledMatrix;
if (this.is2D) {
scaledMatrix = new DOMMatrix([
this.a,
this.b,
this.c,
this.d,
this.e,
this.f,
]);
} else {
scaledMatrix = new DOMMatrix(this.toFloat32Array());
}

return scaledMatrix.scaleSelf(
scaleX,
scaleY,
scaleZ,
originX,
originY,
originZ
);
}
}
3 changes: 1 addition & 2 deletions src/classes/Path2D.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ export default class Path2D {
throw new TypeError(
"Failed to execute 'addPath' on 'Path2D': parameter 1 is not of type 'Path2D'."
);
for (let i = 0; i < path._path.length; i++)
this._path.push(path._path[i]);
for (let i = 0; i < path._path.length; i++) this._path.push(path._path[i]);
}
}

0 comments on commit 38ffb8e

Please sign in to comment.