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.
Surfaces provide a drawing context where you can render graphics offscreen. This is useful for creating complex effects, caching renders, or generating images programmatically.
Creating Surfaces
import { Skia } from "@shopify/react-native-skia";
// Create a surface
const surface = Skia.Surface.Make(300, 300);
if (surface) {
const canvas = surface.getCanvas();
// Draw on the canvas
const paint = Skia.Paint();
paint.setColor(Skia.Color("blue"));
canvas.drawCircle(150, 150, 100, paint);
// Get the result as an image
const image = surface.makeImageSnapshot();
// Use the image...
}
Surface Methods
Returns the canvas for drawing on the surfaceconst canvas = surface.getCanvas();
makeImageSnapshot
(bounds?: SkRect) => SkImage
Captures the surface content as an image. Optionally specify a rectangle to capture only a portion.const fullImage = surface.makeImageSnapshot();
const partialImage = surface.makeImageSnapshot(rect(0, 0, 100, 100));
Ensures all queued draw operations are completed
Returns the width of the surfaceconst w = surface.width();
Returns the height of the surfaceconst h = surface.height();
Use Cases
Caching Complex Renders
Pre-render complex graphics for better performance:
import { useMemo } from "react";
import { Skia } from "@shopify/react-native-skia";
const useCachedImage = (size: number) => {
return useMemo(() => {
const surface = Skia.Surface.Make(size, size);
if (!surface) return null;
const canvas = surface.getCanvas();
const paint = Skia.Paint();
// Complex drawing operations
for (let i = 0; i < 100; i++) {
paint.setColor(Skia.Color(`hsl(${i * 3.6}, 100%, 50%)`));
canvas.drawCircle(
size / 2,
size / 2,
(size / 2) * (1 - i / 100),
paint
);
}
return surface.makeImageSnapshot();
}, [size]);
};
export default function CachedRender() {
const image = useCachedImage(300);
if (!image) return null;
return (
<Canvas style={{ flex: 1 }}>
<Image image={image} x={0} y={0} width={300} height={300} />
</Canvas>
);
}
Generating Textures
Create procedural textures:
const generateTexture = (width: number, height: number) => {
const surface = Skia.Surface.Make(width, height);
if (!surface) return null;
const canvas = surface.getCanvas();
const paint = Skia.Paint();
// Generate noise texture
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
const value = Math.random() * 255;
paint.setColor(Skia.Color(`rgb(${value},${value},${value})`));
canvas.drawRect(rect(x, y, 1, 1), paint);
}
}
return surface.makeImageSnapshot();
};
Image Processing
Manipulate images:
const applyFilter = (sourceImage: SkImage) => {
const surface = Skia.Surface.Make(
sourceImage.width(),
sourceImage.height()
);
if (!surface) return null;
const canvas = surface.getCanvas();
const paint = Skia.Paint();
// Apply filter
const blur = Skia.ImageFilter.MakeBlur(10, 10, TileMode.Decal, null);
paint.setImageFilter(blur);
canvas.drawImage(sourceImage, 0, 0, paint);
return surface.makeImageSnapshot();
};
Creating Thumbnails
Generate smaller versions of images:
const createThumbnail = (sourceImage: SkImage, maxSize: number) => {
const srcWidth = sourceImage.width();
const srcHeight = sourceImage.height();
const scale = Math.min(maxSize / srcWidth, maxSize / srcHeight);
const thumbWidth = Math.floor(srcWidth * scale);
const thumbHeight = Math.floor(srcHeight * scale);
const surface = Skia.Surface.Make(thumbWidth, thumbHeight);
if (!surface) return null;
const canvas = surface.getCanvas();
canvas.drawImageRect(
sourceImage,
rect(0, 0, srcWidth, srcHeight),
rect(0, 0, thumbWidth, thumbHeight),
Skia.Paint()
);
return surface.makeImageSnapshot();
};
Compositing Multiple Images
const compositeImages = (images: SkImage[]) => {
const surface = Skia.Surface.Make(300, 300);
if (!surface) return null;
const canvas = surface.getCanvas();
const paint = Skia.Paint();
images.forEach((image, index) => {
paint.setAlphaf(1 / (index + 1));
canvas.drawImage(image, 0, 0, paint);
});
return surface.makeImageSnapshot();
};
Drawing on Canvas
The canvas obtained from a surface has all standard drawing methods:
const surface = Skia.Surface.Make(400, 400);
const canvas = surface.getCanvas();
const paint = Skia.Paint();
// Background
paint.setColor(Skia.Color("white"));
canvas.drawPaint(paint);
// Shapes
paint.setColor(Skia.Color("blue"));
canvas.drawCircle(200, 200, 100, paint);
paint.setColor(Skia.Color("red"));
paint.setStyle(PaintStyle.Stroke);
paint.setStrokeWidth(5);
canvas.drawRect(rect(100, 100, 200, 200), paint);
// Text
const font = Skia.Font(null, 24);
paint.setColor(Skia.Color("black"));
paint.setStyle(PaintStyle.Fill);
canvas.drawText("Hello", 150, 220, paint, font);
// Get result
const image = surface.makeImageSnapshot();
Saving Surface Output
import RNFS from "react-native-fs";
import { ImageFormat } from "@shopify/react-native-skia";
const saveSurface = async (surface: SkSurface, filename: string) => {
const image = surface.makeImageSnapshot();
const bytes = image.encodeToBytes(ImageFormat.PNG);
const path = `${RNFS.DocumentDirectoryPath}/${filename}`;
await RNFS.writeFile(path, bytes.buffer, "base64");
console.log("Saved to:", path);
};
- Surfaces allocate memory for offscreen buffers - use judiciously
- Larger surfaces use more memory
- Create surfaces once and reuse when possible
- Call
flush() to ensure rendering is complete before capturing
- Dispose of surfaces when no longer needed (automatic in JavaScript)
- For repeated renders, consider caching the result as an image
Examples
Chart Rendering
const renderChart = (data: number[]) => {
const width = 400;
const height = 300;
const surface = Skia.Surface.Make(width, height);
if (!surface) return null;
const canvas = surface.getCanvas();
const paint = Skia.Paint();
// Background
paint.setColor(Skia.Color("white"));
canvas.drawPaint(paint);
// Draw bars
const barWidth = width / data.length;
const maxValue = Math.max(...data);
data.forEach((value, index) => {
const barHeight = (value / maxValue) * (height - 40);
const x = index * barWidth;
const y = height - barHeight - 20;
paint.setColor(Skia.Color("blue"));
canvas.drawRect(rect(x + 5, y, barWidth - 10, barHeight), paint);
});
return surface.makeImageSnapshot();
};
QR Code Generator
const generateQR = (data: string, size: number) => {
const surface = Skia.Surface.Make(size, size);
if (!surface) return null;
const canvas = surface.getCanvas();
const paint = Skia.Paint();
// Generate QR code data (simplified)
const matrix = generateQRMatrix(data);
const moduleSize = size / matrix.length;
paint.setColor(Skia.Color("white"));
canvas.drawPaint(paint);
paint.setColor(Skia.Color("black"));
matrix.forEach((row, y) => {
row.forEach((module, x) => {
if (module) {
canvas.drawRect(
rect(x * moduleSize, y * moduleSize, moduleSize, moduleSize),
paint
);
}
});
});
return surface.makeImageSnapshot();
};