Skip to content

Commit bec1321

Browse files
committed
Add more tests
1 parent 75c38ab commit bec1321

File tree

7 files changed

+182
-34
lines changed

7 files changed

+182
-34
lines changed

package/cpp/api/JsiSkMatrix.h

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -27,17 +27,33 @@ class JsiSkMatrix : public JsiSkWrappingSharedPtrHostObject<SkMatrix> {
2727
static SkMatrix getMatrix(jsi::Runtime &runtime, const jsi::Value &value) {
2828
const auto &object = value.asObject(runtime);
2929
const auto &array = object.asArray(runtime);
30-
auto scaleX = array.getValueAtIndex(runtime, 0).asNumber();
31-
auto skewX = array.getValueAtIndex(runtime, 1).asNumber();
32-
auto transX = array.getValueAtIndex(runtime, 2).asNumber();
33-
auto skewY = array.getValueAtIndex(runtime, 3).asNumber();
34-
auto scaleY = array.getValueAtIndex(runtime, 4).asNumber();
35-
auto transY = array.getValueAtIndex(runtime, 5).asNumber();
36-
auto pers0 = array.getValueAtIndex(runtime, 6).asNumber();
37-
auto pers1 = array.getValueAtIndex(runtime, 7).asNumber();
38-
auto pers2 = array.getValueAtIndex(runtime, 8).asNumber();
39-
return SkMatrix::MakeAll(scaleX, skewX, transX, skewY, scaleY, transY,
40-
pers0, pers1, pers2);
30+
if (array.size(runtime) == 9) {
31+
auto scaleX = array.getValueAtIndex(runtime, 0).asNumber();
32+
auto skewX = array.getValueAtIndex(runtime, 1).asNumber();
33+
auto transX = array.getValueAtIndex(runtime, 2).asNumber();
34+
auto skewY = array.getValueAtIndex(runtime, 3).asNumber();
35+
auto scaleY = array.getValueAtIndex(runtime, 4).asNumber();
36+
auto transY = array.getValueAtIndex(runtime, 5).asNumber();
37+
auto pers0 = array.getValueAtIndex(runtime, 6).asNumber();
38+
auto pers1 = array.getValueAtIndex(runtime, 7).asNumber();
39+
auto pers2 = array.getValueAtIndex(runtime, 8).asNumber();
40+
return SkMatrix::MakeAll(scaleX, skewX, transX, skewY, scaleY, transY,
41+
pers0, pers1, pers2);
42+
} else if (array.size(runtime) == 16) {
43+
auto m11 = array.getValueAtIndex(runtime, 0).asNumber();
44+
auto m12 = array.getValueAtIndex(runtime, 1).asNumber();
45+
auto m14 = array.getValueAtIndex(runtime, 3).asNumber();
46+
auto m21 = array.getValueAtIndex(runtime, 4).asNumber();
47+
auto m22 = array.getValueAtIndex(runtime, 5).asNumber();
48+
auto m24 = array.getValueAtIndex(runtime, 7).asNumber();
49+
auto m41 = array.getValueAtIndex(runtime, 12).asNumber();
50+
auto m42 = array.getValueAtIndex(runtime, 13).asNumber();
51+
auto m44 = array.getValueAtIndex(runtime, 15).asNumber();
52+
return SkMatrix::MakeAll(m11, m12, m14, m21, m22, m24, m41, m42, m44);
53+
}
54+
throw jsi::JSError(runtime,
55+
"Expected array of length 9 or 16 for matrix, got " +
56+
std::to_string(array.size(runtime)));
4157
}
4258

4359
JSI_HOST_FUNCTION(concat) {

package/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@
9494
"eslint-config-react-native-wcandillon": "3.10.2",
9595
"eslint-plugin-reanimated": "2.0.0",
9696
"jest": "29.6.4",
97+
"jest-diff": "^29.7.0",
9798
"merge-dirs": "^0.2.1",
9899
"pixelmatch": "^5.3.0",
99100
"pngjs": "^6.0.0",

package/src/__tests__/setup.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import fs from "fs";
33

44
import { PNG } from "pngjs";
55
import pixelmatch from "pixelmatch";
6+
import { diff } from "jest-diff";
67

78
import type { SkSurface, SkImage } from "../skia/types";
89

@@ -93,3 +94,34 @@ export const checkImage = (
9394
}
9495
return 0;
9596
};
97+
98+
expect.extend({
99+
toBeApproximatelyEqual(_received, _argument, tolerance = 0.1) {
100+
const received =
101+
Array.isArray(_received) || _received instanceof Float32Array
102+
? _received
103+
: [_received];
104+
const argument =
105+
Array.isArray(_argument) || _received instanceof Float32Array
106+
? _argument
107+
: [_argument];
108+
if (received.length !== argument.length) {
109+
return { pass: false, message: () => "Arrays have different lengths" };
110+
}
111+
for (let i = 0; i < received.length; i++) {
112+
if (
113+
isNaN(argument[i]) ||
114+
isNaN(received[i]) ||
115+
Math.abs(received[i] - argument[i]) > tolerance
116+
) {
117+
const diffString = diff(received, argument);
118+
return {
119+
pass: false,
120+
message: () => `Element at index ${i} differ more than ${tolerance}:
121+
${diffString}`,
122+
};
123+
}
124+
}
125+
return { pass: true, message: () => "Arrays are approximately equal" };
126+
},
127+
});

package/src/renderer/__tests__/e2e/Matrix4.spec.tsx

Lines changed: 92 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,23 @@ import React from "react";
33
import { checkImage } from "../../../__tests__/setup";
44
import { Group, Rect } from "../../components";
55
import { surface } from "../setup";
6-
import { processTransform3d } from "../../../skia/types";
6+
import {
7+
multiply4,
8+
perspective,
9+
processTransform3d,
10+
rotateX,
11+
translate,
12+
} from "../../../skia/types";
713

814
/**
915
* @worklet
1016
*/
11-
export const toMatrix3 = (m: number[]) => {
17+
export const toCKMatrix3 = (m: number[]) => {
1218
"worklet";
1319
return [m[0], m[1], m[3], m[4], m[5], m[7], m[12], m[13], m[15]];
1420
};
1521

16-
const perspective = (d: number) => [
22+
const ckPerspective = (d: number) => [
1723
1,
1824
0,
1925
0,
@@ -37,7 +43,7 @@ const concat = (a: number[], b: number[]) => CanvasKit.M44.multiply(a, b);
3743
describe("Matrix4", () => {
3844
it("should be a row major matix", () => {
3945
const m4 = CanvasKit.M44.identity();
40-
expect(processTransform3d([])).toEqual(toMatrix3(m4));
46+
expect(processTransform3d([])).toEqual(toCKMatrix3(m4));
4147
});
4248
it("should match identity matrix", () => {
4349
const m4 = CanvasKit.M44.identity();
@@ -48,7 +54,7 @@ describe("Matrix4", () => {
4854
{ translateX: -256 / 2 },
4955
{ translateY: -256 / 2 },
5056
])
51-
).toEqual(toMatrix3(m4));
57+
).toEqual(toCKMatrix3(m4));
5258
});
5359
it("Identity should match identity matrix", () => {
5460
const m4 = CanvasKit.M44.identity();
@@ -59,12 +65,12 @@ describe("Matrix4", () => {
5965
{ translateX: -256 / 2 },
6066
{ translateY: -256 / 2 },
6167
])
62-
).toEqual(toMatrix3(m4));
68+
).toEqual(toCKMatrix3(m4));
6369
});
6470
it("should match perspective matrix", () => {
6571
let m4 = CanvasKit.M44.identity();
6672
m4 = concat(m4, CanvasKit.M44.translated([256 / 2, 256 / 2, 0]));
67-
m4 = concat(m4, perspective(300));
73+
m4 = concat(m4, ckPerspective(300));
6874
m4 = concat(m4, CanvasKit.M44.translated([-256 / 2, -256 / 2, 0]));
6975
expect(
7076
processTransform3d([
@@ -74,15 +80,15 @@ describe("Matrix4", () => {
7480
{ translateX: -256 / 2 },
7581
{ translateY: -256 / 2 },
7682
])
77-
).toEqual(toMatrix3(m4));
83+
).toEqual(toCKMatrix3(m4));
7884
});
7985
it("should match rotation matrix (1)", () => {
8086
let m4 = CanvasKit.M44.identity();
8187
m4 = concat(m4, CanvasKit.M44.rotated([1, 0, 0], 1));
82-
expect(processTransform3d([{ rotateX: 1 }])).toEqual(toMatrix3(m4));
88+
expect(processTransform3d([{ rotateX: 1 }])).toEqual(toCKMatrix3(m4));
8389
m4 = CanvasKit.M44.identity();
8490
m4 = concat(m4, CanvasKit.M44.rotated([0, 1, 0], Math.PI));
85-
expect(processTransform3d([{ rotateY: Math.PI }])).toEqual(toMatrix3(m4));
91+
expect(processTransform3d([{ rotateY: Math.PI }])).toEqual(toCKMatrix3(m4));
8692
});
8793
it("should match rotation matrix (2)", () => {
8894
let m4 = CanvasKit.M44.identity();
@@ -97,7 +103,7 @@ describe("Matrix4", () => {
97103
{ translateX: -256 / 2 },
98104
{ translateY: -256 / 2 },
99105
])
100-
).toEqual(toMatrix3(m4));
106+
).toEqual(toCKMatrix3(m4));
101107
});
102108
it("Should do a perspective transformation (1)", async () => {
103109
const { width, height } = surface;
@@ -149,4 +155,79 @@ describe("Matrix4", () => {
149155
);
150156
checkImage(image, "snapshots/matrix4/perspective.png");
151157
});
158+
it("Should do a perspective transformation (4)", async () => {
159+
const { width, height } = surface;
160+
const pad = 32;
161+
const rct = {
162+
x: pad,
163+
y: pad,
164+
width: width - pad * 2,
165+
height: height - pad * 2,
166+
};
167+
let matrix = translate(width / 2, height / 2);
168+
matrix = multiply4(matrix, perspective(300));
169+
matrix = multiply4(matrix, rotateX(1));
170+
matrix = multiply4(matrix, translate(-width / 2, -height / 2));
171+
const image = await surface.draw(
172+
<Group>
173+
<Rect rect={rct} color="magenta" />
174+
<Rect rect={rct} color="cyan" opacity={0.5} matrix={matrix} />
175+
</Group>
176+
);
177+
checkImage(image, "snapshots/matrix4/perspective.png");
178+
});
179+
it("Should do a perspective transformation (5)", async () => {
180+
const { width, height } = surface;
181+
const pad = 32;
182+
const rct = {
183+
x: pad,
184+
y: pad,
185+
width: width - pad * 2,
186+
height: height - pad * 2,
187+
};
188+
let matrix = translate(width / 2, height / 2);
189+
matrix = multiply4(matrix, perspective(300));
190+
matrix = multiply4(matrix, rotateX(1));
191+
matrix = multiply4(matrix, translate(-width / 2, -height / 2));
192+
const image = await surface.draw(
193+
<Group>
194+
<Rect rect={rct} color="magenta" />
195+
<Rect rect={rct} color="cyan" opacity={0.5} matrix={matrix} />
196+
</Group>
197+
);
198+
checkImage(image, "snapshots/matrix4/perspective.png");
199+
});
200+
it("concat() should accept 4x4 matrices (1)", async () => {
201+
const result = await surface.eval((Skia) => {
202+
const m3 = Skia.Matrix();
203+
return m3.get();
204+
});
205+
expect(result).toEqual([1, 0, 0, 0, 1, 0, 0, 0, 1]);
206+
});
207+
it("concat() should accept 4x4 matrices (2)", async () => {
208+
const { width, height } = surface;
209+
let matrix = translate(width / 2, height / 2);
210+
matrix = multiply4(matrix, perspective(300));
211+
matrix = multiply4(matrix, rotateX(1));
212+
matrix = multiply4(matrix, translate(-width / 2, -height / 2));
213+
214+
const result = await surface.eval(
215+
(Skia, ctx) => {
216+
const m3 = Skia.Matrix();
217+
m3.concat(ctx.matrix);
218+
return m3.get();
219+
},
220+
{ matrix }
221+
);
222+
// eslint-disable-next-line @typescript-eslint/no-explicit-any, jest/valid-expect
223+
(expect(result) as any).toBeApproximatelyEqual(
224+
processTransform3d([
225+
{ translate: [width / 2, height / 2] },
226+
{ perspective: 300 },
227+
{ rotateX: 1 },
228+
{ translate: [-width / 2, -height / 2] },
229+
]),
230+
0.1
231+
);
232+
});
152233
});

