Bun.Image

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")
    :ファイルパス
  • new Bun.Image(buffer)
    :Buffer / ArrayBuffer / TypedArray
  • new Bun.Image(Bun.file("photo.jpg"))
    :BunFile(非同期読み込み)
  • Bun.file("photo.jpg").image()
    :上記と同じ機能
  • Bun.s3("bucket/photo.jpg").image()
    :S3File

入力に関する注記

  • フォーマット検出: フォーマットはバイト内容から自動的に検出されます。拡張子や
    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" }

リサイズ

  • img.resize(800)
    :幅を 800 に、アスペクト比維持
  • img.resize(800, 600)
    正確に 800×600(歪ませる)
  • img.resize(800, 600, { fit: "inside" })
    :800×600 の範囲内に収める(アスペクト比維持)
  • img.resize(..., { withoutEnlargement: true })
    :拡大縮小禁止(アップスケール防止)
  • img.resize(..., { filter: "mitchell" })

fitBehavior パラメータ

挙動説明
"fill"
(デフォルト)
幅×高さに合わせて正確に引き伸ばす
"inside"
アスペクト比を維持し、結果をコンテナ内に収める

フィルタ(リサンプリングカーネル)

画像のリサイズ品質を選択します。"lanczos3" がデフォルトで写真用途に適しています。

フィルタ説明
"lanczos3"
(デフォルト)
汎用用途・最もシャープ(写真向け)
"lanczos2"
少し柔らかく、リングアーティファクトが少ない
"mitchell"
スムーズなグラデーション(古典的な Bicubic の妥協案)
"cubic"
Catmull-Rom — Mitchell よりシャープだがリングあり得る
"mks2013"
/
"mks2021"
"Magic Kernel Sharp" — Facebook/Instagram 採用
"bilinear"
/
"linear"
高速で柔らかい処理
"box"
面積平均化(ダウンサイズに最適)
"nearest"
ピクセルアート・ハードエッジ向け

メモ: JPEG ソースで目標サイズが半以下の場合、M/8 IDCT スケールへ跳躍してデコードするため、フル解像度バッファーは保持されません。


回転・反転

  • img.rotate(90)
    :90° 時計回りに回転(90° の倍数のみ)
  • img.flip()
    :X 軸中心で上下反転(縦鏡映り)
  • img.flop()
    :Y 軸中心で左右反転(横鏡映り)

モジュール調整(Modulate)

img.modulate({
  brightness: 1.2, // 明るさ(1=変更なし)
  saturation: 0;   // 彩度(0=グレースケール、>1=高彩度)
});

出力フォーマット

フォーマットメソッドを呼び出してエンコード設定します。指定しないとソースフォーマットが再利用されます。

メソッドシグネチャ備考
.jpeg()
img.jpeg({ quality: 85 })
品質:1〜100(デフォルト 80)
.png()
img.png({ compressionLevel: 6 })
Zlib レベル 0〜9
.png()
img.png({ palette: true, colors: 64, dither: true })
インデックス PNG(256 色以下)
.webp()
img.webp({ quality: 80 })
品質:1〜100
.heic()
img.heic({ quality: 80 })
macOS / Windows 限定
.avif()
img.avif({ quality: 60 })
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()
の詳細

  • 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 で貼り付け」のようなパッシブヒントにする場合:

  1. clipboardChangeCount()
    (整数読み取り)をポーリング
  2. 変化が生じたら
    hasClipboardImage()
    を呼ぶ(macOS の推奨パターン)

プラットフォームバックエンド

バックエンドLinuxmacOSWindows
JPEG / PNG / WebPlibjpeg-turbo · spng · libwebp同左同左
BMP / GIF (デコード)組み込みImageIOWIC
TIFF (デコード)Highway SIMDImageIO²WIC¹
リサイズ / 回転 / 反転Highway SIMDAccelerate vImageHighway SIMD
HEIC / AVIF❌ ERR_IMAGE_FORMAT_UNSUPPORTEDImageIO ²WIC ¹
クリップボード❌ null を返すNSPasteboardWin32

¹ Windows: HEIF イメージ拡張機能または AV1 ビデオ拡張機能(Microsoft Store)が必要です。 ² AVIF エンコードには OS の AV1 エンコーダーが必要です(Apple Silicon M3 以降のみ対応)。Intel Mac と M1/M2 は

ERR_IMAGE_FORMAT_UNSUPPORTED
で拒否されます。AVIF デコードは macOS 13 以降で可能です。

