Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add translate, translateSelf, scale and scaleSelf to DOMMatrix #83

Merged
merged 10 commits into from
Oct 20, 2021
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();
jtenner marked this conversation as resolved.
Show resolved Hide resolved
});

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);
jtenner marked this conversation as resolved.
Show resolved Hide resolved
});

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 {
jtenner marked this conversation as resolved.
Show resolved Hide resolved
_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]);
}
}