Bitmap with WIC
夏です。夏になりました。
早く雪山行きたいです。板に乗ってぬるぬる滑りたいです。半年時間をスキップできないでしょうか。
でも南半球なら冬だぜフヒヒとかいってオージーとかニュージーにいくと鯨食いとかいってボコボコにされるので怖くていけないです。日本がいいです。
はやく冬になあれ ミ☆
ちゅうことで今回は、Bitmapの読み込みWICバージョンです。
前回は、自力でビットマップを読み込むのがダリーのでGDI+の力を借りて読み込み、表示してみました。
しかし、Direct2DつかってるのにGDI+かよ、GDI+の描画機能使わないのにGDI+使うのかよ、っていうか俺たちもういい大人なんだしGDI+とかいい加減やめようぜ?という気持ちがあったかと思います。
マイクロソフトもきっとそのように思っていたに違いありません。証拠にマックロソフトはVistaからWIC(Windows Imaging Component)というものを入れてきました。
WICはもともとWPF用に設計されたみたいなのですが、それ自体はCOMで提供されており、それをラップしたコンポーネントが.net frameworkに取り入れられてSystem.Windows.Media.Imaging名前空間以下で提供されています。
C++からも、COMで提供されているので当然使えます。Direct2Dと似たような感じで使うことができます。(WPF用に作られたからといってマネージコードというわけではありません)
WICは、XPでも、.net frameworkの新しいのが入っている、自前でWICをインストールする(させる)という環境であれば動きます。当然ながらVistaや7であれば何もしなくても動きます。
Direct2D自体、WDDM前提でXPをほとんど相手にしていないライブラリなので、Direct2DとあわせてWICを使うことをためらう必要はまったくないでしょう。
WICとは、画像の読み込み(デコード)や、書き込み(エンコード)を行うためのライブラリです。
普通の画像読み込みコンポーネントは、対応した画像種しか読み込めないものが多いです。しかしWICは拡張可能でプラグイン可能なのでコーデック(エンコーダ/デコーダ)を書くことができます。
Susieプラグイン対応!みたいなソフトウェアを時々見かけますが、それと同じで、後から対応するエンコーダ/デコーダを入れればソフトウェアは画像の読み込みや書き込みに対応することができます。
Vista以降のWindows自体がWICを使っているらしいので、俺フォーマットの画像をエクスプローラでサムネイル表示する、なんてこともできそうです。
エッチなゲームに入っている謎フォーマット画像のデコーダを気合で書いてしまえば特殊なソフトウェアなんかつかわなくても、そのまま表示できますね!!!!!!!!!!
逆にWICに対応していないとエクスプローラやイメージャでは読めるのに、作ったソフトでは読み込めない、なんてことになってしまいます。
WICはデフォルトで、メジャーな画像形式のエンコーダ/デコーダはほとんど入っており、特に拡張(追加のエンコーダ/デコーダ)がなくても十分使えます。
また、複数のフレームを持つ画像の扱いも簡単に行うことができます。
なんかそれ以外にもGDI+よりセキュアだとかなんだとか角度とか色々と利点があるようですが、割愛します。
ここでは、WICを用いてGDI+のかわりに画像を読み込んでDirect2Dに渡す、ということをやってみたいと思います。
WICについてさらに詳しく知りたい、自分でWIC用のコーデック(エンコーダ、デコーダ)を書いてみたい!という知的探究心のある方へ、以下のリンクを貼っておきます。
Window Imaging Componentの基礎
基礎的なことが書いてあります。本筋以外にもさらっといろいろなものへのポインタがあっていいとおもいます。
WIC対応コーデックの作成方法
上記ワード版
概要からコーデックの作成方法まで書いてあります。結構量があって大変ですが、いいとおもいます。印刷しちゃいました。
本サイトではあくまでDirect2Dが本筋なので、WICの必要以上の解説は行いません。画像読み込めればそれでいいよ。って言う人は本サイトだけで十分かと思います。
(自分の勉強の意味も込めて、いつかはWIC用の入門コーナー(orサイト)つくってみたいなーとはおもっています)
さて、今回のコーディングについてですが、前エントリであるBitmap with GDI+をベースに行います。
具体的には、画像のロード部分を置き換えるだけです。そちらを読んでいない方は、先に目を通しておいてください。
あ、でも前エントリと違ってGDI+は一切使いませんので、GDI+用のヘッダやlibを関連付ける必要は無いですし、GDI+の初期化や終了処理なども必要ありませんので、それらも削除して下さい。
まずはWICを使うための環境構築です。
Visual Studio 2008にはもともとWIC開発用のヘッダやlibなどが含まれているらしいので、特になにもする必要は無い、とのことです。
なんか駄目な場合はWindows SDKをインストールします。んで、Visual C++のディレクトリ設定でヘッダとライブラリパスを追加します。
追加するためのパスはWindows SDKをインストールした先の中を漁っていけばIncludeとかLibというディレクトリがありますのでこれらを追加します。これはWindows SDKに限ったことではなく、だいたいほとんどがこの暗黙のルールに従ったディレクトリ構造になっているので、覚えておいて損はないかと思います。
やってることはDirectX SDKを入れた時と同じですね。何もしないでもWICのプログラミングができる環境でも、どうせだからWindows SDKを入れちゃっていいかもしれません。
ただこの時注意したいのは、DirectX SDKとWindows SDKが両方入っている状態では、どちらにもDirect2D開発用のファイルが入っているので、競合する可能性があります。
なのでディレクトリ設定の優先順位をDirectX SDKのディレクトリがインクルード、ライブラリ共に高くなるようにしておきましょう。
WIC開発に必要なヘッダはwincodec.hとwincodecsdk.hです。
#include <wincodec.h> #include <wincodecsdk.h>
お決まりのように上記2つをインクルードします。
そして、Direct2Dと同じようにlibをリンクする必要があります。リンクする必要があるのはWindowsCodecs.libです。
本サイトでは、何度もlibのリンクをしてきていますので、特に解説はしません。わからない場合は過去のエントリを参照してください。
コードを書くにあたって最初にやらなければならないのはCOMの初期化です。
WICもDirect2DもCOMなのですが、Direct2Dは簡易版COMというやつで、COMの難しいこと気にしたりする必要は無いし、COMの初期化も必要ありませんでした。
しかしWICは簡易版ではなくマジなCOMなので、初期化と終了処理を書いてやる必要があります。
::CoInitialize(NULL);
上記コードをWinMainの最初あたりに記述しましょう。これでCOMの初期化は終わりです。
次にCOMの終了処理を記述します。
::CoUninitialize();
上記コードをWinMainの最後に記述しましょう。これでCOMの終了処理は終わりです。
これでWICを利用するための準備は整いました。
WICを使って画像ファイルを読み込むためには以下の手順で行います。
(複数の方法があるのですが、これはおそらくもっとも簡単な一例です)
・ファクトリを作成する
・ファイル名からデコーダを作成
・デコードされたフレームを取得
・コンバータでDirect2Dフォーマットへ変換
・WICビットマップからDirect2Dビットマップを生成
ファクトリ以外のWICオブジェクトは、ファクトリを通じて生成します。Direct2Dと同じような構造です。
では、まずファクトリから。
グローバル変数としてIWICImagingFactoryを追加します。
CComQIPtr<IWICImagingFactory> g_imagingFactory;
口を酸っぱくしていいますが、実際のコーディングではグローバル変数じゃなくて、メンバにしたりとか色々考えましょう。
WICのファクトリはDirect2Dのファクトリと同じく、プログラム中にたった一度だけ生成して、あとは使いまわします。
次に、このIWICImagingFactoryを生成します。
COMを初期化した後あたりに以下のコードを差し込みましょう。
if(FAILED(::CoCreateInstance(CLSID_WICImagingFactory, NULL, CLSCTX_INPROC_SERVER, IID_IWICImagingFactory, reinterpret_cast<void**>(&g_imagingFactory)))) { ::MessageBox(NULL, L"ImagingFactory作成に失敗", NULL, MB_OK); return -1; }
CoCreateInstanceを使ってCOMインターフェイスを取得しています。まさにCOMです。
これらの詳細については、COMについて調べてください。ここでは、こうすればIWICImagingFactoryが作れるということだけわかれば問題ないかと思います。
そして、肝心の画像の読み込みです。
Bitmap with GDI+のエントリでやったGDI+を使った画像読み込みの部分をそっくりそのまま置き換えます。
/*ファイルからデコーダを作成*/ CComQIPtr<IWICBitmapDecoder> dec; g_imagingFactory->CreateDecoderFromFilename(L"ossan.png", NULL, GENERIC_READ, WICDecodeMetadataCacheOnLoad, &dec); /*フレームを取得*/ CComQIPtr<IWICBitmapFrameDecode> frame; dec->GetFrame(0, &frame); /*コンバータでDirect2D用フォーマットに変換*/ CComQIPtr<IWICFormatConverter> converter; g_imagingFactory->CreateFormatConverter(&converter); converter->Initialize(frame, GUID_WICPixelFormat32bppPBGRA, WICBitmapDitherTypeNone, NULL, 0.f, WICBitmapPaletteTypeMedianCut); /*Direct2D用ビットマップを作成*/ g_target->CreateBitmapFromWicBitmap(converter, NULL, &g_bitmap);
見難くなるのでエラー処理ははしょっています。基本、全部COMなので各関数の戻り値はHRESULTです。FAILEDマクロなどを使って正しくエラー処理を行ってください。
最後のDirect2D用ビットマップを作成のコード以外は、全部WICのコードです。
コメントが解説を表しているので必要以上にここでは述べませんが、ざっとだけ解説します。
まずデコーダの作成です。ファイルからデコーダを作成しています。CreateDecoderFromFilenameを用いるとファイルの中身を見て、ああこれはpngだからpng用のデコーダが必要だな、とそれを返してくれています。
第二引数にNULLを指定していますが、ここにベンダーのヒントを渡すことができます。複数の同じタイプに対するデコーダが合った場合、何を使うか、みたいな。どうでもいいのでNULLにします。
第三引数に読み取りで使いますと指定して、即座にイメージ情報をキャッシュしろという意味で第四引数にWICDecodeMetadataCacheOnLoadを指定しています。ほかにも必要な時に必要なものだけキャッシュしろ、とかそういう指定もできます。
次はフレームの取得について。画像は通常一枚の絵ですが、複数の画像がパックされている画像ファイルもあります。
ここでは複数のフレームを含む画像は想定していないので単純に最初のフレームを取得しています。
そして、コンバータについて。これはDirect2Dが受け取れる形に変換しています。(GUID_WICPixelFormat32bppPBGRA)
そのほかにも色々と指定しています。どういう効果があるのかは実験してみてください。名前からなんとなく推測できます。
そして最後にDirect2D用ビットマップの生成です。これにはCreateBitmapFromWicBitmapを用います。WICのコンバータを渡しているだけです。
以上で、画像の読み込みからDirect2D用ビットマップの生成ができました。
ああそうだ、もうひとつ考慮しておきたい点がありました。
g_imagingFactoryなのですが、CoUninitializeを呼ぶ前にリリースしておきましょう。
g_imagingFactory.Release(); ::CoUninitialize();
なぜかというと、g_imagingFactoryはグローバル変数なので、こいつ(CComQIPtr)のデストラクタが呼ばれるのはアプリケーション終了時です。
このタイミングでは、すでにCoUninitializeが呼び出された後で、つまり終了処理をした後にCOMインターフェイスをリリースすることになってしまいます。
なので、その前に開放しておいてやろうということです。
Direct2Dは簡易版COMなので、CoUninitializeの前だろうが後だろうが関係ありません。
これで完成です。
実行してみてください。前回やったGDI+を使った画像の読み込みの時と同じように画像が表示されていますでしょうか?
これがWICを使った画像の読みです。
もちろんGDI+のほうが慣れているからそっちのほうがいい、という場合はそちらを使っても問題はありませんが、せっかく新しく優れたものがあるのだから、使わない手は無いと思います。
新しいことは良いことだ!