pixfx

JS engine

pixfx-wasm is the engine as plain JavaScript bindings: list effects, read their metadata, spawn instances, step them and read their pixels. You decide how to draw them.

Use it to draw with WebGL or WebGPU, in a worker, or to test pixels. To simply show effects on a canvas, the canvas renderer does the drawing for you.

Build

wasm-pack build crates/pixfx-wasm --target web --out-dir pkg
import init, { Fx, effects, meta } from "./pkg/pixfx_wasm.js";
await init();

Example

const fx = new Fx("fire/campfire", { seed: 1 });
canvas.width = fx.width();
canvas.height = fx.height();
const ctx = canvas.getContext("2d");

let last = performance.now();
function frame(now) {
  fx.update(Math.max(0, now - last) / 1000); // never step backwards
  last = now;
  fx.draw("both");
  ctx.putImageData(new ImageData(fx.rgba(), fx.width(), fx.height()), 0, 0);
  requestAnimationFrame(frame);
}
requestAnimationFrame(frame);

Functions

effects() → string[]

Every effect id, in registry order.

meta(id) → object

{id, name, desc, tags, size: [w, h], origin: [x, y], glowMargin, duration, loop, fps, seed}. Throws unknown effect "..." for an id not in the registry. Fields are described in Concepts.

Fx

A live effect on its own canvas: meta.size plus margin empty pixels on every side, with the effect's origin at meta.origin + margin.

new Fx(id, opts?)

opts is undefined, null or a plain object with the common options, the effect's own options, and:

OptionMeaning
marginWhole number from 0 to 4096: empty pixels round the canvas. 0 (the default) eases light out at the edge so frames match the sprite sheet; meta(id).glowMargin fits the effect's glow and fades nothing.

Options are checked once, here. A field of the wrong type throws an error naming it, for example option flip must be a boolean or option palette[2]: not a #rgb, #rrggbb or #rrggbbaa colour. x and y are refused: an Fx always draws at its own origin. Keys whose value is undefined are skipped.

Methods

MethodDoes
update(dt)Advance by dt seconds (at most 0.25, in 1/120 s sub-steps). Throws for NaN, a missing argument or a negative step, and leaves the instance unchanged.
draw(layer)Clear the canvas and draw "back", "front" or "both" at the current time.
stop()Let it end: loops fade out, one-shots are cut short.
done()True once a stopped instance has faded, or a one-shot has finished.
t()Seconds since the instance started.
width(), height()Canvas size in pixels, margin included.
originX(), originY()Where the effect's origin sits on the canvas. Draw the canvas at (x - originX(), y - originY()) to place the effect at (x, y).
free()Release the instance's wasm memory (wasm-bindgen).

Reading pixels

After draw(), read the frame in the form that suits your renderer:

MethodReturns
rgba()A Uint8ClampedArray copy, width × height × 4: both layers composited over transparency, ready for new ImageData(px, w, h).
color()A Uint32Array copy of the colour layer, one ABGR word per pixel, row-major.
light()A Uint32Array copy of the light layer, one xBGR word per pixel (top byte unused).
colorRgbaPtr()Address in wasm memory of the colour layer as straight-alpha RGBA8 bytes. No copy.
lightRgbaPtr()Address of the light layer as RGBA8: lit pixels r, g, b, 255, unlit 0, 0, 0, 0, to draw additively. No copy.

The pointer methods avoid a copy per frame. View the bytes before the next call on the same instance; they share one buffer, so lightRgbaPtr() overwrites what colorRgbaPtr() wrote.

import init, { Fx } from "./pkg/pixfx_wasm.js";
const wasm = await init();

const ptr = fx.colorRgbaPtr();
const bytes = new Uint8ClampedArray(wasm.memory.buffer, ptr, fx.width() * fx.height() * 4);
gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, fx.width(), fx.height(), gl.RGBA, gl.UNSIGNED_BYTE, bytes);

Make a new view each frame: wasm memory can grow, which detaches old views.

Rust side

The bindings are a thin layer over plain Rust, which other crates use directly: pixfx_wasm::Player (an instance plus its canvas) and pixfx_wasm::parse_options (JS object to typed Options and margin). pixfx-canvas is built on both.

Demo

wasm-pack build crates/pixfx-wasm --target web --out-dir demo/pkg
cd crates/pixfx-wasm/demo && python3 -m http.server 8765 --bind 127.0.0.1