IFS Encyclopedia

ifslib

ifslib is a standalone WebAssembly library for parsing and rendering fractals described in the AIFS language. It powers every fractal canvas on this site. Embed it in any web page, Node.js script, or WASM-capable environment — no dependencies, no bundler required. For analytics (Hausdorff dimension, neighbor graph, boundary IFS generation), see the Advanced API →

Download

The WASM binary is served alongside this site and is part of the open-source repository:

Instantiation

ifslib is a WASI reactor with zero imports. Pass an empty imports object and call _initialize() once:

const resp = await fetch('ifslib.wasm');
const buf  = await resp.arrayBuffer();
const { instance } = await WebAssembly.instantiate(buf, {});  // no imports needed
const wasm = instance.exports;
wasm._initialize();  // must call once after instantiation

For repeated renders, compile the module once and instantiate a fresh copy per render (ifslib has a single global renderer per instance):

const mod = await WebAssembly.compile(buf);  // compile once, cache
// later:
const { exports: wasm } = await WebAssembly.instantiate(mod, {});
wasm._initialize();

API Reference

Memory helpers

ifslib uses a C string ABI. Use malloc / free from the exports, and the memory export to read/write bytes:

function writeCString(wasm, str) {
  const bytes = new TextEncoder().encode(str + '\0');
  const ptr = wasm.malloc(bytes.length);
  new Uint8Array(wasm.memory.buffer).set(bytes, ptr);
  return ptr;              // caller must free()
}

function readCString(wasm, ptr) {
  if (!ptr) return '';
  const mem = new Uint8Array(wasm.memory.buffer);
  let end = ptr;
  while (mem[end] !== 0) end++;
  return new TextDecoder().decode(mem.subarray(ptr, end));
}

Core rendering pipeline

ExportSignatureDescription
_initialize() () → void Must be called once after instantiation. Initialises the global renderer.
init(ptr) (i32) → i32 Parse an AIFS source string (C string pointer). Returns the number of blocks found (≥1) on success, 0 on parse error. if (!wasm.init(ptr)) still works as a failure check. Error text via get_last_output().
get_block_idx(block) (i32) → i32 Resolve a block string ID to its 0-based internal index without selecting it. Accepts the block's string ID (the @id part of @id:parentId). Pass nullptr (pointer 0) or empty string to get the index of the first non-hidden block. Returns the index on success, or −1 if not found. Must be called after init().
set_block(block_idx) (i32) → i32 Select a block by its 0-based numeric index (as returned by get_block_idx). Pass −1 to select the first non-hidden block. Resets the root set to the block default ($root if defined, otherwise first visible variable). Can be called multiple times to switch blocks without calling init() again. Returns the number of variables in the block (≥1) on success, or 0 on error.
set_root(root) (i32) → i32 Override the root attractor set within the already-selected block (C string). Must be called after set_block. Can be called multiple times to switch root variables without calling set_block() again. Returns the Euclidean dimension of the projected attractor (DIM ≥ 0) on success, or −1 on failure.
get_var_name(var_idx) (i32) → i32 Return the name of the variable at 0-based var_idx within the currently selected block. Returns ALL variables (map definitions and attractor sets alike) — not just attractors. Returns a pointer to a null-terminated string valid until the next ifslib call, or 0 (null) if out of range or no block selected. Must be called after set_block.
set_camera(params, n) (i32, i32) → i32 Override the viewport for subsequent render() calls. n=4: 2D — [cx, cy, r, angle_deg]; n=10: 3D — [loc.x,y,z, ref.x,y,z, up.x,y,z, fov_deg]; n=0: reset to automatic camera. Must be called after set_block. Returns 1 on success.
render(w, h, quality, thickness) (i32, i32, i32, i32) → i32 Render to an RGBA pixel buffer of w×h×4 bytes. Returns a pointer to the buffer (valid until the next render call), or 0 on failure. quality ≥ 1 (2 is usually sufficient); thickness ≥ 1 pixels.
get_last_output() () → i32 Returns a pointer to accumulated console output since the last call. Valid until the next ifslib call.

For analytics functions — Hausdorff dimension, attractor metrics, neighbor graph, and boundary IFS — see the Advanced API.

Minimal Example

Render a 256×256 Sierpiński triangle and draw it onto a <canvas>:

const aifs = `@
$dim=2
f1=[0.5,0,0,0.5]
f2=[0.5,0]*[0.5,0,0,0.5]
f3=[0.25,0.5]*[0.5,0,0,0.5]
S=(f1|f2|f3)*S`;

const resp = await fetch('ifslib.wasm');
const { instance } = await WebAssembly.instantiate(await resp.arrayBuffer(), {});
const wasm = instance.exports;
wasm._initialize();

// write AIFS source into WASM memory
const bytes = new TextEncoder().encode(aifs + '\0');
const ptr = wasm.malloc(bytes.length);
new Uint8Array(wasm.memory.buffer).set(bytes, ptr);

if (!wasm.init(ptr)) throw new Error(readCString(wasm, wasm.get_last_output()));
wasm.free(ptr);

// pass -1 to select the first non-hidden block
if (!wasm.set_block(-1)) throw new Error('set_block failed');

const W = 256, H = 256;
const pixPtr = wasm.render(W, H, 2, 1);  // quality=2, thickness=1
if (!pixPtr) throw new Error('render failed');

const rgba = new Uint8ClampedArray(wasm.memory.buffer, pixPtr, W * H * 4);
const canvas = document.querySelector('canvas');
canvas.width = W; canvas.height = H;
canvas.getContext('2d').putImageData(new ImageData(rgba, W, H), 0, 0);

Note: to select the default block, pass −1 to wasm.set_block(-1). To select by block ID, call get_block_idx(ptr) first to get the index, then pass it to set_block(idx). set_root is only needed when you want a specific root set other than the block default. Both functions can be called repeatedly to switch blocks/roots without reinitialising.

Using in a Web Worker

For non-blocking rendering (required for large canvases), run ifslib inside a Web Worker. The reference implementation used by this site supports:

See ifslib-worker.js for the full implementation.

Using in Node.js

import { readFile } from 'node:fs/promises';

const buf = await readFile('ifslib.wasm');
const { instance } = await WebAssembly.instantiate(buf, {});
const wasm = instance.exports;
wasm._initialize();
// ... same API as browser

Tested on Node.js 18+. No WASI polyfills needed — the binary has no external imports.

Embedding on Your Own Site

  1. Copy ifslib.wasm (and optionally ifslib-worker.js) into your public/ (or static assets) directory.
  2. Serve the WASM file with the correct MIME type: application/wasm. Most static hosts (GitHub Pages, Netlify, Vercel, Cloudflare Pages) do this automatically.
  3. ifslib does not use SharedArrayBuffer and has no cross-origin isolation requirements. No special response headers are needed.

License

ifslib.wasm is the computational core of the IFStile project, created by Dmitry Mekhontsev and distributed under the GNU Lesser General Public License v3 (LGPL-3.0). This means you can embed it in projects under any license (open-source or proprietary) without affecting your code, as long as modifications to ifslib itself remain open-source.