@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.
Files changed (64) hide show
  1. package/cpp/api/recorder/DrawingCtx.h +15 -0
  2. package/cpp/api/recorder/JsiRecorder.h +2 -1
  3. package/cpp/api/recorder/RNRecorder.h +2 -2
  4. package/cpp/api/recorder/Shaders.h +6 -5
  5. package/lib/commonjs/skia/types/Recorder.d.ts +1 -1
  6. package/lib/commonjs/skia/types/Recorder.js.map +1 -1
  7. package/lib/commonjs/sksg/Container.d.ts +2 -10
  8. package/lib/commonjs/sksg/Container.js +1 -78
  9. package/lib/commonjs/sksg/Container.js.map +1 -1
  10. package/lib/commonjs/sksg/Container.web.d.ts +14 -0
  11. package/lib/commonjs/sksg/Container.web.js +91 -0
  12. package/lib/commonjs/sksg/Container.web.js.map +1 -0
  13. package/lib/commonjs/sksg/Recorder/ReanimatedRecorder.d.ts +1 -1
  14. package/lib/commonjs/sksg/Recorder/ReanimatedRecorder.js +2 -2
  15. package/lib/commonjs/sksg/Recorder/ReanimatedRecorder.js.map +1 -1
  16. package/lib/commonjs/sksg/Recorder/Recorder.d.ts +1 -1
  17. package/lib/commonjs/sksg/Recorder/Recorder.js +3 -2
  18. package/lib/commonjs/sksg/Recorder/Recorder.js.map +1 -1
  19. package/lib/commonjs/sksg/Recorder/Visitor.js +2 -2
  20. package/lib/commonjs/sksg/Recorder/Visitor.js.map +1 -1
  21. package/lib/commonjs/sksg/Recorder/commands/Shaders.js +3 -3
  22. package/lib/commonjs/sksg/Recorder/commands/Shaders.js.map +1 -1
  23. package/lib/module/skia/types/Recorder.d.ts +1 -1
  24. package/lib/module/skia/types/Recorder.js.map +1 -1
  25. package/lib/module/sksg/Container.d.ts +2 -10
  26. package/lib/module/sksg/Container.js +2 -78
  27. package/lib/module/sksg/Container.js.map +1 -1
  28. package/lib/module/sksg/Container.web.d.ts +14 -0
  29. package/lib/module/sksg/Container.web.js +83 -0
  30. package/lib/module/sksg/Container.web.js.map +1 -0
  31. package/lib/module/sksg/Recorder/ReanimatedRecorder.d.ts +1 -1
  32. package/lib/module/sksg/Recorder/ReanimatedRecorder.js +2 -2
  33. package/lib/module/sksg/Recorder/ReanimatedRecorder.js.map +1 -1
  34. package/lib/module/sksg/Recorder/Recorder.d.ts +1 -1
  35. package/lib/module/sksg/Recorder/Recorder.js +3 -2
  36. package/lib/module/sksg/Recorder/Recorder.js.map +1 -1
  37. package/lib/module/sksg/Recorder/Visitor.js +2 -2
  38. package/lib/module/sksg/Recorder/Visitor.js.map +1 -1
  39. package/lib/module/sksg/Recorder/commands/Shaders.js +3 -3
  40. package/lib/module/sksg/Recorder/commands/Shaders.js.map +1 -1
  41. package/lib/typescript/lib/commonjs/sksg/Container.d.ts +1 -13
  42. package/lib/typescript/lib/commonjs/sksg/Container.web.d.ts +15 -0
  43. package/lib/typescript/lib/commonjs/sksg/Reconciler.d.ts +1 -17
  44. package/lib/typescript/lib/commonjs/sksg/Recorder/ReanimatedRecorder.d.ts +1 -1
  45. package/lib/typescript/lib/commonjs/sksg/Recorder/Recorder.d.ts +1 -1
  46. package/lib/typescript/lib/module/sksg/Container.d.ts +1 -14
  47. package/lib/typescript/lib/module/sksg/Container.web.d.ts +15 -0
  48. package/lib/typescript/lib/module/sksg/Reconciler.d.ts +1 -17
  49. package/lib/typescript/lib/module/sksg/Recorder/ReanimatedRecorder.d.ts +1 -1
  50. package/lib/typescript/lib/module/sksg/Recorder/Recorder.d.ts +1 -1
  51. package/lib/typescript/src/skia/types/Recorder.d.ts +1 -1
  52. package/lib/typescript/src/sksg/Container.d.ts +2 -10
  53. package/lib/typescript/src/sksg/Container.web.d.ts +14 -0
  54. package/lib/typescript/src/sksg/Recorder/ReanimatedRecorder.d.ts +1 -1
  55. package/lib/typescript/src/sksg/Recorder/Recorder.d.ts +1 -1
  56. package/package.json +1 -1
  57. package/src/renderer/__tests__/e2e/Shader.spec.tsx +93 -0
  58. package/src/skia/types/Recorder.ts +5 -1
  59. package/src/sksg/Container.ts +2 -81
  60. package/src/sksg/Container.web.ts +95 -0
  61. package/src/sksg/Recorder/ReanimatedRecorder.ts +6 -2
  62. package/src/sksg/Recorder/Recorder.ts +6 -2
  63. package/src/sksg/Recorder/Visitor.ts +2 -2
  64. 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(shaderType: NodeType, props: AnimatedProps<unknown>): void;
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;
@@ -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 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
- 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
- if (HAS_REANIMATED_3 && nativeId !== -1) {
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(shaderType: NodeType, props: AnimatedProps<unknown>): void {
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(shaderType: NodeType, props: AnimatedProps<unknown>) {
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 = (ctx: DrawingContext, props: ShaderProps) => {
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, ctx.shaders.length),
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)) {