Documentation Index
Fetch the complete documentation index at: https://mintlify.com/shopify/react-native-skia/llms.txt
Use this file to discover all available pages before exploring further.
Pictures provide an immediate-mode drawing API for React Native Skia. While Skia normally works in retained mode, Pictures allow you to execute a variable number of drawing commands, making them perfect for dynamic content that changes frame by frame.
What are Pictures?
A Picture is an immutable recording of drawing operations. Once created, it can be drawn multiple times on any canvas without re-recording. This is ideal when:
- The number of drawing commands varies per frame
- You need to execute loops of drawing operations
- You want to cache complex drawings for reuse
- You’re creating procedural graphics
Picture Component
The Picture component renders a recorded picture on the canvas.
Props
| Name | Type | Description |
|---|
| picture | SkPicture | The picture to render |
Basic Example
Here’s a simple example that draws a circle:
import { Canvas, Picture, Skia } from "@shopify/react-native-skia";
import { useMemo } from "react";
const SimplePicture = () => {
const picture = useMemo(() => {
const recorder = Skia.PictureRecorder();
const canvas = recorder.beginRecording(
Skia.XYWHRect(0, 0, 256, 256)
);
const paint = Skia.Paint();
paint.setColor(Skia.Color("lightblue"));
canvas.drawCircle(128, 128, 100, paint);
return recorder.finishRecordingAsPicture();
}, []);
return (
<Canvas style={{ width: 256, height: 256 }}>
<Picture picture={picture} />
</Canvas>
);
};
Animated Circle Trail
This example demonstrates the power of Pictures for variable drawing commands:
import { Canvas, Picture, Skia } from "@shopify/react-native-skia";
import {
useDerivedValue,
useSharedValue,
withRepeat,
withTiming,
} from "react-native-reanimated";
import { useEffect } from "react";
const size = 256;
const n = 20;
const paint = Skia.Paint();
const recorder = Skia.PictureRecorder();
const CircleTrail = () => {
const progress = useSharedValue(0);
useEffect(() => {
progress.value = withRepeat(
withTiming(1, { duration: 3000 }),
-1,
true
);
}, []);
const picture = useDerivedValue(() => {
"worklet";
const canvas = recorder.beginRecording(
Skia.XYWHRect(0, 0, size, size)
);
const numberOfCircles = Math.floor(progress.value * n);
for (let i = 0; i < numberOfCircles; i++) {
const alpha = ((i + 1) / n) * 255;
const r = ((i + 1) / n) * (size / 2);
paint.setColor(
Skia.Color(`rgba(0, 122, 255, ${alpha / 255})`)
);
canvas.drawCircle(size / 2, size / 2, r, paint);
}
return recorder.finishRecordingAsPicture();
});
return (
<Canvas style={{ width: size, height: size }}>
<Picture picture={picture} />
</Canvas>
);
};
Recording Pictures
Creating a Recorder
import { Skia } from "@shopify/react-native-skia";
const recorder = Skia.PictureRecorder();
Begin Recording
Define the recording bounds:
const canvas = recorder.beginRecording(
Skia.XYWHRect(0, 0, width, height)
);
Draw on Canvas
Use the canvas to record drawing commands:
const paint = Skia.Paint();
paint.setColor(Skia.Color("red"));
canvas.drawRect({ x: 0, y: 0, width: 100, height: 100 }, paint);
canvas.drawCircle(50, 50, 25, paint);
Finish Recording
const picture = recorder.finishRecordingAsPicture();
Complex Example: CMY Circles
import { Canvas, Picture, Skia, Group, Paint, Blur } from "@shopify/react-native-skia";
import { useMemo } from "react";
import { BlendMode } from "@shopify/react-native-skia";
const CMYCircles = () => {
const picture = useMemo(() => {
const recorder = Skia.PictureRecorder();
const size = 256;
const canvas = recorder.beginRecording(
Skia.XYWHRect(0, 0, size, size)
);
const r = 0.33 * size;
const paint = Skia.Paint();
paint.setBlendMode(BlendMode.Multiply);
// Cyan circle
paint.setColor(Skia.Color("cyan"));
canvas.drawCircle(r, r, r, paint);
// Magenta circle
paint.setColor(Skia.Color("magenta"));
canvas.drawCircle(size - r, r, r, paint);
// Yellow circle
paint.setColor(Skia.Color("yellow"));
canvas.drawCircle(size / 2, size - r, r, paint);
return recorder.finishRecordingAsPicture();
}, []);
return (
<Canvas style={{ width: 256, height: 256 }}>
<Picture picture={picture} />
</Canvas>
);
};
Applying Effects
Pictures don’t follow standard painting rules. Use the layer property to apply effects:
import { Canvas, Picture, Group, Paint, Blur, Skia } from "@shopify/react-native-skia";
import { useMemo } from "react";
const BlurredPicture = () => {
const picture = useMemo(() => {
const recorder = Skia.PictureRecorder();
const canvas = recorder.beginRecording(
Skia.XYWHRect(0, 0, 256, 256)
);
const paint = Skia.Paint();
paint.setColor(Skia.Color("lightblue"));
canvas.drawCircle(128, 128, 100, paint);
return recorder.finishRecordingAsPicture();
}, []);
return (
<Canvas style={{ width: 256, height: 256 }}>
<Group layer={<Paint><Blur blur={10} /></Paint>}>
<Picture picture={picture} />
</Group>
</Canvas>
);
};
With Color Filters
import { Canvas, Picture, Group, Paint, ColorMatrix, Skia } from "@shopify/react-native-skia";
<Group
layer={
<Paint>
<ColorMatrix
matrix={[
0, 0, 0, 0, 1,
0, 1, 0, 0, 0,
0, 0, 1, 0, 0,
0, 0, 0, 1, 0,
]}
/>
</Paint>
}
>
<Picture picture={picture} />
</Group>
Serialization
Pictures can be serialized for debugging or storage:
import { Canvas, Picture, Skia } from "@shopify/react-native-skia";
import { useMemo } from "react";
const SerializedPicture = () => {
// Create picture
const picture = useMemo(() => {
const recorder = Skia.PictureRecorder();
const canvas = recorder.beginRecording(
Skia.XYWHRect(0, 0, 100, 100)
);
const paint = Skia.Paint();
paint.setColor(Skia.Color("pink"));
canvas.drawRect({ x: 0, y: 0, width: 100, height: 100 }, paint);
const circlePaint = Skia.Paint();
circlePaint.setColor(Skia.Color("orange"));
canvas.drawCircle(50, 50, 50, circlePaint);
return recorder.finishRecordingAsPicture();
}, []);
// Serialize
const serialized = useMemo(
() => picture.serialize(),
[picture]
);
// Deserialize
const copyOfPicture = useMemo(
() => serialized ? Skia.Picture.MakePicture(serialized) : null,
[serialized]
);
return (
<Canvas style={{ width: 256, height: 256 }}>
<Picture picture={picture} />
<Group transform={[{ translateX: 150 }]}>
{copyOfPicture && <Picture picture={copyOfPicture} />}
</Group>
</Canvas>
);
};
Note: Serialized pictures are only compatible with the same Skia version. Use with the Skia debugger.
Picture Instance Methods
makeShader
Convert a picture into a shader:
import { TileMode, FilterMode } from "@shopify/react-native-skia";
const shader = picture.makeShader(
TileMode.Repeat,
TileMode.Repeat,
FilterMode.Linear
);
serialize
Export as bytes:
const bytes = picture.serialize();
// Returns Uint8Array or null
Advanced Patterns
Procedural Pattern
const pattern = useMemo(() => {
const recorder = Skia.PictureRecorder();
const canvas = recorder.beginRecording(
Skia.XYWHRect(0, 0, 256, 256)
);
const paint = Skia.Paint();
for (let x = 0; x < 256; x += 20) {
for (let y = 0; y < 256; y += 20) {
const hue = ((x + y) / 512) * 360;
paint.setColor(Skia.Color(`hsl(${hue}, 70%, 60%)`));
canvas.drawRect(
{ x, y, width: 18, height: 18 },
paint
);
}
}
return recorder.finishRecordingAsPicture();
}, []);
Reusable Picture
const StarPicture = ({ picture }) => (
<>
<Picture picture={picture} />
<Group transform={[{ translateX: 100 }]}>
<Picture picture={picture} />
</Group>
<Group transform={[{ translateY: 100 }]}>
<Picture picture={picture} />
</Group>
</>
);
- Cache pictures with
useMemo when content doesn’t change
- Reuse Paint instances across recordings
- Keep recorder instances outside render when possible
- Use
useDerivedValue for animated pictures
- Serialize expensive pictures for debugging
When to Use Pictures
Use Pictures when:
- Drawing a variable number of shapes
- Creating procedural graphics
- Caching complex drawing operations
- Building custom components with loops
Use Components when:
- Drawing a fixed number of shapes
- Using declarative syntax
- Leveraging React Native Skia’s animation system
- Building standard UI elements
See Also