TakumiTakumi

Images & SVG

Load raster and vector images into Takumi nodes.

Moved from /docs/load-images.

Images get into Takumi through three paths: a URL the renderer fetches, a data URL embedded in the JSX, or raw bytes you load yourself. Pick based on where the source lives and how often you need it.

Image sources

// 1. URL — renderer fetches before rendering.
<img src="https://example.com/avatar.png" width={64} height={64} />;

// 2. Data URL — bytes inline in the JSX, no network call.
<img src="data:image/png;base64,iVBORw0KGgo..." width={64} height={64} />;

// 3. Persistent store key — see "Persistent image store" below.
<img src="my-logo" width={64} height={64} />;

Background images work the same way:

<div style={{ backgroundImage: "url(https://example.com/hero.jpg)" }} />

Fetching strategies

For one-off renders the default behavior — Takumi fetches every src it sees — is fine. For production workloads you want to control when and how fetches happen.

Inline cache

Pass a Map to resourcesOptions.cache and the renderer will check it before fetching. Cheapest way to avoid re-decoding the same URL across requests.

import {  } from "takumi-js";

const  = new <string, ArrayBuffer>();

const  = await (< ="https://example.com/image.png" />, {
  : {  },
});

Extract, fetch, render

For multi-image scenes you can walk the node tree once, fetch every URL in parallel, and hand the byte map to the renderer.

import {  } from "takumi-js/node";
import {  } from "takumi-js/helpers/jsx";
import { ,  } from "takumi-js/helpers";

const  = new ();
const { ,  } = await (< />);

const  = ();
const  = await ();

const  = await .(, { ,  });

function () {
  return < />;
}

This is the path to take when you want to add retries, parallelism limits, or a CDN in front of the fetch.

Persistent store

For assets that appear on every render — logos, backgrounds, brand fonts — preload them once and reference by a stable key. See Persistent image store below.

SVG

SVG is supported through resvg. It rasterizes at render time, at the node's display size, so the output is sharp at any scale.

Inline SVG works as a JSX child:

<svg width="64" height="64" viewBox="0 0 24 24">
  <circle cx="12" cy="12" r="10" fill="#6366f1" />
</svg>

External SVG works through <img>:

<img src="https://example.com/logo.svg" width={120} height={32} />

Supported subset: shapes, paths, transforms, gradients, masks, basic filters, embedded raster images, text using fonts the renderer already knows about. CSS animations and JS handlers are ignored — Takumi rasterizes a static frame.

For SVG with text, register the font with the renderer (not inside the SVG with @font-face) so glyphs resolve through the same fallback chain as the rest of the scene.

object-fit and image-rendering

object-fit controls how an image fills its box when the box's aspect ratio differs from the image's.

ValueBehavior
fillStretch to fill the box. Default for <img>.
containScale to fit inside the box. Letterbox.
coverScale to cover the box. Crops the overflow.
noneNo resize. Source-size pixels, clipped to box.
scale-downnone or contain, whichever is smaller.
<img src="/hero.jpg" width={1200} height={400} style={{ objectFit: "cover" }} />

image-rendering controls the resampling algorithm.

ValueUse for
autoDefault. Engine chooses.
smoothPhotos. Lanczos3 resampling.
pixelatedPixel art, QR codes, anything you don't want blurred.

Persistent image store

Assets that show up in every render shouldn't be re-fetched and re-decoded every time. The persistent image store keeps decoded buffers alive on the renderer; you reference them by a string key.

import {  } from "takumi-js/response";

export function () {
  return new (< />, {
    : [
      { : "my-logo", : () => ("/logo.png").(() => .()) },
      { : "background", : () => ("/background.jpg").(() => .()) },
    ],
  });
}

function () {
  return (
    < ={{ : "url(background)" }}>
      < ="my-logo" />
    </>
  );
}

The same key works in src attributes and any background-image, mask-image CSS property.

For long-running servers, build the renderer once and call putPersistentImage directly:

const renderer = new Renderer({ fonts: [...] });
await renderer.putPersistentImage("my-logo", await fetch("/logo.png").then(r => r.arrayBuffer()));

On Cloudflare Workers, every decoded image counts against the per-isolate memory budget (128 MB for paid plans). Preload only what you know you'll reuse; reach for the inline cache for per-request images.

Edit on GitHub

Last updated on

On this page