モノトーンの伝説日記

Apex Legends, Splatoon, Programming, and so on...

WICの対応フォーマットの判定(C#)

〈更新履歴〉
2021/5/28: AVIF 対応のビルド番号判定追加及び,Build 19041 (2004), 19042 (20H2), 19043 (21H1) 向け hotfix KB5003214 で拡張子 `.hif` に対応したことによる拡張子の追加に対応

 WIC (Windows Imaging Component) で WebP や HEIF (AVIC, HEIC, AVIF) に対応できますが,それの対応フォーマットに関するお話。

1. 初期からインストールされているもの (判定不要)

といったもの。これらは WIC が追加された XP SP3 (SP2 は要ダウンロード), Vista から使えます。なお,JPEG XR (旧 Windows Media Photo, HD Photo) は Vista の一部で使えないらしいので,古いソフトだと一応確認したほうがいいかもですね。

2. WebP や HEIF などの追加フォーマット

 昨今では Microsoft Store に拡張機能としてこれらの機能が使えるようになっています。

 注意すべきは HEIF なんですよね。あくまでこれはコンテナー (MP4 のようなもの) なんですよね。一応拡張子として .heif ってのもあります。ただ,中身のコーデックに応じて拡張子を変えることもできます。

  • AVCI (H.264/AVC): .avci, .avcs
  • HEIC (H.265/HEVC): .heic, .heics
  • AVIF (AV1): .avif, .avifs

 判定方法として better なのは,

  1. CLSID_WICHeifDecoder がアクティベートできるのかをまず確認。OK なら AVCI はデコード可能。
  2. HKEY_CLASSES_ROOT\ActivatableClasses\PackageMicrosoft.HEVCVideoExtension_ から始まるキーが存在するかどうか。OK なら HEIC はデコード可能。
  3. HKEY_CLASSES_ROOT\ActivatableClasses\PackageMicrosoft.AV1VideoExtension_ から始まるキーが存在するかどうか。OK なら AVIF はデコード可能。

といった感じになると思います。結構めんどくさいですが,この手順を踏めば拡張子の有効無効判定のオンオフを実装できるかと。

 一応サンプルコード貼っておきますが,一部例外周りのコードそのままじゃ動かないので,本番コードでは書き換えてください。

public abstract class ImageFormatSupportDetector
{
  private bool? _isSupported = null;

  public bool IsSupported
  {
    get
    {
      if (!this._isSupported.HasValue)
      {
        this._isSupported = this.GetValue();
      }
      return this._isSupported.Value;
    }
  }

  public abstract string[] Extensions { get; }

  public abstract string FileType { get; }

  public abstract bool GetValue();
}

public abstract class ClsidImageFormatSupportDetector : ImageFormatSupportDetector
{
  public abstract Guid CLSID { get; }

  public override bool GetValue()
  {
    try
    {
      var decoderType = Type.GetTypeFromCLSID(CLSID);
      var decoder = Activator.CreateInstance(decoderType);
      return true;
    }
    catch (COMException ex) when (ex.Match(HResult.REGDB_E_CLASSNOTREG))
    {
      return false;
    }
  }
}

public sealed class HEIFSupportDetector : ClsidImageFormatSupportDetector
{
  private bool? _isHIFExtensionSupported;

  public bool IsHIFExtensionSupported
  {
    get
    {
      if (!this._isHIFExtensionSupported.HasValue)
      {
        if (Environment.OSVersion.Version.Build >= 21301)
        {
          this._isHIFExtensionSupported = true;
        }
        else if (Environment.OSVersion.Version.Build >= 19041)
        {
          const string targetHootfixId = "KB5003214";
          const string query = "SELECT HotFixID FROM Win32_QuickFixEngineering";

          bool supported = false;
          var searcher = new System.Management.ManagementObjectSearcher(query);
          foreach (var hotfix in searcher.Get())
          {
            if (hotfix["HotFixID"].ToString() == targetHootfixId)
            {
              supported = true;
              break;
            }
          }
          this._isHIFExtensionSupported = supported;
        }
        else
        {
          this._isHIFExtensionSupported = false;
        }
      }
      return this._isHIFExtensionSupported.Value;
    }
  }

  private bool? _isHEVCSupported;

  public bool IsHEVCSupported
  {
    get
    {
      if (!this._isHEVCSupported.HasValue)
      {
        const string targetKeyName = "Microsoft.HEVCVideoExtension_";

        this._isHEVCSupported = Registry.ClassesRoot
          .OpenSubKey("ActivatableClasses")
          .OpenSubKey("Package")
          .GetSubKeyNames()
          .Any(name => name.StartsWith(targetKeyName));
      }
      return this._isHEVCSupported.Value;
    }
  }

  private bool? _isAV1Supported;

  public bool IsAV1Supported
  {
    get
    {
      if (!this._isAV1Supported.HasValue)
      {
        const string targetKeyName = "Microsoft.AV1VideoExtension_";

        this._isAV1Supported = Registry.ClassesRoot
          .OpenSubKey("ActivatableClasses")
          .OpenSubKey("Package")
          .GetSubKeyNames()
          .Any(name => name.StartsWith(targetKeyName));
      }
      return this._isAV1Supported.Value;
    }
  }

  public bool IsAVIFSupported
  {
    get
    {
      if (!this._isAVIFSupported.HasValue)
      {
        this._isAVIFSupported = Environment.OSVersion.Version.Build >= 18305 && this.IsAV1Supported;
      }
      return this._isAVIFSupported.Value;
    }
  }

  public override string[] Extensions
  {
    get
    {
      var extensions = new Collection<string>();
      if (this.IsHIFExtensionSupported)
      {
        extensions.Add(".hif");
      }
      extensions.Add(".heif");
      extensions.Add(".heifs");
      extensions.Add(".avci");
      extensions.Add(".avcs");
      if (this.IsHEVCSupported)
      {
        extensions.Add(".heic");
        extensions.Add(".heics");
      }
      if (this.IsAVIFSupported)
      {
        extensions.Add(".avif");
        extensions.Add(".avifs");
      }
      return extensions.ToArray();
    }
  }

  public override string FileType
  {
    get
    {
      return this.IsAVIFSupported
        ? this.IsHEVCSupported
          ? "HEIF (AVCI, HEIC, AVIF)"
          : "HEIF (AVCI, AVIF)"
        : this.IsHEVCSupported
          ? "HEIF (AVCI, HEIC)"
          : "HEIF (AVCI)";
    }
  }

  // CLSID_WICHeifDecoder
  public override Guid CLSID => new Guid(0xe9a4a80a, 0x44fe, 0x4de4, 0x89, 0x71, 0x71, 0x50, 0xb1, 0x0a, 0x51, 0x99);
}

 もっといい判定方法ってあるんですかね? あったら教えてください。

7/30 追記: AUMID を検索するのに,レジストリーを使った手法があるようなので,インストールされているかの有無の判定はとりあえずこれで悪くなさそうですね。API あったらそっち使ったほうがもっといい感じにかけるな,と思っていたんですけど。

docs.microsoft.com

参考資料

 WIC GUIDs のページ。

docs.microsoft.com

 wikipedia: High Efficiency Image File Format - Wikipedia