运行时包管理器打包器测试运行器代码检查格式化指南参考博客反馈

图像

使用快速的原生管道解码、转换和编码图像

Bun.Image 是一个可链式调用的图像管道,用于解码、调整大小、旋转以及重新编码 JPEG、PNG、WebP、HEIC 和 AVIF。它基于 libjpeg-turbo、spng、libwebp 和 SIMD 几何内核构建,无需 npm 依赖,也无需构建原生插件。

TypeScript
await Bun.file("photo.jpg").image().resize(400, 400, { fit: "inside" }).webp({ quality: 80 }).write("thumb.webp");

此 API 参考了 Sharp 的设计:从输入构造实例,链式调用转换,选择输出格式,然后 await 一个终结方法。只有在你等待终结方法时才会执行操作,且处理会在 JavaScript 线程之外执行。

输入

构造函数接受路径、字节或 Blob——包括 Bun.file() 和 Bun.s3.file()。Blob#image() 是 new Bun.Image(blob) 的简写:

TypeScript
new Bun.Image("./photo.jpg"); // 文件路径
new Bun.Image(buffer); // Buffer / ArrayBuffer / TypedArray
new Bun.Image(Bun.file("photo.jpg")); // BunFile(延迟读取、在线程外执行)
Bun.file("photo.jpg").image(); // 与上面相同
Bun.s3.file("bucket/photo.jpg").image(); // S3File

Bun 会从字节中检测格式,并忽略扩展名和 Content-Type。

**路径字符串是文件系统路径。**不要将用户控制的字符串直接传给构造函数——这会产生任意文件读取原语。请先使用 fetch 或 Bun.file 将不受信任的输入读入 Buffer,并进行你自己的验证,然后再传入这些字节。

传入 TypedArray/ArrayBuffer 时,终结方法正在执行期间不要修改它——解码会在线程外运行,并借用这些字节。Bun 不接受 SharedArrayBuffer 和可调整大小的缓冲区;请使用 buf.slice() 传入固定视图。

当 libjpeg-turbo 仅发出警告就能完成解码时,Bun 会解码损坏的 JPEG。例如,段之间有多余字节、缺少图像结束标记,以及扫描数据被截断。libjpeg-turbo 会将没有数据的块填充为纯灰色。渐进式文件若在第一次扫描后被截断,则每个块都有数据,因此 Bun 会返回细节较少的完整图像。当 libjpeg-turbo 无法完成解码时,终结方法会以 ERR_IMAGE_DECODE_FAILED 拒绝。metadata() 只读取头部,因此即使终结方法会拒绝,它也可能成功解析。

第二个 options 参数用于防护解压炸弹并控制 EXIF 处理:

TypeScript
new Bun.Image(input, {
  // 如果 width*height > 这个值则拒绝。会在读取头部后、
  // 分配像素缓冲区之前检查。默认值与 Sharp 一致(约 268 MP)。
  maxPixels: 4096 * 4096,
  // 在任何其他操作之前应用 JPEG EXIF 方向。默认:true。
  autoOrient: true,
});

元数据

无需解码像素数据即可读取 width、height 和 format:

TypeScript
const { width, height, format } = await new Bun.Image(input).metadata();
// => { width: 1920, height: 1080, format: "jpeg" }

调整大小

TypeScript
img.resize(800); // 宽度为 800,保持宽高比
img.resize(800, 600); // 精确为 800×600(拉伸)
img.resize(800, 600, { fit: "inside" }); // 适配到 800×600 内
img.resize(800, 600, { withoutEnlargement: true }); // 永不放大
img.resize(800, 600, { filter: "mitchell" });
fit行为
"fill"(默认)拉伸到精确的 width × height
"inside"保持宽高比;结果适配于该边界内

filter 用于选择重采样内核。默认的 "lanczos3" 是照片的最佳选择。

滤镜适用场景
"lanczos3" (默认)通用,照片最锐利
"lanczos2"略柔和,振铃伪影更少
"mitchell"平滑渐变;经典的双三次折中方案
"cubic"Catmull-Rom——比 Mitchell 更锐利,可能产生振铃
"mks2013" / "mks2021"“Magic Kernel Sharp”;Facebook/Instagram 使用
"bilinear" / "linear"快速,柔和
"box"区域平均;适合大倍率整数缩小
"nearest"像素风格 / 硬边缘

当源图像是 JPEG 且目标尺寸不超过源图像的一半时,解码会直接跳到最接近的 M/8 IDCT 缩放级别。因此,从一张 24 MP 照片生成缩略图时,不会创建全分辨率缓冲区。

旋转 · 翻转

TypeScript
img.rotate(90); // 顺时针旋转 90°(仅支持 90 的倍数)
img.flip(); // 垂直镜像(绕 x 轴)
img.flop(); // 水平镜像(绕 y 轴)

调节

TypeScript
img.modulate({
  brightness: 1.2, // 1 = 不变
  saturation: 0, // 0 = 灰度,1 = 不变,>1 = 增强
});

输出格式

调用格式方法会设置编码目标;如果没有调用格式方法,Bun 会沿用源格式。

