@shopify/react-native-skia 2.2.7 → 2.2.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cpp/api/recorder/DrawingCtx.h +15 -0
- package/cpp/api/recorder/JsiRecorder.h +2 -1
- package/cpp/api/recorder/RNRecorder.h +2 -2
- package/cpp/api/recorder/Shaders.h +6 -5
- package/lib/commonjs/skia/types/Recorder.d.ts +1 -1
- package/lib/commonjs/skia/types/Recorder.js.map +1 -1
- package/lib/commonjs/sksg/Container.d.ts +2 -10
- package/lib/commonjs/sksg/Container.js +1 -78
- package/lib/commonjs/sksg/Container.js.map +1 -1
- package/lib/commonjs/sksg/Container.web.d.ts +14 -0
- package/lib/commonjs/sksg/Container.web.js +91 -0
- package/lib/commonjs/sksg/Container.web.js.map +1 -0
- package/lib/commonjs/sksg/Recorder/ReanimatedRecorder.d.ts +1 -1
- package/lib/commonjs/sksg/Recorder/ReanimatedRecorder.js +2 -2
- package/lib/commonjs/sksg/Recorder/ReanimatedRecorder.js.map +1 -1
- package/lib/commonjs/sksg/Recorder/Recorder.d.ts +1 -1
- package/lib/commonjs/sksg/Recorder/Recorder.js +3 -2
- package/lib/commonjs/sksg/Recorder/Recorder.js.map +1 -1
- package/lib/commonjs/sksg/Recorder/Visitor.js +2 -2
- package/lib/commonjs/sksg/Recorder/Visitor.js.map +1 -1
- package/lib/commonjs/sksg/Recorder/commands/Shaders.js +3 -3
- package/lib/commonjs/sksg/Recorder/commands/Shaders.js.map +1 -1
- package/lib/module/skia/types/Recorder.d.ts +1 -1
- package/lib/module/skia/types/Recorder.js.map +1 -1
- package/lib/module/sksg/Container.d.ts +2 -10
- package/lib/module/sksg/Container.js +2 -78
- package/lib/module/sksg/Container.js.map +1 -1
- package/lib/module/sksg/Container.web.d.ts +14 -0
- package/lib/module/sksg/Container.web.js +83 -0
- package/lib/module/sksg/Container.web.js.map +1 -0
- package/lib/module/sksg/Recorder/ReanimatedRecorder.d.ts +1 -1
- package/lib/module/sksg/Recorder/ReanimatedRecorder.js +2 -2
- package/lib/module/sksg/Recorder/ReanimatedRecorder.js.map +1 -1
- package/lib/module/sksg/Recorder/Recorder.d.ts +1 -1
- package/lib/module/sksg/Recorder/Recorder.js +3 -2
- package/lib/module/sksg/Recorder/Recorder.js.map +1 -1
- package/lib/module/sksg/Recorder/Visitor.js +2 -2
- package/lib/module/sksg/Recorder/Visitor.js.map +1 -1
- package/lib/module/sksg/Recorder/commands/Shaders.js +3 -3
- package/lib/module/sksg/Recorder/commands/Shaders.js.map +1 -1
- package/lib/typescript/lib/commonjs/sksg/Container.d.ts +1 -13
- package/lib/typescript/lib/commonjs/sksg/Container.web.d.ts +15 -0
- package/lib/typescript/lib/commonjs/sksg/Reconciler.d.ts +1 -17
- package/lib/typescript/lib/commonjs/sksg/Recorder/ReanimatedRecorder.d.ts +1 -1
- package/lib/typescript/lib/commonjs/sksg/Recorder/Recorder.d.ts +1 -1
- package/lib/typescript/lib/module/sksg/Container.d.ts +1 -14
- package/lib/typescript/lib/module/sksg/Container.web.d.ts +15 -0
- package/lib/typescript/lib/module/sksg/Reconciler.d.ts +1 -17
- package/lib/typescript/lib/module/sksg/Recorder/ReanimatedRecorder.d.ts +1 -1
- package/lib/typescript/lib/module/sksg/Recorder/Recorder.d.ts +1 -1
- package/lib/typescript/src/skia/types/Recorder.d.ts +1 -1
- package/lib/typescript/src/sksg/Container.d.ts +2 -10
- package/lib/typescript/src/sksg/Container.web.d.ts +14 -0
- package/lib/typescript/src/sksg/Recorder/ReanimatedRecorder.d.ts +1 -1
- package/lib/typescript/src/sksg/Recorder/Recorder.d.ts +1 -1
- package/package.json +1 -1
- package/src/renderer/__tests__/e2e/Shader.spec.tsx +93 -0
- package/src/skia/types/Recorder.ts +5 -1
- package/src/sksg/Container.ts +2 -81
- package/src/sksg/Container.web.ts +95 -0
- package/src/sksg/Recorder/ReanimatedRecorder.ts +6 -2
- package/src/sksg/Recorder/Recorder.ts +6 -2
- package/src/sksg/Recorder/Visitor.ts +2 -2
- package/src/sksg/Recorder/commands/Shaders.ts +8 -3
@@ -257,4 +257,97 @@ vec4 main(vec2 pos) {
|
|
257
257
|
);
|
258
258
|
checkImage(img, docPath("shaders/color.png"));
|
259
259
|
});
|
260
|
+
it("should display a mix of red and lightblue color", async () => {
|
261
|
+
const { Skia } = importSkia();
|
262
|
+
const colorSelection = Skia.RuntimeEffect.Make(`uniform shader child1;
|
263
|
+
uniform shader child2;
|
264
|
+
|
265
|
+
vec4 main(vec2 pos) {
|
266
|
+
vec4 c1 = child1.eval(pos);
|
267
|
+
vec4 c2 = child2.eval(pos);
|
268
|
+
return mix(c1, c2, 0.5);
|
269
|
+
}`)!;
|
270
|
+
const img = await surface.draw(
|
271
|
+
<Fill>
|
272
|
+
<Shader source={colorSelection}>
|
273
|
+
<ColorShader color="lightblue" />
|
274
|
+
<ColorShader color="red" />
|
275
|
+
</Shader>
|
276
|
+
</Fill>
|
277
|
+
);
|
278
|
+
checkImage(img, docPath("shaders/mixed-colors.png"));
|
279
|
+
});
|
280
|
+
it("should display a mix of red and lightblue from custom shaders", async () => {
|
281
|
+
const { Skia } = importSkia();
|
282
|
+
const colorSelection = Skia.RuntimeEffect.Make(`uniform shader child1;
|
283
|
+
uniform shader child2;
|
284
|
+
|
285
|
+
vec4 main(vec2 pos) {
|
286
|
+
vec4 c1 = child1.eval(pos);
|
287
|
+
vec4 c2 = child2.eval(pos);
|
288
|
+
return mix(c1, c2, 0.5);
|
289
|
+
}`)!;
|
290
|
+
expect(colorSelection).toBeDefined();
|
291
|
+
const colorShader = Skia.RuntimeEffect.Make(`
|
292
|
+
uniform vec4 color;
|
293
|
+
|
294
|
+
vec4 main(vec2 pos) {
|
295
|
+
return color;
|
296
|
+
}
|
297
|
+
`)!;
|
298
|
+
expect(colorShader).toBeDefined();
|
299
|
+
const img = await surface.draw(
|
300
|
+
<Fill>
|
301
|
+
<Shader source={colorSelection}>
|
302
|
+
<Shader
|
303
|
+
source={colorShader}
|
304
|
+
uniforms={{ color: Skia.Color("lightblue") }}
|
305
|
+
/>
|
306
|
+
<Shader
|
307
|
+
source={colorShader}
|
308
|
+
uniforms={{ color: Skia.Color("red") }}
|
309
|
+
/>
|
310
|
+
</Shader>
|
311
|
+
</Fill>
|
312
|
+
);
|
313
|
+
checkImage(img, docPath("shaders/mixed-colors.png"));
|
314
|
+
});
|
315
|
+
|
316
|
+
it("should display different results based on children order", async () => {
|
317
|
+
const { Skia } = importSkia();
|
318
|
+
const orderSensitiveShader = Skia.RuntimeEffect.Make(`uniform shader child1;
|
319
|
+
uniform shader child2;
|
320
|
+
|
321
|
+
vec4 main(vec2 pos) {
|
322
|
+
vec4 c1 = child1.eval(pos);
|
323
|
+
vec4 c2 = child2.eval(pos);
|
324
|
+
// Order-dependent: blend child2 over child1 with position-based alpha
|
325
|
+
float alpha = pos.x / 256.0;
|
326
|
+
return mix(c1, c2, alpha);
|
327
|
+
}`)!;
|
328
|
+
expect(orderSensitiveShader).toBeDefined();
|
329
|
+
const colorShader = Skia.RuntimeEffect.Make(`
|
330
|
+
uniform vec4 color;
|
331
|
+
|
332
|
+
vec4 main(vec2 pos) {
|
333
|
+
return color;
|
334
|
+
}
|
335
|
+
`)!;
|
336
|
+
expect(colorShader).toBeDefined();
|
337
|
+
const img = await surface.draw(
|
338
|
+
<Fill>
|
339
|
+
<Shader source={orderSensitiveShader}>
|
340
|
+
<Shader
|
341
|
+
source={colorShader}
|
342
|
+
uniforms={{ color: Skia.Color("blue") }}
|
343
|
+
/>
|
344
|
+
<Shader
|
345
|
+
source={colorShader}
|
346
|
+
uniforms={{ color: Skia.Color("yellow") }}
|
347
|
+
/>
|
348
|
+
</Shader>
|
349
|
+
</Fill>
|
350
|
+
);
|
351
|
+
checkImage(img, docPath("shaders/order-dependent.png"));
|
352
|
+
});
|
260
353
|
});
|
@@ -48,7 +48,11 @@ export interface BaseRecorder {
|
|
48
48
|
colorFilterType: NodeType,
|
49
49
|
props: AnimatedProps<unknown>
|
50
50
|
): void;
|
51
|
-
pushShader(
|
51
|
+
pushShader(
|
52
|
+
shaderType: NodeType,
|
53
|
+
props: AnimatedProps<unknown>,
|
54
|
+
children: number
|
55
|
+
): void;
|
52
56
|
pushBlurMaskFilter(props: AnimatedProps<BlurMaskFilterProps>): void;
|
53
57
|
composePathEffect(): void;
|
54
58
|
composeColorFilter(): void;
|
package/src/sksg/Container.ts
CHANGED
@@ -1,92 +1,13 @@
|
|
1
1
|
import type { SharedValue } from "react-native-reanimated";
|
2
2
|
|
3
|
-
import Rea from "../external/reanimated/ReanimatedProxy";
|
4
3
|
import type { Skia, SkSize } from "../skia/types";
|
5
|
-
import { HAS_REANIMATED_3 } from "../external/reanimated/renderHelpers";
|
6
4
|
|
7
|
-
import
|
8
|
-
import { Recorder } from "./Recorder/Recorder";
|
9
|
-
import { visit } from "./Recorder/Visitor";
|
10
|
-
import { replay } from "./Recorder/Player";
|
11
|
-
import { createDrawingContext } from "./Recorder/DrawingContext";
|
12
|
-
import { Container, StaticContainer } from "./StaticContainer";
|
13
|
-
|
14
|
-
const drawOnscreen = (
|
15
|
-
Skia: Skia,
|
16
|
-
nativeId: number,
|
17
|
-
recording: Recording,
|
18
|
-
onSize?: SharedValue<SkSize>
|
19
|
-
) => {
|
20
|
-
"worklet";
|
21
|
-
if (onSize) {
|
22
|
-
const size = SkiaViewApi.size(nativeId);
|
23
|
-
if (
|
24
|
-
size.width !== onSize.value.width ||
|
25
|
-
size.height !== onSize.value.height
|
26
|
-
) {
|
27
|
-
onSize.value = size;
|
28
|
-
}
|
29
|
-
}
|
30
|
-
const rec = Skia.PictureRecorder();
|
31
|
-
const canvas = rec.beginRecording();
|
32
|
-
//const start = performance.now();
|
33
|
-
|
34
|
-
const ctx = createDrawingContext(Skia, recording.paintPool, canvas);
|
35
|
-
replay(ctx, recording.commands);
|
36
|
-
const picture = rec.finishRecordingAsPicture();
|
37
|
-
//const end = performance.now();
|
38
|
-
//console.log("Recording time: ", end - start);
|
39
|
-
SkiaViewApi.setJsiProperty(nativeId, "picture", picture);
|
40
|
-
};
|
41
|
-
|
42
|
-
class ReanimatedContainer extends Container {
|
43
|
-
private mapperId: number | null = null;
|
44
|
-
|
45
|
-
constructor(
|
46
|
-
Skia: Skia,
|
47
|
-
private nativeId: number,
|
48
|
-
private onSize?: SharedValue<SkSize>
|
49
|
-
) {
|
50
|
-
super(Skia);
|
51
|
-
}
|
52
|
-
|
53
|
-
redraw() {
|
54
|
-
if (this.mapperId !== null) {
|
55
|
-
Rea.stopMapper(this.mapperId);
|
56
|
-
}
|
57
|
-
if (this.unmounted) {
|
58
|
-
return;
|
59
|
-
}
|
60
|
-
const recorder = new Recorder();
|
61
|
-
visit(recorder, this.root);
|
62
|
-
const record = recorder.getRecording();
|
63
|
-
const { animationValues } = record;
|
64
|
-
this.recording = {
|
65
|
-
commands: record.commands,
|
66
|
-
paintPool: record.paintPool,
|
67
|
-
};
|
68
|
-
const { nativeId, Skia, recording } = this;
|
69
|
-
if (animationValues.size > 0) {
|
70
|
-
this.mapperId = Rea.startMapper(() => {
|
71
|
-
"worklet";
|
72
|
-
drawOnscreen(Skia, nativeId, recording!);
|
73
|
-
}, Array.from(animationValues));
|
74
|
-
}
|
75
|
-
Rea.runOnUI((onSize?: SharedValue<SkSize>) => {
|
76
|
-
"worklet";
|
77
|
-
drawOnscreen(Skia, nativeId, recording!, onSize);
|
78
|
-
})(this.onSize);
|
79
|
-
}
|
80
|
-
}
|
5
|
+
import { StaticContainer } from "./StaticContainer";
|
81
6
|
|
82
7
|
export const createContainer = (
|
83
8
|
Skia: Skia,
|
84
9
|
nativeId: number,
|
85
10
|
onSize?: SharedValue<SkSize>
|
86
11
|
) => {
|
87
|
-
|
88
|
-
return new ReanimatedContainer(Skia, nativeId, onSize);
|
89
|
-
} else {
|
90
|
-
return new StaticContainer(Skia, nativeId);
|
91
|
-
}
|
12
|
+
return new StaticContainer(Skia, nativeId, onSize);
|
92
13
|
};
|
@@ -0,0 +1,95 @@
|
|
1
|
+
import type { SharedValue } from "react-native-reanimated";
|
2
|
+
|
3
|
+
import Rea from "../external/reanimated/ReanimatedProxy";
|
4
|
+
import type { Skia, SkSize } from "../skia/types";
|
5
|
+
import { HAS_REANIMATED_3 } from "../external/reanimated/renderHelpers";
|
6
|
+
|
7
|
+
import type { Recording } from "./Recorder/Recorder";
|
8
|
+
import { Recorder } from "./Recorder/Recorder";
|
9
|
+
import { visit } from "./Recorder/Visitor";
|
10
|
+
import { replay } from "./Recorder/Player";
|
11
|
+
import { createDrawingContext } from "./Recorder/DrawingContext";
|
12
|
+
import { Container, StaticContainer } from "./StaticContainer";
|
13
|
+
|
14
|
+
import "../skia/NativeSetup";
|
15
|
+
import "../views/api";
|
16
|
+
|
17
|
+
const drawOnscreen = (
|
18
|
+
Skia: Skia,
|
19
|
+
nativeId: number,
|
20
|
+
recording: Recording,
|
21
|
+
onSize?: SharedValue<SkSize>
|
22
|
+
) => {
|
23
|
+
"worklet";
|
24
|
+
if (onSize) {
|
25
|
+
const size = SkiaViewApi.size(nativeId);
|
26
|
+
if (
|
27
|
+
size.width !== onSize.value.width ||
|
28
|
+
size.height !== onSize.value.height
|
29
|
+
) {
|
30
|
+
onSize.value = size;
|
31
|
+
}
|
32
|
+
}
|
33
|
+
const rec = Skia.PictureRecorder();
|
34
|
+
const canvas = rec.beginRecording();
|
35
|
+
//const start = performance.now();
|
36
|
+
|
37
|
+
const ctx = createDrawingContext(Skia, recording.paintPool, canvas);
|
38
|
+
replay(ctx, recording.commands);
|
39
|
+
const picture = rec.finishRecordingAsPicture();
|
40
|
+
//const end = performance.now();
|
41
|
+
//console.log("Recording time: ", end - start);
|
42
|
+
SkiaViewApi.setJsiProperty(nativeId, "picture", picture);
|
43
|
+
};
|
44
|
+
|
45
|
+
class ReanimatedContainer extends Container {
|
46
|
+
private mapperId: number | null = null;
|
47
|
+
|
48
|
+
constructor(
|
49
|
+
Skia: Skia,
|
50
|
+
private nativeId: number,
|
51
|
+
private onSize?: SharedValue<SkSize>
|
52
|
+
) {
|
53
|
+
super(Skia);
|
54
|
+
}
|
55
|
+
|
56
|
+
redraw() {
|
57
|
+
if (this.mapperId !== null) {
|
58
|
+
Rea.stopMapper(this.mapperId);
|
59
|
+
}
|
60
|
+
if (this.unmounted) {
|
61
|
+
return;
|
62
|
+
}
|
63
|
+
const recorder = new Recorder();
|
64
|
+
visit(recorder, this.root);
|
65
|
+
const record = recorder.getRecording();
|
66
|
+
const { animationValues } = record;
|
67
|
+
this.recording = {
|
68
|
+
commands: record.commands,
|
69
|
+
paintPool: record.paintPool,
|
70
|
+
};
|
71
|
+
const { nativeId, Skia, recording } = this;
|
72
|
+
if (animationValues.size > 0) {
|
73
|
+
this.mapperId = Rea.startMapper(() => {
|
74
|
+
"worklet";
|
75
|
+
drawOnscreen(Skia, nativeId, recording!);
|
76
|
+
}, Array.from(animationValues));
|
77
|
+
}
|
78
|
+
Rea.runOnUI((onSize?: SharedValue<SkSize>) => {
|
79
|
+
"worklet";
|
80
|
+
drawOnscreen(Skia, nativeId, recording!, onSize);
|
81
|
+
})(this.onSize);
|
82
|
+
}
|
83
|
+
}
|
84
|
+
|
85
|
+
export const createContainer = (
|
86
|
+
Skia: Skia,
|
87
|
+
nativeId: number,
|
88
|
+
onSize?: SharedValue<SkSize>
|
89
|
+
) => {
|
90
|
+
if (HAS_REANIMATED_3 && nativeId !== -1) {
|
91
|
+
return new ReanimatedContainer(Skia, nativeId, onSize);
|
92
|
+
} else {
|
93
|
+
return new StaticContainer(Skia, nativeId);
|
94
|
+
}
|
95
|
+
};
|
@@ -111,9 +111,13 @@ export class ReanimatedRecorder implements BaseRecorder {
|
|
111
111
|
this.recorder.pushColorFilter(colorFilterType, props);
|
112
112
|
}
|
113
113
|
|
114
|
-
pushShader(
|
114
|
+
pushShader(
|
115
|
+
shaderType: NodeType,
|
116
|
+
props: AnimatedProps<unknown>,
|
117
|
+
children: number
|
118
|
+
): void {
|
115
119
|
this.processAnimationValues(props);
|
116
|
-
this.recorder.pushShader(shaderType, props);
|
120
|
+
this.recorder.pushShader(shaderType, props, children);
|
117
121
|
}
|
118
122
|
|
119
123
|
pushBlurMaskFilter(props: AnimatedProps<BlurMaskFilterProps>): void {
|
@@ -152,11 +152,15 @@ export class Recorder implements BaseRecorder {
|
|
152
152
|
});
|
153
153
|
}
|
154
154
|
|
155
|
-
pushShader(
|
155
|
+
pushShader(
|
156
|
+
shaderType: NodeType,
|
157
|
+
props: AnimatedProps<unknown>,
|
158
|
+
children: number
|
159
|
+
) {
|
156
160
|
if (!isShader(shaderType) && !(shaderType === NodeType.Blend)) {
|
157
161
|
throw new Error("Invalid color filter type: " + shaderType);
|
158
162
|
}
|
159
|
-
this.add({ type: CommandType.PushShader, shaderType, props });
|
163
|
+
this.add({ type: CommandType.PushShader, shaderType, props, children });
|
160
164
|
}
|
161
165
|
|
162
166
|
pushBlurMaskFilter(props: AnimatedProps<BlurMaskFilterProps>) {
|
@@ -160,7 +160,7 @@ const pushImageFilters = (
|
|
160
160
|
if (isImageFilter(imageFilter.type)) {
|
161
161
|
recorder.pushImageFilter(imageFilter.type, imageFilter.props);
|
162
162
|
} else if (isShader(imageFilter.type)) {
|
163
|
-
recorder.pushShader(imageFilter.type, imageFilter.props);
|
163
|
+
recorder.pushShader(imageFilter.type, imageFilter.props, 0);
|
164
164
|
}
|
165
165
|
const needsComposition =
|
166
166
|
imageFilter.type !== NodeType.BlendImageFilter &&
|
@@ -176,7 +176,7 @@ const pushShaders = (recorder: BaseRecorder, shaders: Node<any>[]) => {
|
|
176
176
|
if (shader.children.length > 0) {
|
177
177
|
pushShaders(recorder, shader.children);
|
178
178
|
}
|
179
|
-
recorder.pushShader(shader.type, shader.props);
|
179
|
+
recorder.pushShader(shader.type, shader.props, shader.children.length);
|
180
180
|
});
|
181
181
|
};
|
182
182
|
|
@@ -34,14 +34,18 @@ import type { Command } from "../Core";
|
|
34
34
|
import { CommandType } from "../Core";
|
35
35
|
import type { DrawingContext } from "../DrawingContext";
|
36
36
|
|
37
|
-
const declareShader = (
|
37
|
+
const declareShader = (
|
38
|
+
ctx: DrawingContext,
|
39
|
+
props: ShaderProps,
|
40
|
+
children: number
|
41
|
+
) => {
|
38
42
|
"worklet";
|
39
43
|
const { source, uniforms, ...transform } = props;
|
40
44
|
const m3 = ctx.Skia.Matrix();
|
41
45
|
processTransformProps(m3, transform);
|
42
46
|
const shader = source.makeShaderWithChildren(
|
43
47
|
processUniforms(source, uniforms),
|
44
|
-
ctx.shaders.splice(0,
|
48
|
+
ctx.shaders.splice(0, children),
|
45
49
|
m3
|
46
50
|
);
|
47
51
|
ctx.shaders.push(shader);
|
@@ -257,6 +261,7 @@ interface PushShader<T extends keyof Props>
|
|
257
261
|
extends Command<CommandType.PushShader> {
|
258
262
|
shaderType: T;
|
259
263
|
props: Props[T];
|
264
|
+
children: number;
|
260
265
|
}
|
261
266
|
|
262
267
|
const isShader = <T extends keyof Props>(
|
@@ -273,7 +278,7 @@ export const pushShader = (
|
|
273
278
|
) => {
|
274
279
|
"worklet";
|
275
280
|
if (isShader(command, NodeType.Shader)) {
|
276
|
-
declareShader(ctx, command.props);
|
281
|
+
declareShader(ctx, command.props, command.children);
|
277
282
|
} else if (isShader(command, NodeType.ImageShader)) {
|
278
283
|
declareImageShader(ctx, command.props);
|
279
284
|
} else if (isShader(command, NodeType.ColorShader)) {
|