## Demo: image-pipeline ### ImagePipelineDemo.svelte ```svelte

Each example references a size by name. getImageUrl is synchronous — it only signs a URL; the fetch, decode, and transform happen in the endpoint on the browser's request, cached on disk so later requests skip the work. getImage runs the same size inline when you need the bytes server-side.

Metadata

getImage() returns the transformed bytes plus dimensions and format:

Rendered via the thumb size {meta.width} × {meta.height} · {meta.format}

Resize · fit

The same source into a 240×240 box. fit: 'fill' stretches to the exact box; fit: 'inside' preserves aspect ratio and fits within it:

fit fillfit: 'fill'
fit insidefit: 'inside'

Rotate

A size's rotate turns the image clockwise in multiples of 90:

rotated 90 degreesrotate: 90
rotated 180 degreesrotate: 180
rotated 270 degreesrotate: 270

Flip · flop

flip: true mirrors vertically (about the x-axis); flop: true mirrors horizontally:

originaloriginal
flippedflip
floppedflop

Modulate

A size's modulate adjusts brightness and saturation (1 = unchanged):

greyscalesaturation: 0
brightenedbrightness: 1.5
saturatedsaturation: 2

Output formats

The same 300px image through three format sizes, with byte sizes from getImage(). Bun.Image can also encode avif:

{#each formats as { label, url, size } (label)}
{label} {label} · {kb(size)}
{/each}
``` ### routes.ts ```ts import { Mochi } from 'mochi-framework'; import type { MochiRouteValue } from 'mochi-framework'; export const routes: Record = { '/demos/image-pipeline': Mochi.page('./src/demos/image-pipeline/ImagePipelineDemo.svelte'), }; ``` ### index.ts ```ts import { Mochi, logger } from 'mochi-framework'; import { routes } from './routes'; await Mochi.serve({ port: 3333, development: process.env.MODE === 'development', // Named image sizes — the URL only carries the src + size name, and the endpoint runs the transform lazily on request. image: { sizes: { hero: { width: 600, height: 400, fit: 'inside' }, square: { width: 400, height: 400, fit: 'inside' }, card: { width: 400, height: 267, fit: 'inside' }, thumb: { width: 240, height: 240, fit: 'inside' }, 'fit-fill': { width: 240, height: 240, fit: 'fill' }, 'fit-inside': { width: 240, height: 240, fit: 'inside' }, rotate90: { width: 200, height: 200, fit: 'inside', rotate: 90 }, rotate180: { width: 200, height: 200, fit: 'inside', rotate: 180 }, rotate270: { width: 200, height: 200, fit: 'inside', rotate: 270 }, flip: { width: 200, height: 200, fit: 'inside', flip: true }, flop: { width: 200, height: 200, fit: 'inside', flop: true }, grayscale: { width: 200, height: 200, fit: 'inside', modulate: { saturation: 0 } }, brighten: { width: 200, height: 200, fit: 'inside', modulate: { brightness: 1.5 } }, saturate: { width: 200, height: 200, fit: 'inside', modulate: { saturation: 2 } }, 'fmt-jpeg': { width: 300, height: 300, fit: 'inside', format: 'jpeg', quality: 85 }, 'fmt-png': { width: 300, height: 300, fit: 'inside', format: 'png' }, 'fmt-webp': { width: 300, height: 300, fit: 'inside', format: 'webp', quality: 80 }, }, }, routes, }); logger.info('Server running at http://localhost:3333'); ```