Turn any image into pixel art. One command definition serves both CLI and HTTP API via incur. Deployable to Cloudflare Workers with mppx payment gating via the Machine Payments Protocol.
Built with @cf-wasm/photon (Rust/Wasm), incur (linked locally — file:../incur — for multipart/form-data and binary body support), and mppx (MPP payment gating).
bun install
bun run build:cli # build + link `plate` globally# PNG output (pipe to file)
plate photo.png > out.png
plate photo.png -s 32 -c 8 > out.png # 32px grid, 8 colors
# Named palettes
plate photo.png -p pico8 > out.png # PICO-8 (16 colors)
plate photo.png -p gameboy > out.png # Game Boy (4 colors)
plate photo.png -p nes > out.png # NES
plate photo.png -p mono > out.png # Black & white
plate photo.png -p grayscale > out.png # 8-shade grayscale
# Custom palette (one hex color per line)
plate photo.png --palette-file colors.hex > out.png
# Dithering
plate photo.png -d floyd-steinberg > out.png # error diffusion
plate photo.png -d ordered > out.png # 4×4 Bayer pattern
# SVG and JSON output
plate photo.png -f svg > out.svg # scalable vector (rects)
plate photo.png -f json # { width, height, grid[][] }
# Piping (stdin → stdout)
cat photo.png | plate -s 16 > pixel.png
# Specify total blocks (auto-calculates grid preserving aspect ratio)
plate photo.png -b 256 > out.png
# Subcommands
plate health # health check
plate palettes # list available palettes| Flag | Alias | Default | Description |
|---|---|---|---|
--size |
-s |
64 |
Grid size in pixels (longest side) |
--blocks |
-b |
— | Total block count (auto-sizes grid) |
--colors |
-c |
16 |
Max colors in palette |
--palette |
-p |
— | Named palette: pico8, gameboy, nes, mono, grayscale |
--palette-file |
— | Custom palette file (hex colors, one per line) | |
--dither |
-d |
none |
Dithering: none, floyd-steinberg, ordered |
--format |
-f |
png |
Output format: png, svg, json |
--scale |
-x |
8 |
Output scale multiplier |
The same incur command definition serves as an HTTP API via cli.fetch. Path segments map to commands, form fields map to options. The image arg uses file() from incur (not z.file() from zod) for transport-aware file input that works across CLI, HTTP multipart, and MCP.
The root pixelate endpoint (POST /) is gated behind a 0.01 pathUSD payment via mppx on the Tempo moderato testnet. Health and palettes endpoints are free. The worker (src/worker.ts) exports a Workers fetch handler that routes by path: root requests go through mppx payment gating wrapping cli.fetch, non-root paths (/health, /palettes, etc.) pass through to cli.fetch directly.
bun run dev # Vite dev server with HMR on :5173# Help — list all endpoints and usage (free)
curl localhost:5173/help
# Health check (free)
curl localhost:5173/health
# List available palettes (free)
curl localhost:5173/palettes
# Pixelate an image (POST multipart to root — requires payment in production)
curl -F image=@photo.png \
-F size=32 \
-F palette=pico8 \
-F dither=floyd-steinberg \
localhost:5173/ -o out.pngThe root endpoint accepts multipart form data with these fields: image (required file), size, blocks, colors, scale, dither, palette, format.
In production, the root endpoint returns 402 Payment Required. Use tempo request to automatically handle the MPP payment flow:
# Pixelate via paid endpoint (0.01 pathUSD per request, Tempo moderato testnet)
tempo request -n tempo-moderato \
-F image=@photo.png \
-F size=32 \
-F palette=pico8 \
https://plate.struong996.workers.dev/ -o out.png
# Free endpoints work with plain curl
curl https://plate.struong996.workers.dev/help
curl https://plate.struong996.workers.dev/health
curl https://plate.struong996.workers.dev/palettesbun run build # vite build → dist/_worker.js
bun run deploy # wrangler deploySet secrets for mppx payment gating (Tempo moderato testnet):
wrangler secret put MPP_SECRET_KEY # mppx signing key
wrangler secret put PAYMENT_RECIPIENT # pathUSD recipient addressimport { pixelate } from 'plate'
const result = pixelate(imageBytes, {
size: 32,
colors: 8,
dither: 'floyd-steinberg',
})
// result.buffer — PNG as Uint8Array
// result.pixelData — raw RGB grid pixels
// result.width — output width
// result.height — output height
// result.colorsUsed — number of colors used
// result.grid — { w, h } grid dimensionsbun run dev # Vite dev server with HMR
bun run build # vite build for Workers
bun run build:cli # tsc + chmod + bun link
bun run build:lib # tsc → dist/ (library)
bun run typecheck # type-check without emitting
bun run deploy # wrangler deploy to CF WorkersMIT