猫の手ツール

Step 3: Core Technology

17. iPhoneのHEIC画像が
ブラウザで表示できない問題

HEICを表示できるかはブラウザ、OS、画像の構成で変わります。このサイトの画像変換には、必要なときにheic2anyを取得する分岐があります。採用版と実際の変換順を確認し、対応範囲やメタデータを残せるかを、公式資料と区別して読んでいきます。

1. HEICとは何か?なぜブラウザで表示できないのか

HEIFは画像を格納するコンテナー形式で、HEVCで符号化した画像に .heic の拡張子がよく使われます。iPhoneの高効率設定で保存された写真などが該当します。画質とサイズの関係は画像や設定によって異なります。

表示できるかはブラウザ・OS・画像の符号化方式に依存します。Safari 17では対応OS上でHEIC表示が追加されており、「Safariでも必ず表示できない」という説明は正確ではありません。アプリでは対応状況を確認し、非対応画像の変換やエラー表示を用意します。

2. ブラウザ内の解決策:heic2any の導入

この画像変換の入力処理は、選択したFileをブラウザ内のデコーダーとCanvasへ渡します。この処理のために画像をサーバーへ送信するコードは含めていません。

このサイトでは、ブラウザ上で動作するJavaScriptライブラリ heic2any を使います。これはlibheifを利用したデコード処理をブラウザ側で行い、JPEGやPNGなどへ変換するライブラリです。利用するバージョンの実装と対応範囲を確認し、WebAssemblyを使うかどうかだけで性能を判断しないようにします。

実装:HEICを検知して自動変換するロジック

ユーザーがファイルを選択した際、MIMEタイプと拡張子を手がかりに、変換を試す対象を判定できます。これらは形式を保証する検査ではなく、実際のデコード失敗も扱う必要があります。

【具体的なコード】heic2any による非同期変換

// ファイルが選択された時のハンドラ
async function handleImageSelect(file) {
    let targetFile = file;

    // MIMEタイプが image/heic なら変換実行
    if (/^image\/(heic|heif)$/i.test(file.type) || /\.hei[cf]$/i.test(file.name)) {
        try {
            // ローディングインジケーターを表示
            showSpinner("iPhone形式の画像を変換中...");

            const result = await heic2any({
                blob: file,
                toType: "image/jpeg",
                quality: 0.8
            });

            // 複数画像の場合は先頭の1枚を扱う方針の例
            const convertedBlob = Array.isArray(result) ? result[0] : result;
            if (!(convertedBlob instanceof Blob)) throw new Error("画像がありません");
            const baseName = file.name.replace(/\.[^.]+$/, "");
            targetFile = new File([convertedBlob], `${baseName}.jpg`, {
                type: "image/jpeg"
            });
        } catch (error) {
            showErrorMessage("画像の変換に失敗しました。");
            return;
        } finally {
            hideSpinner();
        }
    }

    // 変換した場合はJPEG、変換対象外なら元の形式として読み込む
    processImage(targetFile);
}

3. 注意点と落とし穴:リソース管理とUX

変換を組み込むときは、処理の場所、取得コスト、保持するデータを確認します。

  • CPU負荷と変換時間: HEICのデコードは非常に重い処理です。特にスペックの低いスマートフォンでは、画像サイズや端末によって変換時間が大きく変わります。ユーザーが「フリーズした」と誤解しないよう、明確なローディング表示(スピナーなど)が必須です。
  • ライブラリのサイズ: 画像デコーダーを含むため、ライブラリの取得・解析コストがあります。必要な時だけ動的にインポート(Dynamic Import)するなどの工夫をしないと、ページ全体の読み込みが遅くなる可能性があります。
  • メモリ消費: 大きな画像(4K以上など)を変換すると、ブラウザのメモリを一時的に大量消費します。不要なBlobへの参照やBlob URLを保持し続けない設計が必要です。Blob自体に明示的なfreeメソッドがあるわけではありません。

4. 変換に失敗した時も、次の操作を案内する

