Skip to main content
Technical preview

Redraw is currently in technical preview, available to wcandillon.dev subscribers. API is unstable.

Images

Redraw draws images from plain GPUTextures. Textures live on the device, not on a Library, so any canvas can sample them; a set of factories covers the common sources.

Creating textures

import { makeTexture, makeTextureFromCanvas, makeTextureFromImage } from "redraw";

// From raw RGBA pixels (straight alpha, width * height * 4 bytes):
const texture = makeTexture(device, width, height, data);

// From an image source (Blob, ImageData, another canvas, ...):
const photo = await makeTextureFromImage(device, blob);

// Snapshot a 2D canvas's current contents:
const snapshot = makeTextureFromCanvas(device, canvasElement);

All textures are rgba8unorm with straight (un-premultiplied) alpha. The caller owns the texture: destroy it when no canvas samples it anymore. In React, get the device with the useDevice() hook (see React).

drawImage

canvas.drawImage(texture, x?, y?, options?) draws the texture with its top-left corner at (x, y) in local space, one texel per local unit (scale via the canvas transform):

canvas.save();
canvas.scale(0.5);
canvas.drawImage(photo, 40, 40, { filter: "linear" });
canvas.restore();

The options:

OptionDefaultWhat it does
filter"nearest"Sampling filter: "nearest", "linear", or a cubic resampler { B, C } (e.g. { B: 1/3, C: 1/3 } for Mitchell).
tileMode"decal"What lies outside the image rect: transparent ("decal") or the image tiled across the whole clip region ("repeat").

Shading geometry with an image

drawImage covers the common case; to shade arbitrary geometry with an image, claim a texture slot yourself and add an ImageShader as the paint's color shader:

import { Circle, ImageShader, Paint } from "redraw";

const slot = canvas.useTexture(photo);
const paint = new Paint().addShader(
new ImageShader(slot, [photo.width, photo.height], {
filter: "linear",
offset: [40, 40],
}),
);
canvas.draw(new Circle([200, 200], 120), paint);

Texture slots are per-frame state, reset by render() like the rest of the recording. The same texture always reuses its slot, so only distinct textures count against the Library's textureSlots (1 by default); pass a larger textureSlots to createLibrary when a frame samples more.