[Astro] #126 HEIC / HEIF Image Converter — libheif WASMによるブラウザ完結型バッチ変換ツール

[Astro] #126 HEIC / HEIF Image Converter — libheif WASMによるブラウザ完結型バッチ変換ツール

概要

iPhoneで撮影した写真のデフォルト形式であるHEIC/HEIFを、JPEG/PNG/WebPへブラウザだけで変換するツールを実装しました。

コアにはlibheif(C/C++製のHEIF/AVIFデコーダ)のEmscripten WASMビルドであるlibheif-jsを使用。デコード処理はすべてブラウザ内で完結し、画像ファイルがサーバーに送信されることはありません。

複数ファイルの一括変換(バッチ処理)をメインのユースケースとして設計しており、サムネイルグリッド表示・ZIP一括ダウンロードに対応しています。

スクリーンショット

[Astro] #126 HEIC / HEIF Image Converter — libheif WASMによるブラウザ完結型バッチ変換ツール

動画(GIF)

[Astro] #126 HEIC / HEIF Image Converter — libheif WASMによるブラウザ完結型バッチ変換ツール

機能一覧

カテゴリ内容
入力形式HEIC, HEIF, HIF, AVIF
出力形式JPEG(品質スライダー), PNG, WebP(品質スライダー)
バッチ変換複数ファイル一括読み込み・一括変換・進捗表示
サムネイルグリッドデコード済み画像の縮小プレビューをグリッド表示、サイズ可変スライダー(80〜300px)
プレビューサムネクリックでフル解像度表示、Canvas pan/zoom(ホイール・ドラッグ)、Fit/1:1切替
ダウンロード個別ダウンロード / JSZipによるZIP一括ダウンロード
ファイル情報ファイル名、サイズ、解像度、デコード時間、圧縮率
D&Dドロップゾーン + ビューポートへのドラッグ&ドロップ対応

1. libheif WASM — デコードパイプライン

ライブラリ構成

HEICファイルのデコードには以下のライブラリチェーンを使用しています。

  • libheif — ISO/IEC 23008-12 HEIF/AVIFコンテナのパーサ
  • libde265 — HEVC(H.265)デコーダ
  • libheif-js — 上記をEmscriptenでWASMにコンパイルし、npmモジュールとして配布

CDN経由でESMバンドル版(libheif-bundle.mjs)を動的importし、WASMの別途配置を不要にしています。

デコードフロー

File → ArrayBuffer → Uint8Array
  → HeifDecoder.decode() → Image[]
  → image.display() → RGBA Uint8ClampedArray
  → Canvas putImageData → toBlob(JPEG/PNG/WebP)

HeifDecoder.decode()はHEIFコンテナ内の全画像を配列で返します(Live Photosやバースト撮影では複数画像が格納される場合がある)。現在は先頭画像(images[0])を取得し、image.display()のコールバックでRGBAピクセルデータを受け取ります。

2. バッチ変換アーキテクチャ

メモリ管理

大量のHEICファイル(100枚以上)を扱うケースを想定し、以下の設計にしています。

  • シーケンシャル処理 — 1枚ずつデコード→変換→Blob化→次のファイルへ。並列デコードはメモリを爆発させるため意図的に避けている
  • サムネイルは縮小版 — フルサイズ(4000×3000=48MBのRGBA)をDOMに並べるのではなく、200px以下に縮小したJPEG dataURLを生成
  • UIスレッド保護 — 変換ループ内でsetTimeout(10)を挟み、ブラウザのレスポンスを維持
  • Canvas即時破棄 — 変換用の一時Canvasはスコープ内で使い捨て、GCに任せる

ZIPダウンロード

変換済みBlobをメモリに保持し、全件完了後にJSZipesm.sh経由で動的import)でZIPファイルを生成。個別ダウンロードとZIPダウンロードの切替はチェックボックスで制御。

3. サムネイルグリッド

動的要素のスタイリング

Astroのscoped CSSは、テンプレートに存在するHTML要素にのみdata-astro-cid-*属性を付与します。JavaScriptで動的にinnerHTMLで生成した要素にはこの属性がつかないため、scoped CSSのセレクタにマッチしません。

本ツールではサムネイルカードを全てinline styleで生成することで、この問題を完全に回避しています。

'<div style="background:#161616;border:1px solid #222;...">'
  + '<div style="width:100%;aspect-ratio:1;...">'
  + '<img src="' + thumbUrl + '" style="width:100%;height:100%;object-fit:contain;">'

サイズ可変スライダー

左パネルのThumbスライダー(80px〜300px)でCSS Gridのgrid-template-columnsminmax値をリアルタイム変更。

grid.style.gridTemplateColumns =
  'repeat(auto-fill,minmax(' + size + 'px,1fr))';

4. プレビューオーバーレイ

Canvas pan/zoom

サムネイルクリックでHEICファイルを再デコードし、フル解像度のImageDataをCanvasに描画。マウスホイールでズーム(カーソル位置中心)、ポインタドラッグでパン。Fit/1:1ボタンで表示切替。

display:flex → fitView() の遅延

プレビューオーバーレイはdisplay:noneからdisplay:flexに切り替えた直後、子要素のサイズがまだ0の状態でfitView()が呼ばれると真っ黒になるバグがありました。requestAnimationFrameを2段ネストすることで、レイアウト計算完了後にfitを実行しています。

overlay.style.display = 'flex';
requestAnimationFrame(function() {
  requestAnimationFrame(function() {
    fitView();
  });
});

5. HEICフォーマットについて

HEIC(High Efficiency Image Container)はAppleがiOS 11以降のデフォルト撮影形式として採用した画像フォーマットです。内部コーデックにHEVC(H.265)を使用し、JPEGの約2倍の圧縮効率を実現します。

しかし、Windows・Android・多くのWebサービスではネイティブサポートが限定的で、「iPhoneの写真が開けない」という需要が常にあります。既存のオンライン変換サービスの大半はサーバーアップロード型ですが、本ツールはWASMでブラウザ完結するため、プライバシー面で優位です。

技術スタック

  • libheif-js — libheif + libde265 のEmscripten WASMビルド(CDN ESM import)
  • Canvas 2D — プレビュー描画(pan/zoom)、サムネイル生成、フォーマット変換(toBlob
  • JSZip — ZIP一括ダウンロード(esm.sh経由の動的import)
  • Astro — 静的サイト生成、scoped CSS + inline styleハイブリッド

今後の展望

WASM系ツール3連作の第1弾として実装しました。次はTesseract WASM(OCR)、sql.js(SQLite DBビューア)と続く予定です。

HEIC Converter自体の拡張としては、EXIF情報の表示・保持(exifr等)、AVIF出力対応(libheifのエンコード機能活用)、Web Worker化による完全非同期デコードなどが考えられます。