執行環境套件管理器打包器測試執行器程式碼檢查格式化指南參考部落格意見回饋

影像

使用快速的原生管道解碼、轉換和編碼影像

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"