サポートされないフォーマットの処理

システムバックエンドで対応しない場合、ターミナルは

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"

同じ日のほかのニュース

一覧に戻る →

2026/05/24 3:45

私の Writerdeck を語る時が来ました。

## 日本語翻訳: 著者は、6年経った System76 Galago Pro ラップトップを「writerdeck」と名づけたオフライン書写ステーションに変換し、X11、Wayland、およびデスクトップ環境を排する tty ベースの構成で Debian Trixie を実行することでミニマリズムを優先しています。コンテンツが公開共有を目的としているためフルディスク暗号化は省略され、管理には `sudo` ユーザーモデルに切り替えて root ログインが無効化されました。本質的なツールとして、Neovim がテキスト編集に使用され(従来のエディタに代わり)、Debian バックポートからの `kmscon` でスケーラブルなターミナルウィンドウを可能にし、セッション多重化には `tmux` を使用し、インストール済みの Network Manager 経由の `nm-tui` が Wi-Fi/WAN の管理に用いられます。電力モニタリングおよび画面明るさ制御は `acpi` と `light` コマンドで行われ、自動ログインと `.bashrc`内の起動スクリプトにより Neovim が `tmux` セッション内で動作し、ブート時に Vimwiki が起動するようにしています。Syncthing は Vimwiki フォルダをリモートサーバーに同期させ、ブラウザ GUI を必要とせずオフラインファーストなワークスペースを維持するために全てのネットワークアドレスを活用しています。この構成はデジタルの雑多さを削減し、悪い書写習慣を打破助けるとともに、クリエイティブ専門家にとってセキュリティと生産性を向上させます。

2026/05/24 7:25

自分でロールを作るな

## 日本語訳: 要約: 開発者は、暗号化やユーザーインターフェースコンポーネントといった重要な機能に対して独自の実装を即時に停止する必要があります。なぜなら、「自分自身でつくる」というソリューションは過去の実績が証明する通り危険であるためです。最も重要な教訓は、安全性と使い勝手を確保するために、自作コードの代わりに既成でピアレビューされた標準を採用しなければならないことです。独自のカスタム暗号化パッケージには、初期化の不備や予測可能なパターンなどの深刻な欠陥を内包しており、規制産業では財務規制に違反し、高額の罰則を引き起こす可能性があります。セキュリティの問題だけでなく、ネイティブブラウザ要素を置換することは性能低下をもたらし、過剰な JavaScript ロジックによりキーボードスクロールの破損やリンクの読み込み遅延といった問題を引き起こします。さらに、独自のソリューションは自動入力機能など、安全なパスワード管理などの重要なネイティブ機能を排除してしまいます。この傾向は、組み込みブラウザ機能の安定性よりも創造的なツールの構築を優先しており、ユーザー(高齢者のご家族も含まれます)が慣れ親しんだツールを常に再学習することを強いられています。これらの使い勝手に関する落とし穴を防ぎ、規制当局による罰金を避けるためには、開発者はネイティブ要素を置換するのではなく補完する方向へ転換すべきです。これにより、すべてのウェブサイトで一貫した動作を確保できます。

2026/05/21 6:21

私の二画面デスクセットアップ(2025)

## 日本語訳: 著者は、壁を向いた単調なパソコンデスクを部屋の方を向くように回転させることで(ドアを見渡させ、奥行きを追加)、そしてテック専用だった単一の面を、二つの明確なゾーニングに分かれた大規模な USM ハラーデスクに置き換えることにより、ハイブリッドなワークスペースへと成功裡に変化させています。このデザインは、ソーシャルメディアでのフィードバックを受けてから以前の壁を向いた配置が持っていた「古い」外観に対応し、また、未使用のアイテム、おもちゃ、プロジェクトをアナログ側の面に置き続けることで、ストイックなミニマリズムの限界を解決し、アイデアを刺激することを可能にしています。単純にチェアを二つの半分の間で移動させることで強化された明確な精神的境界線により、このセットアップは一つの表面上で「作業」「思考」「子供たちと過ごす時間」という 3 つの異なる機能を発揮します。9 ヶ月の使用後、著者は単一コンピューターの配置に戻る予定はないことを確認しており、むしろミニマリスト的な規律とマキシマリスト的な柔軟性を融合させたこの柔軟なシステムを維持する意欲を持っています。これにより、別々の部屋を必要とせずに、ワークフローと創造的アウトプットを向上させることができます。