TypeScript
img.jpeg({ quality: 85 }); // 1–100,默认 80
img.png({ compressionLevel: 6 }); // zlib 级别 0–9
img.png({ palette: true, colors: 64, dither: true }); // 索引 PNG
img.webp({ quality: 80 });
img.webp({ lossless: true });
img.heic({ quality: 80 }); // 仅 macOS / Windows
img.avif({ quality: 60 }); // 仅 macOS / Windows

palette: true 会将图像量化为不超过 256 种颜色的调色板,并输出索引(color-type 3)PNG,可选择使用 Floyd–Steinberg dither。对于屏幕截图和 UI 资源,索引 PNG 通常比真彩色图像小 3–5 倍。

终结方法

管道在等待以下任一方法之前不会执行任何操作:

TypeScript
await img.bytes(); // Uint8Array
await img.buffer(); // Buffer
await img.blob(); // Blob,.type 设置为输出 MIME
await img.toBase64(); // string
await img.dataurl(); // "data:image/png;base64,…"
await img.write("out.webp"); // number(写入的字节数)
await img.write(Bun.s3.file("bucket/out.webp"));

.write() 接受与 Bun.write 相同的目标类型——路径字符串、Bun.file()、Bun.s3.file() 或文件描述符。如果你没有链式调用格式方法,并且目标是路径字符串,则扩展名会决定格式(.jpg/.png/.webp/.heic/.avif)。

占位符

.placeholder() 会返回一个低质量占位图,可在真实图像加载前内嵌到 HTML 中。占位图是以 data: URL 呈现的 ThumbHash 渲染的 ≤32px 模糊图像。大小约为 400–700 字节,无需客户端解码器:

TypeScript
const lqip = await Bun.file("hero.jpg").image().placeholder();
// <img src={lqip} … /> — 然后在加载时切换到真实 URL。

如果想对图像 本身 进行由粗到细的渲染,请编码为渐进式 JPEG:

TypeScript
img.jpeg({ progressive: true });

第一个终结方法解析后,img.width 和 img.height 反映的是 输出 尺寸(在此之前它们为 -1)。

Bun.serve 集成

Bun.Image 管道是一个有效的 Response body,并会自动设置 Content-Type。为了在服务端处理程序中让编码保持在 JS 线程之外,请先 await 一个终结方法:

TypeScript
Bun.serve({
  routes: {
    "/avatar/:id": async req => {
      // 在接触文件系统之前先验证(见上面的输入说明)。
      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);
    },
  },
});

直接传递管道(new Response(img))同样有效,但会在正文初始化期间同步执行编码。

剪贴板

TypeScript
const img = Bun.Image.fromClipboard();
if (img) {
  const png = await img.resize(800, 800, { fit: "inside" }).png().bytes();
}

fromClipboard() 会从 macOS 和 Windows 的系统剪贴板读取 PNG、TIFF、HEIC、JPEG、WebP、GIF 或 BMP;之后再交给常规解码管道处理。如果没有图像则返回 null,而在 Linux 上始终返回 null——请自行调用 wl-paste/xclip 并将字节传给构造函数。

对于“剪贴板中有图像,按 ⌘V”这种被动提示,请轮询 clipboardChangeCount()(一次整数读取),并仅在值发生变化时调用 hasClipboardImage()。轮询是文档推荐的模式,因为 macOS 没有剪贴板更改通知。

平台后端

LinuxmacOSWindows
JPEG / PNG / WebPlibjpeg-turbo · spng · libwebpsamesame
BMP / GIF (decode)built-inImageIOWIC
TIFF (decode)❌ImageIOWIC
Resize / rotate / flipHighway SIMDAccelerate vImage ³Highway SIMD
HEIC / AVIF❌ ERR_IMAGE_FORMAT_UNSUPPORTEDImageIO ²WIC ¹
Clipboard❌ returns nullNSPasteboardWin32

¹ Windows 需要从 Microsoft Store 获取 HEIF Image Extensions / AV1 Video Extension。 ² AVIF 编码 需要操作系统 AV1 编码器——仅限 Apple Silicon M3+。Intel Mac 和 M1/M2 会以 ERR_IMAGE_FORMAT_UNSUPPORTED 拒绝;AVIF 解码 在 ImageIO 支持的所有环境中均可使用(macOS 13+)。 ³ 仅在使用默认 lanczos3 滤镜时,调整大小才会使用 vImage;在 macOS 上,其他所有 filter 都会使用 Highway SIMD。

当当前计算机上无法使用系统后端格式时,终结方法会以 error.code === "ERR_IMAGE_FORMAT_UNSUPPORTED" 拒绝。请根据该错误代码分支处理,以回退到可移植格式:

TypeScript
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;
  });

由系统后端处理的格式(TIFF、HEIC、AVIF、剪贴板)会采用操作系统的补丁级别,因此请保持 macOS / Windows 为最新版本。JPEG、PNG 和 WebP 在所有平台上都使用相同的静态链接编解码器,因此在 Linux、macOS 和 Windows 上编码的输出逐字节相同。若要让几何处理也强制使用可移植的 Highway 路径(例如用于黄金图像测试),请设置进程全局后端:

TypeScript
Bun.Image.backend = "bun"; // macOS/Windows 上默认是 "system"