Bun.Image Bun.Image is a chainable image processing pipeline built into the Bun runtime that can decode, resize, rotate, and re-encode JPEG, PNG, WebP, HEIC, and AVIF formats without any npm dependencies or native addon build steps. The API is modeled after Sharp, allowing users to construct an image from a file path, bytes, or Blob, chain transformation operations like resize and rotate, select an output format, and then await a terminal method to execute the work off the JavaScript thread. All processing runs lazily—nothing executes until a terminal method like `.bytes()`, `.buffer()`, `.blob()`, or `.toBase64()` is awaited. Documentation Index Fetch the complete documentation index at: https://bun.com/docs/llms.txt Use this file to discover all available pages before exploring further. 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. 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. 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, } ; 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" } ; filter selects the resampling kernel. The default "lanczos3" is the right choice for photographs. 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 } ; 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 ; //