package/src/skia/types/Matrix.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import type { SkJSIInstance } from "./JsiInstance";
22
import type { SkCanvas } from "./Canvas";
3-
import type { Transforms3d } from "./Matrix4";
3+
import type { Matrix3, Matrix4, Transforms3d } from "./Matrix4";
44
import { processTransform3d } from "./Matrix4";
55

66
export const isMatrix = (obj: unknown): obj is SkMatrix =>
77
obj !== null && (obj as SkJSIInstance<string>).__typename__ === "Matrix";
88

99
export interface SkMatrix extends SkJSIInstance<"Matrix"> {
10-
concat: (matrix: SkMatrix | number[]) => SkMatrix;
10+
concat: (matrix: SkMatrix | Matrix4 | Matrix3 | number[]) => SkMatrix;
1111
translate: (x: number, y: number) => SkMatrix;
1212
scale: (x: number, y?: number) => SkMatrix;
1313
skew: (x: number, y: number) => SkMatrix;

package/src/skia/types/Matrix4.ts

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export type Matrix3 = readonly [
1212
number,
1313
number,
1414
number,
15-
number,
15+
number
1616
];
1717

1818
export type Matrix4 = readonly [
@@ -240,28 +240,37 @@ export const scale = (
240240
return m4;
241241
};
242242

243+
const rotateAxis = (axis: Vec3, angle: number, p?: Point) => {
244+
"worklet";
245+
const result = rotate(axis, angle);
246+
if (p) {
247+
return pivot(result, p);
248+
}
249+
return result;
250+
};
251+
243252
/**
244253
* @worklet
245254
*/
246-
export const rotateZ = (value: number, p: Point) => {
255+
export const rotateZ = (value: number, p?: Point) => {
247256
"worklet";
248-
return pivot(rotate([0, 0, 1], value), p);
257+
return rotateAxis([0, 0, 1], value, p);
249258
};
250259

251260
/**
252261
* @worklet
253262
*/
254-
export const rotateX = (value: number, p: Point) => {
263+
export const rotateX = (value: number, p?: Point) => {
255264
"worklet";
256-
return pivot(rotate([1, 0, 0], value), p);
265+
return rotateAxis([1, 0, 0], value, p);
257266
};
258267

259268
/**
260269
* @worklet
261270
*/
262-
export const rotateY = (value: number, p: Point) => {
271+
export const rotateY = (value: number, p?: Point) => {
263272
"worklet";
264-
return pivot(rotate([0, 1, 0], value), p);
273+
return rotateAxis([0, 1, 0], value, p);
265274
};
266275

267276
/**

package/src/skia/web/JsiSkMatrix.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
import type { CanvasKit, Matrix3x3 } from "canvaskit-wasm";
22

3-
import type { SkMatrix } from "../types";
3+
import { toMatrix3, type Matrix3, type Matrix4, type SkMatrix } from "../types";
44

55
import { HostObject } from "./Host";
66

7+
const isMatrixHostObject = (
8+
obj: SkMatrix | number[] | Matrix4 | Matrix3
9+
): obj is SkMatrix => !Array.isArray(obj);
10+
711
export class JsiSkMatrix
812
extends HostObject<Matrix3x3, "Matrix">
913
implements SkMatrix
@@ -24,9 +28,14 @@ export class JsiSkMatrix
2428
// Do nothing - the matrix is represenetd by a Float32Array
2529
};
2630

27-
concat(matrix: SkMatrix | number[]) {
31+
concat(matrix: SkMatrix | number[] | Matrix4 | Matrix3) {
2832
this.preMultiply(
29-
Array.isArray(matrix) ? matrix : JsiSkMatrix.fromValue(matrix)
33+
// eslint-disable-next-line no-nested-ternary
34+
isMatrixHostObject(matrix)
35+
? JsiSkMatrix.fromValue(matrix)
36+
: matrix.length === 16
37+
? toMatrix3(matrix as Matrix4)
38+
: [...matrix]
3039
);
3140
return this;
3241
}

0 commit comments

Comments
 (0)