変換の処理が失敗しても、処理中表示を終え、画像を選び直せる状態へ戻します。利用者には失敗した段階を短く伝えます。形式の非対応と、ライブラリを取得できない通信エラーは、次に試す操作が違います。

対応画像をそのまま読み込む入口を用意する一方、変換失敗や形式の差がある場合は元画像を残し、別形式で試せる案内を用意します。

変換後に確認すること

この例は heic2any を読み込んだ環境を前提とし、showSpinner・showErrorMessage・hideSpinner・processImage はアプリ側のUI関数です。複数画像を含む入力は先頭1枚だけ使う例なので、全画像の保存が必要なら配列を個別に扱います。

  • 出力の向き、色、画素数が意図どおりかを元画像と見比べる。
  • 撮影日時や位置情報などのメタデータが保持されると仮定しない。
  • 破損ファイルや非対応画像では、JPEG/PNGへ変換して再選択する方法を案内する。

拡張子を .jpg に変えるだけでは変換になりません。原本を残し、加工用のコピーを作って確認します。

採用版0.0.4と、本番の変換分岐を確認する

画像変換ツールで確認した採用版はheic2any 0.0.4です。本番は拡張子がheic/heifの場合に動的importし、JPEG品質0.9で変換します。複数結果なら先頭の1枚を後続のCanvasへ渡す設計です。

const heicModule = await import('heic2any');
const heic2any = heicModule.default || heicModule;
const converted = await heic2any({ blob: file, toType: 'image/jpeg', quality: 0.9 });
processBlob = Array.isArray(converted) ? converted[0] : converted;

本番の抜粋で、npmで導入済みのビルド環境を前提にします。独立したVite等の開発プロジェクトならnpm.cmd install [email protected]で版を固定します。単独HTMLへimport文だけを貼って動く構成ではありません。制作解説では変換後のCanvas処理へ進みます。

まとめ

HEIC問題は、単なるフォーマットの違いではなく、OSの壁とブラウザの限界がぶつかり合うフロントエンドの難所です。ブラウザで使える画像デコーダーを利用すれば、対応するHEIC画像を端末内で変換できます。ただし、すべてのHEIFの構成やメタデータをそのまま維持できるとは限りません。

動作する入力を一つ確認した後、別の画像構成・ブラウザ・保存方法を追加で試します。どこまで確認したかを記録すると、対応形式の説明を実際の結果に合わせられます。

参考資料

heic2any公式プロジェクト(英語)(別タブで開く)は、メタデータを維持する用途が主目的ではないこと、ブラウザ環境が必要なことを説明しています。同READMEのブラウザ対応に関する古い記述は、WebKit公式のSafari 17.0更新(英語)(別タブで開く)のHEIC対応追加と照合します。この記事では採用コードを確認していますが、すべてのHEIC入力やiPhone実機での変換成功を測定したものではありません。

制作時に確かめたこと

ツールを作る途中で実際に確認した症状と、変更を選んだ経緯です。日付は当時の記録日です。現在の実装や、このページの学習サンプルの検証とは分けて読んでください。

HEIC変換を必要な時に読み込むまで

型エラー・取得方法・通信の説明を一緒に見直す

ツールによってheic2anyの読み込み方が違い、一方では暗黙のanyに関する型エラーが出るのに、別のツールのstatic importでは出ない状態がありました。3月28日には、CDNのURLを使うimportをnpmパッケージのimportへ変える方法を確認しています。

4月2日には必要な時だけawait importする方式を検討し、「ツールを読み込んだ後に通信を切れば動く」という説明も修正対象にしました。まだ取得していないモジュールが必要になるため、端末内処理とオフライン利用は同じ条件ではありません。

4月6日には、const heicModule = await import('heic2any');という読み込み方法でも動いたと確認しています。これはその時の読み込み方法についての報告です。全ツールへの統一や、すべてのHEIC画像の変換成功を確認したという意味ではありません。現在の画像変換も、本文で説明した通り、対象形式の場合に動的importする構成です。