
2026/05/24 7:57
Bun.Image
RSS: https://news.ycombinator.com/rss
要約▶
Japanese Translation:
Summary:
本文は、libjpeg-turbo、spng、libwebp および SIMD カーネルを基盤として構築された強力なゼロ依存画像処理ライブラリ Bun.Image を紹介します。Bun.Image は JPEG、PNG、WebP、HEIC、AVIF といった形式のデコード、リサイズ、回転、再エンコードを実行でき、npm の依存関係はゼロです。その主な革新点は、重いデコードとエンコードタスクを JavaScript メインスレッドから直接実行することで、サーバーのスローダウンを防ぎながら、パス、バイト列、Blob などの様々なファイル入力に対して堅牢な扱いを実現することです。Sharp のような API を基盤としており、開発者は変換チェーンをシームレスに組み合わせることができ、不審なデータを検証して任意のファイル読み取り脆弱性をブロックし、厳格なピクセル制限によりデコンプレッションボムを防ぐことができます。macOS、Windows、Linux 間で一貫したエンコード出力を保証し、HEIC/AVIF など特定の形式においてバージョン依存のエラーを回避するために、ユーザーは
Bun.Image.backend = "bun" を設定することでポータブルなバックエンドをグローバルに強制できます。この機能により、デプロイが簡素化され、リソース枯渇攻撃に対するセキュリティが向上し、外部コーデックなしで Bun サーバーのレスポンス本体として直接安全かつ容易に統合可能となります。本文
Bun.Image ドキュメント
概要
Bun.Image は、JPEG、PNG、WebP、HEIC、AVIF の画像を処理するためのチェーン可能(連鎖的)な画像パイプラインです。
- 機能: 暗号化、リサイズ、回転、再エンコード
- 基盤: libjpeg-turbo、spng、libwebp、SIMD 幾何学的計算用コアを使用
- 要件: npm パッケージ不要、ネイティブアドオンビルドステップ不要
API デザイン: Sharp をモデルとしています。入力から構成し、変換を連鎖させ、出力フォーマットを選択後、ターミナルメソッド(終端処理)で待機します。すべての作業はJavaScript スレッドの外側で実行されます。
await Bun.file("photo.jpg").image() .resize(400, 400, { fit: "inside" }) .webp({ quality: 80 }) .write("thumb.webp");
入力
コンストラクタは、パス文字列、バイト列(Buffer)、または Blob を受け付けます。
:ファイルパスnew Bun.Image("./photo.jpg")
:Buffer / ArrayBuffer / TypedArraynew Bun.Image(buffer)
:BunFile(非同期読み込み)new Bun.Image(Bun.file("photo.jpg"))
:上記と同じ機能Bun.file("photo.jpg").image()
:S3FileBun.s3("bucket/photo.jpg").image()
入力に関する注記
- フォーマット検出: フォーマットはバイト内容から自動的に検出されます。拡張子や
ヘッダーは無視されます。Content-Type - セキュリティ警告:
- パス文字列はファイルシステムのパスを指定するため、ユーザー制御可能な文字列をコンストラクタに直接渡さないでください。
- 非信頼入力は Buffer に読み込むなど注意してください。
- TypedArray の安全性:
- ターミナル待機中に TypedArray を操作しないでください(スレッド外部での借用のため)。
- SharedArrayBuffer や変更可能なバッファーは拒否されます。
- 固定されたビューを取得するには
を使用してください。buf.slice()
オプション引数
2 つ目の引数は以下の制御に役立ちます:
new Bun.Image(input, { // 幅×高さがこの値を超えると拒否(デフォルト:Sharp と同様約 268M ピクセル) maxPixels: 4096 * 4096, // JPEG の EXIF オーリエントーションを適用する。デフォルト:true autoOrient: true, });
メタデータ
ピクセルデータをデコードせずに情報を取得できます:
const { width, height, format } = await new Bun.Image(input).metadata(); // => { width: 1920, height: 1080, format: "jpeg" }
リサイズ
:幅を 800 に、アスペクト比維持img.resize(800)
:正確に 800×600(歪ませる)img.resize(800, 600)
:800×600 の範囲内に収める(アスペクト比維持)img.resize(800, 600, { fit: "inside" })
:拡大縮小禁止(アップスケール防止)img.resize(..., { withoutEnlargement: true })img.resize(..., { filter: "mitchell" })
fitBehavior パラメータ
| 挙動 | 説明 |
|---|---|
(デフォルト) | 幅×高さに合わせて正確に引き伸ばす |
| アスペクト比を維持し、結果をコンテナ内に収める |
フィルタ(リサンプリングカーネル)
画像のリサイズ品質を選択します。"lanczos3" がデフォルトで写真用途に適しています。
| フィルタ | 説明 |
|---|---|
(デフォルト) | 汎用用途・最もシャープ(写真向け) |
| 少し柔らかく、リングアーティファクトが少ない |
| スムーズなグラデーション(古典的な Bicubic の妥協案) |
| Catmull-Rom — Mitchell よりシャープだがリングあり得る |
/ | "Magic Kernel Sharp" — Facebook/Instagram 採用 |
/ | 高速で柔らかい処理 |
| 面積平均化(ダウンサイズに最適) |
| ピクセルアート・ハードエッジ向け |
メモ: JPEG ソースで目標サイズが半以下の場合、M/8 IDCT スケールへ跳躍してデコードするため、フル解像度バッファーは保持されません。
回転・反転
:90° 時計回りに回転(90° の倍数のみ)img.rotate(90)
:X 軸中心で上下反転(縦鏡映り)img.flip()
:Y 軸中心で左右反転(横鏡映り)img.flop()
モジュール調整(Modulate)
img.modulate({ brightness: 1.2, // 明るさ(1=変更なし) saturation: 0; // 彩度(0=グレースケール、>1=高彩度) });
出力フォーマット
フォーマットメソッドを呼び出してエンコード設定します。指定しないとソースフォーマットが再利用されます。
| メソッド | シグネチャ | 備考 |
|---|---|---|
| | 品質:1〜100(デフォルト 80) |
| | Zlib レベル 0〜9 |
| | インデックス PNG(256 色以下) |
| | 品質:1〜100 |
| | macOS / Windows 限定 |
| | macOS / Windows 限定 |
パレットモード(インデックス化)
PNG の場合、
palette: true を指定すると 256 色以下のパレットに量子化され、インデックス形式(カラータイプ 3)が出力されます。
- 利点: スクリーンショットや UI アセットでは真彩色表示よりも約 3〜5 倍軽量になります。
ターミナル
パイプラインは、以下いずれかの待機(await)を行うまで作業を実行しません。
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"); // ファイルに書き出し await img.write(Bun.s3("bucket/out.webp"));
.write()
の詳細
.write()
と同じ宛先を受け付けます(パス文字列、Bun.write
、Bun.file()
、ファイルディスクリプター)。Bun.s3()- フォーマットメソッドなしの場合かつ、パス文字列である場合は拡張子でフォーマットを決定します(
/.jpg
など)。.png
出力サイズ確認
最初のターミナルが解決した後、
img.width と img.height は出力サイズを反映します(以前は -1)。
プレースホルダー(LQIP)
本番読み込み前の低品質プレースホルダーとして、.placeholder() を使用できます。
const lqip = await Bun.file("hero.jpg").image().placeholder(); // <img src={lqip} … />
- 機能: ThumbHash レンダリングによる最大 32px のぼかし
- 形式: data URL(約 400〜700 バイト)
- メリット: クライアントサイドでのデコーダー不要、ロード後本物へ切り替え可能
プログレッシブ JPEG
粗粒度から微細なグリディへのレンダリングに使用します:
img.jpeg({ progressive: true });
Bun.serve との連携
Bun.Image パイプラインは有効な Response ボディとなり、Content-Type を自動設定します。サーバーハンドラ内でエンコードを JS スレッド外に保つには、まずターミナルを待機する必要があります:
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)
クリップボード
const img = Bun.Image.fromClipboard(); if (img) { const png = await img.resize(800, 800, { fit: "inside" }).png().bytes(); }
対応機能と制限
- 読み込み: macOS / Windows のシステムペーストボード(PNG、TIFF、HEIC、JPEG、WebP、GIF、BMP)。
- Linux: 常に
を返します。自力でnull
やwl-paste
を呼び出してください。xclip
パッシブ検出パターン
「クリップボードに画像があれば ⌘V で貼り付け」のようなパッシブヒントにする場合:
(整数読み取り)をポーリングclipboardChangeCount()- 変化が生じたら
を呼ぶ(macOS の推奨パターン)hasClipboardImage()
プラットフォームバックエンド
| バックエンド | Linux | macOS | Windows |
|---|---|---|---|
| JPEG / PNG / WebP | libjpeg-turbo · spng · libwebp | 同左 | 同左 |
| BMP / GIF (デコード) | 組み込み | ImageIO | WIC |
| TIFF (デコード) | Highway SIMD | ImageIO² | WIC¹ |
| リサイズ / 回転 / 反転 | Highway SIMD | Accelerate vImage | Highway SIMD |
| HEIC / AVIF | ❌ ERR_IMAGE_FORMAT_UNSUPPORTED | ImageIO ² | WIC ¹ |
| クリップボード | ❌ null を返す | NSPasteboard | Win32 |
¹ Windows: HEIF イメージ拡張機能または AV1 ビデオ拡張機能(Microsoft Store)が必要です。 ² AVIF エンコードには OS の AV1 エンコーダーが必要です(Apple Silicon M3 以降のみ対応)。Intel Mac と M1/M2 は
で拒否されます。AVIF デコードは macOS 13 以降で可能です。ERR_IMAGE_FORMAT_UNSUPPORTED
サポートされないフォーマットの処理
システムバックエンドで対応しない場合、ターミナルは
e.code === "ERR_IMAGE_FORMAT_UNSUPPORTED" をスローします。これをフロップバック(フォールバック)させることができます:
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、クリップボードは OS に依存するため、OS のパッチレベルを継承します。一方、JPEG/PNG/WebP はすべてのプラットフォームで同じ静的リンクコーデックを使用します。
幾何学演算についてもポートbleな Highway パス(バックエンド)を強制するには:
Bun.Image.backend = "bun"; // デフォルトは macOS/Windows 上で "system"