Image - BunDocumentation Index Search...⌘KInstall Bun Search...Navigation Utilities ImageRuntimePackage ManagerBundlerTest RunnerGuidesReferenceBlogFeedback:first-child]:!hidden peer-[.is-custom]:[&>:first-child]:sm:!hidden peer-[.is-custom]:[&>:first-child]:md:!hidden peer-[.is-custom]:[&>:first-child]:lg:!hidden peer-[.is-custom]:[&>:first-child]:xl:!hidden">Get StartedWelcome to BunInstallationQuickstartTypeScriptTypeScript 6 and 7bun initbun createCore RuntimeBun RuntimeWatch ModeDebuggingREPLbunfig.tomlFile & Module SystemFile TypesModule ResolutionJSXAuto-installPluginsFile System RouterHTTP serverServerRoutingCookiesTLSError HandlingMetricsNetworkingFetchWebSocketsTCPUDPDNSData & StorageCookiesFile I/OStreamsBinary DataArchiveSQLSQLiteS3RedisConcurrencyWorkersProcess & SystemEnvironment VariablesShellSpawnWebViewCronInterop & ToolingNode-APIFFIC CompilerTranspilerUtilitiesCSRF ProtectionSecretsConsoleTOMLYAMLMarkdownJSON5JSONLHTMLRewriterImageHashingGlobSemverColorUtilsStandards & CompatibilityGlobalsBun APIsWeb APIsNode.js CompatibilityContributingRoadmapBenchmarkingContributingBuilding WindowsBindgenLicense On this pageInputMetadataResizeRotate · flipModulateOutput formatsTerminalsPlaceholdersBun.serve integrationClipboardPlatform backendsUtilitiesImage Copy pagespan]:line-clamp-1 overflow-hidden group flex items-center py-0.5 gap-1 text-sm text-gray-950/50 dark:text-white/50 group-hover:text-gray-950/70 dark:group-hover:text-white/70 rounded-none rounded-r-xl border px-3 border-gray-200 aspect-square dark:border-white/[0.07] bg-background-light dark:bg-background-dark hover:bg-gray-600/5 dark:hover:bg-gray-200/5" aria-label="More actions" type="button" id="radix-_R_2shjllfium6abasnpfiulb_" aria-haspopup="menu" aria-expanded="false" data-state="closed"> *]:[overflow-wrap:anywhere]"> Copy pagespan]:line-clamp-1 overflow-hidden group flex items-center py-0.5 gap-1 text-sm text-gray-950/50 dark:text-white/50 group-hover:text-gray-950/70 dark:group-hover:text-white/70 rounded-none rounded-r-xl border px-3 border-gray-200 aspect-square dark:border-white/[0.07] bg-background-light dark:bg-background-dark hover:bg-gray-600/5 dark:hover:bg-gray-200/5" aria-label="More actions" type="button" id="radix-_R_5hjllfium6abasnpfiulb_" aria-haspopup="menu" aria-expanded="false" data-state="closed"> Bun.Image is a chainable image pipeline for decoding, resizing, rotating, and re-encoding JPEG, PNG, WebP, HEIC, and AVIF — built on libjpeg-turbo, spng, libwebp, and SIMD geometry kernels, with zero npm dependencies and no native addon build step.
Yes NoSuggest editsRaise issueHTMLRewriterPreviousHashingNext⌘I xgithubdiscordyoutubePowered byThis documentation is built and hosted on Mintlify, a developer documentation platform
Fetch the complete documentation index at: /docs/llms.txt
Use this file to discover all available pages before exploring further.
Skip to main contentBun home pageDecode, transform, and encode images with a fast native pipeline
await Bun.file("photo.jpg").image().resize(400, 400, { fit: "inside" }).webp({ quality: 80 }).write("thumb.webp");
The API is shaped after Sharp: construct from an input, chain transforms, pick an output format, then await a terminal method. Nothing runs until the terminal is awaited, and the work executes off the JavaScript thread.
Input
The constructor accepts a path, bytes, or a Blob — including Bun.file() and Bun.s3(). Blob#image() is shorthand for new Bun.Image(blob):
new Bun.Image("./photo.jpg"); // file path
new Bun.Image(buffer); // Buffer / ArrayBuffer / TypedArray
new Bun.Image(Bun.file("photo.jpg")); // BunFile (read lazily, off-thread)
Bun.file("photo.jpg").image(); // same as above
Bun.s3("bucket/photo.jpg").image(); // S3File
The format is sniffed from the bytes — extensions and Content-Type are ignored.
Path strings are filesystem paths. Don’t pass user-controlled strings directly to the constructor — that’s an arbitrary-file-read primitive. Read untrusted input into a Buffer (e.g. via fetch/Bun.file with your own validation) and pass the bytes.
When passing a TypedArray/ArrayBuffer, don’t mutate it while a terminal is pending — decode runs off-thread and borrows the bytes. SharedArrayBuffer and resizable buffers are refused; use buf.slice() to pass a fixed view.
A second options argument guards against decompression bombs and controls EXIF handling:
new Bun.Image(input, {
// Reject if width*height > this. Checked after reading the header,
// before allocating the pixel buffer. Default matches Sharp (~268 MP).
maxPixels: 4096 * 4096,
// Apply JPEG EXIF Orientation before any other op. Default: true.
autoOrient: true,
});
Metadata
Read width, height, and format without decoding pixel data:
const { width, height, format } = await new Bun.Image(input).metadata();
// => { width: 1920, height: 1080, format: "jpeg" }
Resize
img.resize(800); // width 800, keep aspect ratio
img.resize(800, 600); // exactly 800×600 (stretch)
img.resize(800, 600, { fit: "inside" }); // fit within 800×600
img.resize(800, 600, { withoutEnlargement: true }); // never upscale
img.resize(800, 600, { filter: "mitchell" });
fitBehavior"fill" (default)Stretch to exactly width × height"inside"Preserve aspect ratio; result fits within the box
filter selects the resampling kernel. The default "lanczos3" is the right choice for photographs.
FilterUse when"lanczos3" (default)General-purpose, sharpest for photos"lanczos2"Slightly softer, fewer ringing artifacts"mitchell"Smooth gradients; the classic bicubic compromise"cubic"Catmull-Rom — sharper than Mitchell, can ring"mks2013" / "mks2021"”Magic Kernel Sharp”; used by Facebook/Instagram"bilinear" / "linear"Fast, soft"box"Area-average; good for large integer downscales"nearest"Pixel art / hard edges
When the source is a JPEG and the target is at most half the source size, decode skips straight to the nearest M/8 IDCT scale, so generating a thumbnail from a 24 MP photo never materializes the full-resolution buffer.
Rotate · flip
img.rotate(90); // 90° clockwise (multiples of 90 only) img.flip(); // mirror vertically (about the x-axis) img.flop(); // mirror horizontally (about the y-axis)Modulate
img.modulate({
brightness: 1.2, // 1 = unchanged
saturation: 0, // 0 = greyscale, 1 = unchanged, >1 = boost
});
Output formats
Calling a format method sets the encode target; without one, the source format is reused.
img.jpeg({ quality: 85 }); // 1–100, default 80
img.png({ compressionLevel: 6 }); // zlib level 0–9
img.png({ palette: true, colors: 64, dither: true }); // indexed PNG
img.webp({ quality: 80 });
img.webp({ lossless: true });
img.heic({ quality: 80 }); // macOS / Windows only
img.avif({ quality: 60 }); // macOS / Windows only
palette: true quantizes to a ≤256-color palette and emits an indexed (color-type 3) PNG, optionally with Floyd–Steinberg dither. This is typically 3–5× smaller than truecolor for screenshots and UI assets.
Terminals
A pipeline does no work until one of these is awaited:
await img.bytes(); // Uint8Array
await img.buffer(); // Buffer
await img.blob(); // Blob with .type set to the output MIME
await img.toBase64(); // string
await img.dataurl(); // "data:image/png;base64,…"
await img.write("out.webp"); // number (bytes written)
await img.write(Bun.s3("bucket/out.webp"));
.write() accepts the same destinations as Bun.write — a path string, Bun.file(), Bun.s3(), or an fd. If you didn’t chain a format method and the destination is a path string, the extension picks one (.jpg/.png/.webp/.heic/.avif).
Placeholders
For a low-quality placeholder to inline in HTML before the real image loads, .placeholder() returns a ThumbHash-rendered ≤32px blur as a data: URL — ~400–700 bytes, no client-side decoder needed:
const lqip = await Bun.file("hero.jpg").image().placeholder();
// — then swap to the real URL on load.
For coarse-to-fine rendering of the image itself, encode a progressive JPEG:
img.jpeg({ progressive: true });
After the first terminal resolves, img.width and img.height reflect the output dimensions (they’re -1 before).
Bun.serve integration
A Bun.Image pipeline is a valid Response body and sets Content-Type automatically. To keep the encode off the JS thread in a server handler, await a terminal first:
Bun.serve({
routes: {
"/avatar/:id": async req => {
// Validate before touching the filesystem (see the Input note above).
if (!/^[a-z0-9]+$/.test(req.params.id)) return new Response(null, { status: 400 });
const out = await Bun.file(`avatars/${req.params.id}.png`).image().resize(128, 128).webp().blob();
return new Response(out);
},
},
});
Passing the pipeline directly (new Response(img)) also works, but currently runs the encode synchronously during body init.
Clipboard
const img = Bun.Image.fromClipboard();
if (img) {
const png = await img.resize(800, 800, { fit: "inside" }).png().bytes();
}
fromClipboard() reads PNG, TIFF, HEIC, JPEG, WebP, GIF, or BMP from the system pasteboard on macOS and Windows; the regular decode pipeline takes it from there. Returns null if there’s no image, and always null on Linux — call wl-paste/xclip yourself and pass the bytes to the constructor.
For a passive “image in clipboard, press ⌘V” hint, poll clipboardChangeCount() (a single integer read) and call hasClipboardImage() only when it moves; macOS has no clipboard-change notification, so this is the documented pattern.
Platform backends
LinuxmacOSWindowsJPEG / PNG / WebPlibjpeg-turbo · spng · libwebpsamesameBMP / GIF (decode)built-inImageIOWICTIFF (decode)❌ImageIOWICResize / rotate / flipHighway SIMDAccelerate vImageHighway SIMDHEIC / AVIF❌ ERR_IMAGE_FORMAT_UNSUPPORTEDImageIO ²WIC ¹Clipboard❌ returns nullNSPasteboardWin32
¹ Windows requires the HEIF Image Extensions / AV1 Video Extension from the Microsoft Store.
² AVIF encode needs an OS AV1 encoder — Apple Silicon M3+ only. Intel Mac and M1/M2 reject with ERR_IMAGE_FORMAT_UNSUPPORTED; AVIF decode works everywhere ImageIO does (macOS 13+).
When a system-backend format isn’t available on the current machine, the terminal rejects with error.code === "ERR_IMAGE_FORMAT_UNSUPPORTED" — branch on that to fall back to a portable format:
const out = await img
.avif({ quality: 50 })
.bytes()
.catch(e => {
if (e.code === "ERR_IMAGE_FORMAT_UNSUPPORTED") return img.webp({ quality: 80 }).bytes();
throw e;
});
Formats handled by the system backend (TIFF, HEIC, AVIF, clipboard) inherit the OS’s patch level — keep macOS / Windows updated. JPEG, PNG, and WebP go through the same statically-linked codecs on every platform, so encoded output is byte-identical across Linux, macOS, and Windows. To force the portable Highway path for geometry too — e.g. for golden-image tests — set the process-global backend:
Bun.Image.backend = "bun"; // default is "system" on macOS/Windows
Was this page helpful?
Image - Bun,AI智能索引,全网链接索引,智能导航,网页索引
- Decode, transform, and encode images with a fast native pipeline