Takumi

Images & emoji

Load and cache images, and render emoji.

External Images

Takumi fetches external images in src attributes and CSS background-image / mask-image.

To reuse fetched bytes across renders, pass a fetchCache: a Map<string, Promise<ArrayBuffer>>, or anything with Map-like get/set/delete. It stores the in-flight promise, so concurrent renders needing the same URL share one fetch:

import {  } from "takumi-js";

const  = < ="https://example.com/image.png" />;

const  = new <string, <ArrayBuffer>>();

const  = await (, {
  : {
    ,
  },
});

A plain Map never evicts. If your URLs are high-cardinality (a different image per render), pass a bounded cache such as an LRU, or omit fetchCache.

Fetch limits

Every remote fetch (images, fonts, Google Fonts CSS) is bounded. The group form of images takes the same options:

OptionDefaultUse
timeout5000 (ms)Abort a hanging host. 0 or negative disables it.
maxBytes32 MiBReject a body past this size, by content-length or streamed.
allowUrlallow allReturn false to block a URL, e.g. an SSRF (server-side request forgery) allowlist.
fetchglobalThis.fetchCustom fetch implementation.
import {  } from "takumi-js";

const  = < ="https://example.com/image.png" />;

const  = await (, {
  : {
    : 3000,
    : 4 * 1024 * 1024,
    : () => new (). === "example.com",
  },
});

render() and ImageResponse fetch these URLs from your server, so set an allowUrl allowlist whenever user input can influence the markup. The policy is checked on every redirect hop (capped at 5), so an allowed URL can't 3xx its way to an internal address. DNS is not inspected; keep a custom fetch for a stricter policy such as resolved-address filtering.

Pre-fetched Images

Provide images by key so the renderer doesn't have to fetch them itself. Each key can be used in any src field or in the background-image / mask-image CSS properties.

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

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

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

Raw pixels

For pixels you compute yourself (charts, generative art), pass raw RGBA instead of encoding a PNG first; nothing is decoded. The Bitmap helper renders a row-major width * height * 4 byte buffer as an <img>:

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

const  = new (64 * 64 * 4);

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

Set premultiplied when the bytes are already alpha-premultiplied and the renderer takes them as-is. Outside JSX, an image node's src takes the same { width, height, data, premultiplied? } object.

Decode caching

Takumi caches each decoded image so repeated content is decoded once. Two independent layers exist:

LayerStoresKeyed bySet with
Byte cacheFetched bytesURLimages.fetchCache
Decode cacheDecoded pixelsHash of image bytesper-image cache / images.cache

The decode cache lives in the renderer and is bounded: entries weigh in at their decoded size against a byte budget (16 MiB by default), and once it fills the cache evicts the least recently used. A high-cardinality workload (a different image every render) won't grow memory; it misses the cache and decodes each time. SVG sources and their rasterized pixmaps share the same budget, as do parsed stylesheets.

Set the budget when constructing the renderer; 0 disables caching:

const renderer = new Renderer({ cacheMaxBytes: 64 * 1024 * 1024 });

The per-image cache mode takes two values:

  • auto (default): cache the decoded image within the byte budget.
  • none: read the cache but never populate it. Use it for a one-off image you won't render again, so it can't displace images worth keeping.
import {  } from "takumi-js/response";

export function () {
  return new (< />, {
    : [
      {
        : "banner",
        : () => ("/banner.png").(() => .()),
        : "none", 
      },
    ],
  });
}

To set the mode for every image in a render, including ones Takumi fetches itself, pass images as a group instead of an array. A per-source cache still wins. For a route where each request renders a different image, cache: "none" keeps one-off decodes out of the cache:

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

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

function () {
  return < ="logo" />;
}

Decode limits

Takumi rejects oversized images at decode time. The limits guard server renders against decode bombs: small files that declare huge dimensions to force multi-gigabyte allocations.

InputLimit
Image8192×8192 pixels
Animated GIF4× the image budget, summed over all frames

Emoji

Dynamic fetching

ImageResponse accepts a satori-compatible emoji option. The providers are twemoji, blobmoji, noto, openmoji, fluent, and fluentFlat.

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

export function () {
  return new (< ="flex justify-center items-center text-3xl">Hello 👋😁</>, {
    : "twemoji", 
  });
}

Manual extraction

ImageResponse calls the extractEmojis helper for you. The helper splits emoji from text into image nodes pointing at a CDN. Run the steps yourself to fetch emoji bytes alongside other images.

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

let {  } = await (< ="flex justify-center items-center text-3xl">Hello 👋😁</>);
 = (, "twemoji");

const  = await ({  });

const  = new ();

const  = await .(, {
  ,
});

prepareImages walks the node tree for remote <img>, backgroundImage, and maskImage URLs, fetches the ones missing from sources, and returns images entries. Pass a fetchCache (a Map<string, Promise<ArrayBuffer>>, or anything with Map-like get/set/delete) to deduplicate concurrent fetches and reuse bytes across renders.

COLR / bitmap fonts

Takumi renders COLR (Color Layer) fonts, the format behind packs like Twemoji-COLR. A COLR file is much smaller than a bitmap emoji font like Noto Color Emoji. The renderer draws emoji from the loaded glyphs, with no network request.

Two steps:

  • Register the font through fonts.
  • Set emoji: "from-font".

Last updated on

On this page