VC++でGDI+ そにょ4 〜画像の読み込み2:リソースからPNGを読み込む〜

リソースからPNGJPEGを読み込む

ゲームを作るにあたって、利用する画像はBitmapじゃちょっとばかし大きすぎる。だから圧縮率も高い上に8bitアルファを持ってるPNGを使いたい。ディレクトリから読み込む場合はユーザに画像を勝手に使われるし、exe単体で配布できない。じゃあリソースにしてしまおう。そういう場合のお話。
PNGなどのリソースから読み込む場合は、Bitmapと他で別の方法をとらなければならない*1。そしてこれがちょいと手間だ。それぞれのWin32APIは知らなければ各自調べほしい。手順はこんな感じになる。

  1. FindResource関数でリソースハンドルを取得
  2. SizeofResource関数でサイズを取得
  3. LoadResource関数でリソースを読み出しLockResource関数でをロック
  4. GlobalAlloc関数でメモリを確保
  5. GlobalLock関数でメモリをロック
  6. CopyMemory関数でリソースを確保したメモリにコピー
  7. CreateStreamOnHGlobal関数でストリームを作成
  8. 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:普通にできる方法があったら教えて欲しい。