2024年4月7日日曜日

C# FormアプリでMicrosoftのライブラリだけでheic画像表示

WPFではサポートされてもFormアプリでは使えない機能があります。

Imageのheic対応もそのひとつで、次のようなコードでheicファイルを表示しようとするとメモリ不足のエラーが発生します。

pictureBox1.Image = Image.FromFile(mediaItem.FilePath);

Webでheic表示関連を検索するとMagick.NETを使う例が見つかりますが、Microsoftのライブラリだけで行う方法はまだ見つかりませんでした。Magick.NETは様々なフォーマットを扱える優れたものだと思いますが、dllをアプリに含めると、小さいなアプリでは本体よりMagick.NETの方が大きくなってしまいます。heicサポートの追加だけが目的な場合はあまり好ましくないので、Microsoftのライブラリだけで行う方法をいくつか試してみました。

なお、前提としてWindows 10, 11にHEIF画像拡張をインストールしてあるものとします。HEIF画像拡張がインストールされていない場合はMicrosoft Store から ダウンロードできます。

方法1
BitmapImage
BitmapEncoderを使用する。
今のところ、これが一番良好な結果になっています。
次のようなステップで行います。

System.Windows.Media.Imaging.BitmapImageをファイルから作る。BmpBitmapEncoderでMemoryStreamに書き出す。
MemoryStreamからSystem.Drawing.Imageを作る。
pictureBox1.Imageにセットする。

コードは次のようになります。

            BitmapImage bImage = new BitmapImage();
            bImage.BeginInit();
            bImage.CacheOption = BitmapCacheOption.None;
            bImage.DecodePixelHeight = height;
            bImage.DecodePixelWidth = width;
            bImage.UriSource = new Uri(FilePath, UriKind.Absolute);
            bImage.EndInit();
            using (MemoryStream ms = new MemoryStream())
            {
                BitmapEncoder enc = new BmpBitmapEncoder();
                enc.Frames.Add(BitmapFrame.Create(bImage));
                enc.Save(ms);
                pictureBox1.Image = Image.FromStream(ms);
            }

ひとつの静止画を扱うなら、これで十分でしょう。イメージのサイズを設定できるので、メモリ消費も必要最低限に抑えられ、スピードもこれ以上早くするのは難しいでしょう。

また、書き出し先をファイルにすればフォーマット変換にも使えます。

System.Windows.Media.Imagingのための参照追加 は次のとおりです。

プロジェクトの参照右クリック⇒参照の追加⇒アッセンブリ
PresentationCoreをチェック⇒OK

方法2
BitmapDecoder
BitmapEncoderを使用する。

BitmapDecoderの使い方はよくわかっていませんが、複数のFrameを持ち、Animationなども扱えます。複雑な画像表示を行えるようですが、ひとつの静止画だけを扱うなら方法1で十分そうです。もしかしたら有用かもしれないので紹介しておきます。

ステップは方法1のBitmapImageがSystem.Windows.Media.Imaging.BitmapDecoderに置き換わっただけです。

コードは次のようになります。

            BitmapDecoder uriBitmap = BitmapDecoder.Create(
                new Uri(FilePath, UriKind.Absolute),
      BitmapCreateOptions.None,
                BitmapCacheOption.Default);
            using (MemoryStream ms = new MemoryStream())
           {
               BitmapEncoder enc = new BmpBitmapEncoder();
               enc.Frames.Add(uriBitmap.Frames[0]);
               enc.Save(ms);
               pictureBox1.Image = Image.FromStream(ms);
           }

調べた限りでは、この流れでは画像サイズを指定できません。そのため、高解像度画像ではメモリ消費が多くなり、スピードも少し遅くなります。

方法3
 AxWMPLib.AxWindowsMediaPlayerを使用する。

MediaPlayerは静止画も表示でき、HEIF 画像拡張インストールされていればheicフォーマットも表示できます。ちなみにHEVCビデオも再生できます。静止画を扱う場合は uiMode="none"が良いでしょう。

メリットとしては、まずは静止画も動画も同じインタフェイスで扱えることです。また、対応していないフォーマットや壊れたファイルのエラー対応もMediaPlayerまかせにできます。

これまでのFormアプリと同じ要領で使え、次の要領でコントロールにファイルのパスを設定するだけなので、お手軽といえます。

(AxWMPLib.AxWindowsMediaPlayer)player.URL = filePath;

VisualStudioでMediaPlayerを使う方法は次のリンクを参照してください。
Microsoft Visual Studio で Windows メディア プレーヤー コントロールを使用する
https://draft.blogger.com/blog/post/edit/6538117324271148932/209217070553378409#

デメリットとしては、方法1,2に比べると遅いこと、メモリ消費量も特に高解像度のheicの場合は多くなります。

HEIFコーディックの有無チェック 
Magick.NETと違い、HEIFコーディックがないとエラーになります。
以下の要領でHEIFコーディックの有無がチェックできます。 

bool HeifDllExists = Registry.GetValue(@"HKEY_CLASSES_ROOT\CLSID\{E9A4A80A-44FE-4DE4-8971-7150B10A5199}\InprocServer32", null, null) != null;

参照:HEIF Format Overview
https://learn.microsoft.com/ja-jp/previous-versions/windows/desktop/legacy/mt846532(v=vs.85)