[Astro] #126 HEIC / HEIF Image Converter — libheif WASMによるブラウザ完結型バッチ変換ツール
概要
iPhoneで撮影した写真のデフォルト形式であるHEIC/HEIFを、JPEG/PNG/WebPへブラウザだけで変換するツールを実装しました。
コアにはlibheif(C/C++製のHEIF/AVIFデコーダ)のEmscripten WASMビルドであるlibheif-jsを使用。デコード処理はすべてブラウザ内で完結し、画像ファイルがサーバーに送信されることはありません。
複数ファイルの一括変換(バッチ処理)をメインのユースケースとして設計しており、サムネイルグリッド表示・ZIP一括ダウンロードに対応しています。
HEIC / HEIF Image Converter
libheif WASMによるHEIC/HEIF画像のブラウザ変換。バッチ一括変換、サムネイルグリッド、ZIPダウンロード対応。
lain-lab.comスクリーンショット
動画(GIF)
機能一覧
| カテゴリ | 内容 |
|---|---|
| 入力形式 | 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をメモリに保持し、全件完了後にJSZip(esm.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-columnsのminmax値をリアルタイム変更。
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化による完全非同期デコードなどが考えられます。