DirectXはゲーム・マルチメディア用のライブラリである。その一部であるDirect2Dは比較的簡単に扱えるコンポーネントである。しかし、Xbox Oneなどのゲーム機でも使えるよう構築されているため、Windowsのリソースに簡単にアクセスすることができないようである。ここではWindowsの画像ファイルをDirect2Dの機能を使って描画する方法を解説する。画像ファイルのデータ形式にはBMP(Microsoft Windows Bitmap Image)、JPEG(Joint Photographic Experts Group )、PNG(Portable Network Graphics)などがあります。GDI+を使うとこれらを簡単に読み込むことができるため、ファイルの読み込みにはGDI+を使用します。使用する言語はC++である。
D2D1CreateFactory関数はDirect2Dリソースを作成できるオブジェクトを作成する。そのオブジェクトからCreateHwndRenderTargetメソッドを呼び出すことでウィンドウにレンダリングできるID2D1HwndRenderTargetインターフェースを作成し、以降はこのインターフェースを使っていく。CreateBitmapメソッドはビットマップを表すID2D1Bitmapインターフェースを作成する。ウィンドウプロシージャ内のWM_PAINTメッセージが来たら、BeginDrawメソッドからEndDrawメソッドの間に、ID2D1Bitmapを引数に指定してDrawBitmapメソッドを呼び出しウィンドウに画像を表示する。WindowsAPIのBeginPain関数とEndPaind関数の間にBitBlt関数で画像を表示するのと同じ要領です。これが、Direct2Dの主な流れである。
問題となるのは、CreateBitmapメソッドの部分です。WindowsAPIであれば画像ファイルへのパスを指定して画像オブジェクトを作成できるところだが、あいにくCreateBitmapメソッドではWindowsのファイルへアクセスすることはできない。第2引数srcDataにはイメージデータのポインターを指定するが、画像のデータをメモリに読み込んで用意する必要がある。
GDI+のBitmap::FromFileメソッドは表示したい画像ファイルパスを引数にBitmapオブジェクトを作成する。このオブジェクトをLockBitsメソッドを呼び出すことで画像データのポインターを取得できる。これをメモリ上に読み出し、これでようやくID2D1HwndRenderTargetインターフェースのCreateBitmapメソッドを呼び出すことができるのである。
#include <Windows.h>#include <d2d1.h>#pragma comment(lib, "d2d1")#include <gdiplus.h>#pragma comment(lib, "gdiplus.lib")#include <math.h> // ceil// WindowsAPIやDirect2Dライブラリとの誤解を防ぐため、個別にusingする。//using namespace Gdiplus;ID2D1Factory* g_pD2dFactory;ID2D1HwndRenderTarget* g_pRenderTarget;template<class Interface>inline void SafeRelease( Interface **ppInterface){ if (*ppInterface != NULL) { (*ppInterface)->Release(); (*ppInterface) = NULL; }}void MsgBoxAndExitByError(const wchar_t* msg){ MessageBoxW(0, msg, L"エラーが発生したので終了します。", 0); exit(0);}void LoadBitmapByGdiplus(ID2D1Bitmap** ppBitmap, UINT* width, UINT* height){ Gdiplus::Bitmap* bmp = Gdiplus::Bitmap::FromFile(L"C:\\img.png"); if (!(bmp && bmp->GetLastStatus() == Gdiplus::Ok)) { MsgBoxAndExitByError(L"画像ファイルを読み込めませんでした。"); } UINT w = *width = bmp->GetWidth(); UINT h = *height = bmp->GetHeight(); // 画像イメージの読み込み Gdiplus::Rect rect(0, 0, w, h); Gdiplus::BitmapData bmpData; bmp->LockBits(&rect, Gdiplus::ImageLockModeRead, PixelFormat32bppARGB, &bmpData); int stride = w * 4; byte* memory = new byte[stride * h]; for (UINT y = 0; y < h; ++y) memcpy(memory + y * stride, (byte*)bmpData.Scan0 + y * bmpData.Stride, stride); bmp->UnlockBits(&bmpData); delete bmp; g_pRenderTarget->CreateBitmap( D2D1::SizeU(w, h), memory, stride, D2D1::BitmapProperties(D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_IGNORE)), ppBitmap); delete[] memory; memory = 0;}LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam){ UINT width, height; static ID2D1Bitmap* s_pBitmap; static UINT s_pBitmapWidth; static UINT s_pBitmapHeight; HRESULT hr; switch (msg) { case WM_CREATE: return 1; case WM_SIZE: width = LOWORD(lParam); height = HIWORD(lParam); if (g_pRenderTarget) { g_pRenderTarget->Resize(D2D1::SizeU(width, height)); } return 0; break; case WM_PAINT: RECT rc; if (!g_pRenderTarget) { GetClientRect(hwnd, &rc); D2D1_SIZE_U size = D2D1::SizeU( rc.right - rc.left, rc.bottom - rc.top ); HRESULT hr = g_pD2dFactory->CreateHwndRenderTarget( D2D1::RenderTargetProperties(), D2D1::HwndRenderTargetProperties(hwnd, size), &g_pRenderTarget ); if (hr == D2DERR_RECREATE_TARGET) { hr = S_OK; SafeRelease(&g_pD2dFactory); } if (s_pBitmap == NULL) { LoadBitmapByGdiplus(&s_pBitmap, &s_pBitmapWidth, &s_pBitmapHeight); } } g_pRenderTarget->BeginDraw(); D2D1_RECT_F rectf = D2D1::RectF( 0.0f, 0.0f, static_cast<float>(s_pBitmapWidth), static_cast<float>(s_pBitmapHeight) ); g_pRenderTarget->DrawBitmap(s_pBitmap, rectf); hr = g_pRenderTarget->EndDraw(); if (hr == D2DERR_RECREATE_TARGET) { SafeRelease(&g_pRenderTarget); } ValidateRect(hwnd, NULL); return 0; case WM_DESTROY: SafeRelease(&s_pBitmap); PostQuitMessage(0); break; } return DefWindowProc(hwnd, msg, wParam, lParam);}#ifndef HINST_THISCOMPONENT EXTERN_C IMAGE_DOS_HEADER __ImageBase;#define HINST_THISCOMPONENT ((HINSTANCE)&__ImageBase) #endifconst wchar_t* const kClassName = L"SimpleDirect2DLaodImage";const wchar_t* const kTitle = kClassName;int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int){ HeapSetInformation(NULL, HeapEnableTerminationOnCorruption, NULL, 0); if (!SUCCEEDED(CoInitialize(NULL))) return 0; HRESULT hr = D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, &g_pD2dFactory); if (!SUCCEEDED(hr)) { MsgBoxAndExitByError(L"Direct2Dファクトリ オブジェクトの作成に失敗しました。"); } unsigned long gdiplusToken; Gdiplus::GdiplusStartupInput gdiplusStartupInput; if (Gdiplus::GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL) != Gdiplus::Ok) { MsgBoxAndExitByError(L"GDI+の初期化に失敗しました。"); } WNDCLASSEXW wcex = { sizeof(WNDCLASSEXW) }; wcex.style = CS_HREDRAW | CS_VREDRAW; wcex.lpfnWndProc = WndProc; wcex.cbClsExtra = 0; wcex.cbWndExtra = sizeof(LONG_PTR); wcex.hInstance = HINST_THISCOMPONENT; wcex.hbrBackground = NULL; wcex.lpszMenuName = NULL; wcex.hCursor = LoadCursor(NULL, IDI_APPLICATION); wcex.lpszClassName = kClassName; RegisterClassExW(&wcex); FLOAT dpiX, dpiY; g_pD2dFactory->GetDesktopDpi(&dpiX, &dpiY); HWND hwnd = CreateWindowW( kTitle, kClassName, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, static_cast<UINT>(ceil(640.f * dpiX / 96.f)), static_cast<UINT>(ceil(480.f * dpiY / 96.f)), NULL, NULL, HINST_THISCOMPONENT, 0 ); if (!hwnd) { MsgBoxAndExitByError(L"ウィンドウの作成に失敗しました。"); } ShowWindow(hwnd, SW_SHOWNORMAL); UpdateWindow(hwnd); MSG msg; while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } SafeRelease(&g_pD2dFactory); SafeRelease(&g_pRenderTarget); Gdiplus::GdiplusShutdown(gdiplusToken); CoUninitialize(); return 0;}このプログラムでは、C:\\img.png に画像を用意していることになっているので、そこに画像ファイルを用意するか、読み込みたい画像ファイルをがあるパスに変更してほしい。実行すると画像を読み込み表示される。パスを間違うとフリーズしたまま終了するようである。
ウィンドウサイズを変えても問題なく画像が表示されることも確認してほしい。
ソースコードのLoadBitmapByGdiplus関数は画像を読み込み、ID2D1Bitmapインターフェースにビットマップを読み込ませる関数である。
関数引数は全て出力に使われる。* widthと *heightは読み込んだ画像の幅と高さを出力する。
void LoadBitmapByGdiplus(ID2D1Bitmap** ppBitmap, UINT* width, UINT* height){次にGDI+の機能でファイルを読み込む
Gdiplus::Bitmap* bmp = Gdiplus::Bitmap::FromFile(L"C:\\img.png");エラーチェックを行う。パスが間違っているなどのエラー時は、MsgBoxAndExitByError関数でメッセージボックスの表示とexit(0)を行っているが効かないようである。
if (!(bmp && bmp->GetLastStatus() == Gdiplus::Ok)) { MsgBoxAndExitByError(L"画像ファイルを読み込めませんでした。"); }幅と高さの変数定義と引数への書き込みを行う。
UINT w = *width = bmp->GetWidth(); UINT h = *height = bmp->GetHeight();LockBitsメソッドを呼び出すための準備を行う。
Gdiplus::Rect rect(0, 0, w, h); Gdiplus::BitmapData bmpData;LockBitsメソッドで画像データのポインタを取得する。
bmp->LockBits(&rect, Gdiplus::ImageLockModeRead, PixelFormat32bppARGB, &bmpData);
ピクセルフォーマットPixelFormat32bppARGBは1ピクセルにつき4バイトなので、1行のデータバイトは幅を4倍すればよい。
int stride = w * 4;一時的に書き出すnewメモリを確保する。
byte* memory = new byte[stride * h];
画像を1行ごとにGDI+からnewメモリへコピーする。
for (UINT y = 0; y < h; ++y) memcpy(memory + y * stride, (byte*)bmpData.Scan0 + y * bmpData.Stride, stride);アンロックとGDI+画像の解放処理を行う。
bmp->UnlockBits(&bmpData); delete bmp;画像データを引数で指定して、ID2D1Bitmapインターフェースを作成する。
g_pRenderTarget->CreateBitmap( D2D1::SizeU(w, h), memory, stride, D2D1::BitmapProperties(D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_IGNORE)), ppBitmap);newメモリを解放する。
delete memory; memory = 0;}