HTTP server for Takumi image rendering.
This project provides a drop-in Docker image for convenient server-side image generation, making it easy to integrate Takumi into any backend (Laravel, Node.js, Go, etc.) via simple HTTP requests.
For documentation on the node tree schema, available node types, and styling options, see the Takumi Reference.
| Method | Endpoint | Description |
|---|---|---|
| GET | /health |
Health check |
| POST | /render |
Render node tree to image |
| POST | /render/animation |
Render animated WebP/APNG |
| POST | /measure |
Measure layout without rendering |
| POST | /images |
Add image to cache |
| DELETE | /images |
Clear image cache |
| POST | /extract-urls |
Extract resource URLs from node tree |
Static rendering supports PNG, JPEG, lossy or lossless WebP, ICO, and SVG. Set
"lossless": true with the WebP format to select lossless encoding.
/render and /measure accept either node or html. Animation frames and
scenes also accept either field. Supplying both is rejected.
Render, measure, and animation options support:
| Field | Type | Description |
|---|---|---|
fonts |
array | Base64 fonts as { "data": "..." } or remote font URL strings |
fontFamilies |
string array | Ordered font fallback families |
lang |
string | Root BCP-47 language tag |
fetchImages |
boolean | Fetch image URLs referenced by the input |
fetchTimeoutMs |
integer | Remote image/font timeout; defaults to 10000 |
fetchCache |
boolean | Reuse fetched bytes between requests; defaults to true |
dithering |
string | none, orderedBayer, or floydSteinberg |
SVG output uses "format": "svg". Scene animations are streamed into their
encoder to keep memory bounded. Explicit frame arrays remain eager because each
frame may have an independent duration.
docker run -p 3000:3000 ghcr.io/flycro/takumi-http:latestservices:
takumi:
image: ghcr.io/flycro/takumi-http:latest
app:
image: your-app
environment:
- TAKUMI_URL=http://takumi:3000
depends_on:
- takumiWith custom fonts (disabling embedded fonts):
services:
takumi:
image: ghcr.io/flycro/takumi-http:latest
environment:
- TAKUMI_LOAD_DEFAULT_FONTS=false
volumes:
- ./fonts:/fonts:ro
command: ["--font-dir", "/fonts"]curl -X POST http://localhost:3000/render \
-H "Content-Type: application/json" \
-d '{
"node": {
"type": "container",
"tw": "w-full h-full flex justify-center bg-black items-center",
"style": {
"backgroundImage": "radial-gradient(circle at 25px 25px, lightgray 2%, transparent 0%), radial-gradient(circle at 75px 75px, lightgray 2%, transparent 0%)",
"backgroundSize": "100px 100px"
},
"children": [{
"type": "container",
"tw": "flex flex-col justify-center items-center",
"children": [
{
"type": "container",
"tw": "flex flex-row gap-3",
"children": [
{"type": "text", "text": "Welcome to", "tw": "text-white font-semibold text-6xl"},
{"type": "text", "text": "Takumi", "tw": "text-[#ff3535] font-semibold text-6xl"},
{"type": "text", "text": "Playground 👋", "tw": "text-white font-semibold text-6xl"}
]
},
{
"type": "text",
"text": "You can try out and experiment with Takumi here.",
"tw": "text-white opacity-75 text-4xl mt-4",
"style": {"fontFamily": "Geist Mono"}
}
]
}]
},
"options": {
"format": "png",
"width": 1200,
"height": 630
}
}' --output playground.pngcurl -X POST http://localhost:3000/render/animation \
-H "Content-Type: application/json" \
-d '{
"frames": [
{
"node": {
"type": "container",
"tw": "w-full h-full flex justify-center items-center bg-red-500",
"children": [{"type": "text", "text": "Frame 1", "tw": "text-white text-4xl font-bold"}]
},
"durationMs": 500
},
{
"node": {
"type": "container",
"tw": "w-full h-full flex justify-center items-center bg-blue-500",
"children": [{"type": "text", "text": "Frame 2", "tw": "text-white text-4xl font-bold"}]
},
"durationMs": 500
},
{
"node": {
"type": "container",
"tw": "w-full h-full flex justify-center items-center bg-green-500",
"children": [{"type": "text", "text": "Frame 3", "tw": "text-white text-4xl font-bold"}]
},
"durationMs": 500
}
],
"options": {
"format": "webp",
"width": 400,
"height": 200
}
}' --output animation.webpcurl -X POST http://localhost:3000/render \
-F 'node={"type":"container","tw":"w-[400] h-[400]","children":[{"type":"image","src":"logo","tw":"w-full h-full object-cover"}]}' \
-F 'options={"format":"png","width":400,"height":400}' \
-F 'resource_logo=@./logo.png' \
--output result.pngFile uploads use the resource_<name> or file_<name> field naming convention. The <name> part becomes the src reference in your node tree.
curl -X POST http://localhost:3000/measure \
-H "Content-Type: application/json" \
-d '{
"node": {
"type": "container",
"tw": "w-[200px] h-[100px]"
},
"options": {
"width": 1000,
"height": 1000
}
}'
# Returns: {"width": 200.0, "height": 100.0, ...}Pre-load images into the server's memory cache to reuse across multiple renders without re-uploading.
1. Add image to cache:
# Base64 encode your image
BASE64_IMAGE=$(base64 -w0 avatar.png)
curl -X POST http://localhost:3000/images \
-H "Content-Type: application/json" \
-d "{\"src\": \"user-avatar\", \"data\": \"$BASE64_IMAGE\"}"
# Returns: {"src": "user-avatar", "message": "Image added to cache"}2. Use cached image in renders:
Reference the cached image by its src name in your node tree:
curl -X POST http://localhost:3000/render \
-H "Content-Type: application/json" \
-d '{
"node": {
"type": "container",
"tw": "w-[400] h-[400] flex items-center justify-center bg-gray-100",
"children": [{
"type": "image",
"src": "user-avatar",
"tw": "w-[200] h-[200] rounded-full object-cover"
}]
},
"options": {"format": "png", "width": 400, "height": 400}
}' --output profile.pngThe "src": "user-avatar" in the image node matches the "src" used when adding to cache.
3. Clear cache when needed:
curl -X DELETE http://localhost:3000/images
# Returns: {"message": "Image cache cleared"}Image sources priority:
fetchedResourcesin the request body (base64 encoded)- Multipart file uploads (
resource_<name>fields) - Persistent image cache (
/imagesendpoint)
This is useful when:
- Rendering many images with the same assets (logos, avatars, backgrounds)
- Reducing request payload size for repeated renders
- Keeping frequently used images in memory for faster access
| Option | Env Var | Default | Description |
|---|---|---|---|
--port |
TAKUMI_PORT |
3000 | Server port |
--font-dir |
TAKUMI_FONT_DIR |
- | Directory containing custom fonts |
--load-default-fonts |
TAKUMI_LOAD_DEFAULT_FONTS |
true | Load embedded fonts (Geist, Geist Mono, Twemoji) |
--enable-cache |
TAKUMI_ENABLE_CACHE |
true | Enable /images endpoint for pre-loading images |
--body-limit |
TAKUMI_BODY_LIMIT |
50MB | Max request body size |
--log-level |
TAKUMI_LOG_LEVEL |
info | Log level (trace, debug, info, warn, error) |
cargo build --releaseThis project is built on top of Takumi - a high-performance image rendering engine. Thanks to @kane50613 for creating and maintaining the core library.
MIT