VC++でGDI+ そにょ4 〜画像の読み込み2:リソースからPNGを読み込む〜
リソースからPNGやJPEGを読み込む
ゲームを作るにあたって、利用する画像はBitmapじゃちょっとばかし大きすぎる。だから圧縮率も高い上に8bitアルファを持ってるPNGを使いたい。ディレクトリから読み込む場合はユーザに画像を勝手に使われるし、exe単体で配布できない。じゃあリソースにしてしまおう。そういう場合のお話。
PNGなどのリソースから読み込む場合は、Bitmapと他で別の方法をとらなければならない*1。そしてこれがちょいと手間だ。それぞれのWin32APIは知らなければ各自調べほしい。手順はこんな感じになる。
- FindResource関数でリソースハンドルを取得
- SizeofResource関数でサイズを取得
- LoadResource関数でリソースを読み出しLockResource関数でをロック
- GlobalAlloc関数でメモリを確保
- GlobalLock関数でメモリをロック
- CopyMemory関数でリソースを確保したメモリにコピー
- CreateStreamOnHGlobal関数でストリームを作成
- Bitmap(またはImageクラス)のFromStreamメソッドでBitmapオブジェクトを作成
こんな感じで呼び出せる関数を作ってみよう。
Gdiplus::Bitmap *myBitmap = LoadFromResource(IDR_PNG1, "PNG");
ではさっそく。わかりやすいようにMessageBoxで例外処理をしてみる。
Gdiplus::Bitmap* LoadFromResource(UINT pResourceID, LPCTSTR pResourceType, HMODULE hInstance=NULL) { try { LPCTSTR pResourceName = MAKEINTRESOURCE(pResourceID); HRSRC hResource = FindResource(hInstance, pResourceName, pResourceType); if(!hResource) { throw "リソースが見つからないっす。"; } DWORD dwResourceSize = SizeofResource(hInstance, hResource); if(!dwResourceSize) { throw "リソースのサイズが0っす。"; } const void* pResourceData = LockResource(LoadResource(hInstance, hResource)); if(!pResourceData) { throw "リソースが読み込めなかったっす。"; } HGLOBAL hResourceBuffer = GlobalAlloc(GMEM_MOVEABLE, dwResourceSize); if(!hResourceBuffer) { GlobalFree(hResourceBuffer); throw "ヒープにメモリが確保できなかったっす。"; } void* pResourceBuffer = GlobalLock(hResourceBuffer); if(!pResourceBuffer) { GlobalUnlock(hResourceBuffer); GlobalFree(hResourceBuffer); throw "メモリブロックが廃棄されてるかサイズが0っす。"; } CopyMemory(pResourceBuffer, pResourceData, dwResourceSize); IStream* pIStream = NULL; if(CreateStreamOnHGlobal(hResourceBuffer, FALSE, &pIStream)==S_OK) { Gdiplus::Bitmap *pBitmap = Gdiplus::Bitmap::FromStream(pIStream); pIStream->Release(); GlobalUnlock(hResourceBuffer); GlobalFree(hResourceBuffer); if(pBitmap==NULL) { throw "ストリームからBitmapを作成できなかったっす。"; } Gdiplus::Status result = pBitmap->GetLastStatus(); if(result==Gdiplus::Ok) { return pBitmap; } delete pBitmap; throw getGdiplusErrorString(result); } GlobalUnlock(hResourceBuffer); GlobalFree(hResourceBuffer); throw "ストリームの作成に失敗しました。"; } catch(LPCSTR errorContent) { std::string errorMessage = std::string(errorContent); errorMessage += "\n画像を表示しないままプログラムを続行しますか?"; if(MessageBox(NULL, errorMessage.c_str(), NULL, MB_YESNO)==IDYES) { return NULL; } else { exit(1); } } return NULL; }
稚拙なコードで申し訳ない。安全性の指摘等あればよろしくです。
getGdiplusErrorStringはGdiplus::Statusからエラーメッセージを取得する関数だ。例外処理用なので別になくてもいい。
LPCSTR getGdiplusErrorString(Gdiplus::Status errorStatus) { switch(errorStatus) { case Gdiplus::GenericError: return "GdiPlus GenericError"; // (中略) } }
Bitmapリソースとで利用する関数を区別したくない場合は、この関数のはじめの方で
if(pResourceType==RT_BITMAP){ if(hInstance==NULL) { hInstance = GetModuleHandle(NULL); } pBitmap = Gdiplus::Bitmap::FromResource(hInstance, (const WCHAR *)pResourceName); if(pBitmap->GetLastStatus()==Gdiplus::Ok){ return pBitmap; } }
みたいな感じにしてあげると良い。pResourceTypeのデフォルト引数をRT_BITMAPにしておけば、こんな感じで呼び出して使うことができる。
Gdiplus::Bitmap *myBitmap = LoadFromResource(IDB_BITMAP1);
*1:普通にできる方法があったら教えて欲しい。