トップ回答者
CD2DBitmapのリソースの読み込みがうまくいかない

質問
-
CD2DBitmapの画像の読み込みがうまくいかなくて困っています。
CD2DBitmapのコンストラクタには以下の3つを指定できるパターンがあります。
1.リソースID
2.ファイルのパス
3.ビットマップのハンドル
すべてを試してみたところ1と3がうまくいきません。
試したプログラムは以下のようになっています。void CResourceBitmapView::OnDraw(CDC* pDC) { CResourceBitmapDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); if (!pDoc) return; using namespace D2D1; CHwndRenderTarget rt(m_hWnd); rt.BeginDraw(); rt.Clear(ColorF(ColorF::White)); UINT nID = IDB_BITMAP1; CD2DRectF rc(0., 0, 48, 48); // ビットマップのサイズ // 1.リソースID(失敗) CD2DBitmap bmp1(&rt, nID, _T("BITMAP")); HRESULT hr = bmp1.Create(&rt); rt.DrawBitmap(&bmp1, rc); // 2.ファイルのパス(成功) CD2DBitmap bmp2( &rt, _T("res\\Bitmap1.bmp")); hr = bmp2.Create(&rt); rt.SetTransform(D2D1::Matrix3x2F::Translation(0, 48)); rt.DrawBitmap(&bmp2, rc); // 3.ビットマップのハンドル(失敗) CBitmap bmp0; BOOL bLoad = bmp0.LoadBitmap(nID); CD2DBitmap bmp3(&rt, (HBITMAP)bmp0); BOOL bValid = bmp3.IsValid(); //success rt.SetTransform(D2D1::Matrix3x2F::Translation(0, 96)); rt.DrawBitmap(&bmp3, rc); rt.EndDraw(); }
ちなみにリソースの定義は以下のようになっています。
IDB_BITMAP1 BITMAP "res\\bitmap1.bmp"
もしうまく動かせている方がいらっしゃいましたらご教示ください。
回答
-
MFC用のDirect2Dラッパークラスを使う場合、CD2DBitmapのコンストラクタにはBITMAPやICONのリソース(RT_BITMAP, RT_GROUP_ICON)を直接渡してはいけません。MFCの実装
%ProgramFiles(x86)%\Microsoft Visual Studio 11.0\VC\atlmfc\src\mfc\afxrendertarget.cpp
をデバッガなどで追っていけば分かりますが、内部でリソースのストリームをもとにWICのデコーダーを使ってメタ情報を取得しているようです。
実は.rcファイルにBITMAPタイプとして記述されたビットマップファイルは、ビルド時にWindowsビットマップファイルフォーマット固有のヘッダー情報の一部(マジックナンバーなどを含む、先頭14バイトのBITMAPFILEHEADER構造体に相当する領域)が欠落した状態でリンクされてしまうため、このストリームをWICのCreateDecoderFromStream()に渡しても読み込みが失敗します(例えばSizeofResource()の結果がビルド時に使ったビットマップファイルのものとは異なってきます)。
もしリソースIDを指定してロードする場合、独自のカスタムタイプのリソースとして、MFCアプリケーションウィザードが生成した.rc2ファイルに定義します。
例えばID_MY_TEST_IMAGE01, ID_MY_TEST_IMAGE02, ID_MY_TEST_IMAGE03がresource.hにあらかじめカスタム定義されているものとすると、
ID_MY_TEST_IMAGE01 MYRESTYPE_BITMAP "res\\image01.bmp" ID_MY_TEST_IMAGE02 MYRESTYPE_ICON "res\\image02.ico" ID_MY_TEST_IMAGE03 BITMAP "res\\image03_32bpp_bgra.bmp"
のような感じで.rc2ファイルに記述しておき、
CD2DBitmap bmp1(&rt, ID_MY_TEST_IMAGE01, _T("MYRESTYPE_BITMAP"));
bmp1.Create(&rt);
もしくは
CD2DBitmap bmp1(&rt, ID_MY_TEST_IMAGE02, _T("MYRESTYPE_ICON"));
bmp1.Create(&rt);
のように渡します。こちらはWICが対応してさえいればいいので、PNGやJPEG、TIFFなども使えます。MFC内部で自動的にWICを使ってGUID_WICPixelFormat32bppPBGRAとしてコンバートしてくれるので、BGRAだけでなくBGR、BGRXやRGB、RGBAも使えます。
一方、HBITMAPを渡してDirect2Dビットマップを作成する場合、WICのCreateBitmapFromHBITMAP()呼び出し時にWICBitmapUseAlphaを渡しているので、必ずアルファチャンネルを含んだBGRA 32bitビットマップを使う必要があるようです(Adobe Photoshopが生成するようなアルファチャンネルを含まないBGRX 32bitビットマップは使えませんので、他のBGRA 32bitビットマップ対応ツールを使うか、GDIではなくGDI+を使ってPNGからロードしてHBITMAPを作成する必要があります)。CreateCompatibleDC()とCreateCompatibleBitmap()を使って作成した互換ビットマップでの描画が成功していたのはBGRAのフォーマットを満たしていたからだと思われます。
CBitmap mfcBmp; mfcBmp.LoadBitmap(ID_MY_TEST_IMAGE03); CD2DBitmap bmp3(&rt, static_cast<HBITMAP>(mfcBmp));
ちなみに.rcファイル中のBITMAPタイプに対応するリソース識別子はRT_BITMAPです。
_T("BITMAP")は使えませんのであしからず。ID_MY_TEST_IMAGE03は.rc2ファイルではなく.rcファイルに定義してもOKです。
なお、今回はテストコードとのことなのであまり気にする必要はありませんが、CView::OnDraw()のオーバーライドでDirect2Dを使う場合、普通はCDCRenderTargetを使います。CHwndRenderTargetを使う場合、OnDraw()には記述せず、ON_REGISTERED_MESSAGE()マクロを使ってAFX_WM_DRAW2Dのカスタムメッセージハンドラーを登録してLPARAMからCHwndRenderTargetを取得します。このAFX_WM_DRAW2Dを使う方法の場合、レンダーターゲットの生成やBeginDraw()/EndDraw()が不要になりますが、設定したトランスフォーム行列などのステートが保存されて次回のレンダリングにも使われることに注意が必要です。また、D2Dレンダーターゲット、D2Dビットマップ、D2Dブラシなどはデバイス依存のリソースオブジェクトで、GPU側にリソースを確保する関係上GDI/GDI+のペンやブラシと比べて生成負荷が高いため、実際は描画のたびに生成せずにコンストラクタなどで事前生成しておくと効率が良くなります。詳細はチュートリアルを参照してください。
MFCはAPIのヘルプが不十分・不親切なのですが、システムAPIコールの手前まではソースコードが付属している半ホワイトボックスです。仕様がよく分からない場合はブラックボックス的にあれこれ試行錯誤したりする前に、まず自分で内部処理を追っていくとよいと思います。
すべての返信
-
レス付きませんね。
既に解決していたら無視してください。D2D1に詳しいわけではありませんが、このような比較的単純なコードにおいて、
D2D1側に瑕疵がある可能性は低いのではないかと考えられます。で、(2.)以外、つまり(1.)と(3.)は、結果的に当該EXEのリソースに
対象ビットマップが存在することが必要である、という類似点があります。以上から予測すると
A.リソース由来(又はHBITMAP由来)のビットマップは扱えない。
B.EXEのリソースに当該ビットマップが無いか、IDが違う。が考えられます。
そこで、リソース由来ではないHBITMAPを作成して(3.)相当を
やってみてはどうでしょう。例えば、
CBitmap bmp_by_DC;
bmp_by_DC.CreateCompatibleBitmap( pDC, 48, 48);
CD2DBitmap bmp4( &rt, ( HBITMAP)bmp_by_DC);てな感じ。
これが成功するなら(B)の疑いが増す。
失敗するなら(A)の疑いが増す。
と考えられます。- 回答の候補に設定 星 睦美 2014年1月27日 4:48
-
レスありがとうございます。
実際にOnDrawの中でメモリ上でビットマップを生成してハンドルを渡すプログラムを作りました。
// 4.メモリ上でビットマップを生成してハンドルを渡す(成功) CBitmap bmp_by_DC; CDC mdc; mdc.CreateCompatibleDC(pDC); bmp_by_DC.CreateCompatibleBitmap(&mdc, 48, 48); CBitmap* pBackupBmp = mdc.SelectObject(&bmp_by_DC); mdc.TextOut(0, 0, _T("test")); mdc.SelectObject(pBackupBmp); CD2DBitmap bmp4(&rt, (HBITMAP)bmp_by_DC); rt.SetTransform(D2D1::Matrix3x2F::Translation(0, 144)); rt.DrawBitmap(&bmp4, rc);
これはちゃんとtestと表示されましたのでHBITMAPを渡すこと自体は悪くないようです。
次にBの可能性があるのかと思いリソースIDを指定してGDIで描画するプログラムをrt.EndDraw()の後に追加しました。
CBitmap bmpGdi; bmpGdi.LoadBitmap(nID); CDC mdc2; mdc2.CreateCompatibleDC(pDC); CBitmap* pBmp = mdc2.SelectObject(&bmpGdi); pDC->BitBlt(0, 48 * 4, 48, 48, &mdc2, 0, 0, SRCCOPY); mdc2.SelectObject(pBmp);
これも表示されるのでBのリソースがないということはありません。
となると3で失敗する理由が分からなくなりました。なにか他に情報があればお願いいたします。
-
MFC用のDirect2Dラッパークラスを使う場合、CD2DBitmapのコンストラクタにはBITMAPやICONのリソース(RT_BITMAP, RT_GROUP_ICON)を直接渡してはいけません。MFCの実装
%ProgramFiles(x86)%\Microsoft Visual Studio 11.0\VC\atlmfc\src\mfc\afxrendertarget.cpp
をデバッガなどで追っていけば分かりますが、内部でリソースのストリームをもとにWICのデコーダーを使ってメタ情報を取得しているようです。
実は.rcファイルにBITMAPタイプとして記述されたビットマップファイルは、ビルド時にWindowsビットマップファイルフォーマット固有のヘッダー情報の一部(マジックナンバーなどを含む、先頭14バイトのBITMAPFILEHEADER構造体に相当する領域)が欠落した状態でリンクされてしまうため、このストリームをWICのCreateDecoderFromStream()に渡しても読み込みが失敗します(例えばSizeofResource()の結果がビルド時に使ったビットマップファイルのものとは異なってきます)。
もしリソースIDを指定してロードする場合、独自のカスタムタイプのリソースとして、MFCアプリケーションウィザードが生成した.rc2ファイルに定義します。
例えばID_MY_TEST_IMAGE01, ID_MY_TEST_IMAGE02, ID_MY_TEST_IMAGE03がresource.hにあらかじめカスタム定義されているものとすると、
ID_MY_TEST_IMAGE01 MYRESTYPE_BITMAP "res\\image01.bmp" ID_MY_TEST_IMAGE02 MYRESTYPE_ICON "res\\image02.ico" ID_MY_TEST_IMAGE03 BITMAP "res\\image03_32bpp_bgra.bmp"
のような感じで.rc2ファイルに記述しておき、
CD2DBitmap bmp1(&rt, ID_MY_TEST_IMAGE01, _T("MYRESTYPE_BITMAP"));
bmp1.Create(&rt);
もしくは
CD2DBitmap bmp1(&rt, ID_MY_TEST_IMAGE02, _T("MYRESTYPE_ICON"));
bmp1.Create(&rt);
のように渡します。こちらはWICが対応してさえいればいいので、PNGやJPEG、TIFFなども使えます。MFC内部で自動的にWICを使ってGUID_WICPixelFormat32bppPBGRAとしてコンバートしてくれるので、BGRAだけでなくBGR、BGRXやRGB、RGBAも使えます。
一方、HBITMAPを渡してDirect2Dビットマップを作成する場合、WICのCreateBitmapFromHBITMAP()呼び出し時にWICBitmapUseAlphaを渡しているので、必ずアルファチャンネルを含んだBGRA 32bitビットマップを使う必要があるようです(Adobe Photoshopが生成するようなアルファチャンネルを含まないBGRX 32bitビットマップは使えませんので、他のBGRA 32bitビットマップ対応ツールを使うか、GDIではなくGDI+を使ってPNGからロードしてHBITMAPを作成する必要があります)。CreateCompatibleDC()とCreateCompatibleBitmap()を使って作成した互換ビットマップでの描画が成功していたのはBGRAのフォーマットを満たしていたからだと思われます。
CBitmap mfcBmp; mfcBmp.LoadBitmap(ID_MY_TEST_IMAGE03); CD2DBitmap bmp3(&rt, static_cast<HBITMAP>(mfcBmp));
ちなみに.rcファイル中のBITMAPタイプに対応するリソース識別子はRT_BITMAPです。
_T("BITMAP")は使えませんのであしからず。ID_MY_TEST_IMAGE03は.rc2ファイルではなく.rcファイルに定義してもOKです。
なお、今回はテストコードとのことなのであまり気にする必要はありませんが、CView::OnDraw()のオーバーライドでDirect2Dを使う場合、普通はCDCRenderTargetを使います。CHwndRenderTargetを使う場合、OnDraw()には記述せず、ON_REGISTERED_MESSAGE()マクロを使ってAFX_WM_DRAW2Dのカスタムメッセージハンドラーを登録してLPARAMからCHwndRenderTargetを取得します。このAFX_WM_DRAW2Dを使う方法の場合、レンダーターゲットの生成やBeginDraw()/EndDraw()が不要になりますが、設定したトランスフォーム行列などのステートが保存されて次回のレンダリングにも使われることに注意が必要です。また、D2Dレンダーターゲット、D2Dビットマップ、D2Dブラシなどはデバイス依存のリソースオブジェクトで、GPU側にリソースを確保する関係上GDI/GDI+のペンやブラシと比べて生成負荷が高いため、実際は描画のたびに生成せずにコンストラクタなどで事前生成しておくと効率が良くなります。詳細はチュートリアルを参照してください。
MFCはAPIのヘルプが不十分・不親切なのですが、システムAPIコールの手前まではソースコードが付属している半ホワイトボックスです。仕様がよく分からない場合はブラックボックス的にあれこれ試行錯誤したりする前に、まず自分で内部処理を追っていくとよいと思います。
-
実は.rcファイルにBITMAPタイプとして記述されたビットマップファイルは、ビルド時にWindowsビットマップファイルフォーマット固有のヘッダー情報の一部(マジックナンバーなどを含む、先頭14バイトのBITMAPFILEHEADER構造体に相当する領域)が欠落した状態でリンクされてしまうため、このストリームをWICのCreateDecoderFromStream()に渡しても読み込みが失敗します(例えばSizeofResource()の結果がビルド時に使ったビットマップファイルのものとは異なってきます)。
そういう仕様なのですね。それですっきりしました。実際にカスタムタイプのリソースとして定義したらちゃんと表示されました。
また、HBITMAPを利用する場合、アルファチャンネルを含んだBGRA 32bitビットマップを作れなかったので試せませんでしたが、WICBitmapUseAlphaの仕様に左右されるということですね。
その他にも内部の仕様やRenderTargetの使い方やMFCのソースの追い方などの貴重なアドバイスもいただきありがとうございました。