EternalWindows
ShellImageData / イメージの描画

グラフィックスを扱うアプリケーション開発において、 数多くのファイル形式に対応することは1つの悩みの種でした。 特にWindows XP以前のWindowsでは、ビットマップを扱う関数ぐらいしか用意されていませんでしたから、 ビットマップ以外のファイルに関しては、アプリケーションがファイルフォーマットを明示的に解析する必要がありました。 しかし、Windows XPからはShellImageDataと呼ばれるCOMベースのAPIが登場し、 これを呼び出すことでPNGやJPEG、GIFなどのファイルを簡単に描画することができます。 MSDNでは、Windows XPから同じく登場したGDI+を使用するように推奨していますが、 ShellImageDataは現在でも依然として使用可能です。

ShellImageDataを使用するには、まずCoCreateInstanceを呼び出してIShellImageDataFactoryを取得します。 そしてこのインターフェースのCreateImageFromFileを呼び出せば、 指定した画像ファイルを表すIShellImageDataを取得することができます。

HRESULT IShellImageDataFactory::CreateImageFromFile(
  LPCWSTR pszPath,
  IShellImageData **ppshimg
);

pszPathは、扱いたい画像ファイルのファイル名またはフルパスを指定します。 ppshimgは、IShellImageData型変数のアドレスを指定します。 このメソッドは、第1引数に存在しないファイル名を指定してもS_OKを返すことに注意してください。

取得したイメージを描画するためには、そのイメージを事前にデコードしておく必要があります。 これには、Decodeを呼び出します。

HRESULT IShellImageData::Decode(
  DWORD dwFlags,
  ULONG cxDesired,
  ULONG cyDesired
);

dwFlagsは、基本的にSHIMGDEC_DEFAULTを指定します。 この場合、イメージはデフォルトの動作でデコードされ、cxDesiredとcyDesiredには0を指定することができます。 一方、dwFlagsにSHIMGDEC_THUMBNAILを指定した場合はサムネイルイメージとしてデコードされ、 cxDesiredとcyDesiredにはデコード後のサイズを指定します。

デコードされたイメージを描画するにはDrawを呼び出します。

HRESULT IShellImageData::Draw(
  HDC hdc,
  LPRECT prcDest,
  LPRECT prcSrc
);

hdcは、デバイスコンテキストのハンドルを指定します。 prcDestは、描画先の位置を格納したRECT構造体のアドレスを指定します。 prcSrcは、描画元の位置を格納したRECT構造体のアドレスを指定します。

今回のプログラムは、ShellImageDataを使用して画像ファイルのイメージを描画します。 カレントディレクトリにsample.pngが存在することを前提としています。

#include <windows.h>
#include <shlobj.h>
#include <shimgdata.h>

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	TCHAR      szAppName[] = TEXT("sample");
	HWND       hwnd;
	MSG        msg;
	WNDCLASSEX wc;

	wc.cbSize        = sizeof(WNDCLASSEX);
	wc.style         = 0;
	wc.lpfnWndProc   = WindowProc;
	wc.cbClsExtra    = 0;
	wc.cbWndExtra    = 0;
	wc.hInstance     = hinst;
	wc.hIcon         = (HICON)LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_SHARED);
	wc.hCursor       = (HCURSOR)LoadImage(NULL, IDC_ARROW, IMAGE_CURSOR, 0, 0, LR_SHARED);
	wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
	wc.lpszMenuName  = NULL;
	wc.lpszClassName = szAppName;
	wc.hIconSm       = (HICON)LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_SHARED);
	
	if (RegisterClassEx(&wc) == 0)
		return 0;

	hwnd = CreateWindowEx(0, szAppName, szAppName, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hinst, NULL);
	if (hwnd == NULL)
		return 0;

	ShowWindow(hwnd, nCmdShow);
	UpdateWindow(hwnd);
	
	while (GetMessage(&msg, NULL, 0, 0) > 0) {
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}

	return (int)msg.wParam;
}

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	static IShellImageDataFactory *pShellImageDataFactory = NULL;
	static IShellImageData        *pShellImageData = NULL;

	switch (uMsg) {

	case WM_CREATE: {
		HRESULT hr;

		CoInitialize(NULL);

		hr = CoCreateInstance(CLSID_ShellImageDataFactory, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pShellImageDataFactory));
		if (FAILED(hr))
			return -1;
		
		hr = pShellImageDataFactory->CreateImageFromFile(L"sample.png", &pShellImageData);
		if (FAILED(hr))
			return -1;
		
		hr = pShellImageData->Decode(SHIMGDEC_DEFAULT, 0, 0);
		if (FAILED(hr)) {
			MessageBox(NULL, TEXT("イメージのデコードに失敗しました。"), NULL, MB_ICONWARNING);
			return -1;
		}

		return 0;
	}

	case WM_PAINT: {
		HDC         hdc;
		RECT        rcDest, rcSrc;
		SIZE        size;
		PAINTSTRUCT ps;

		hdc = BeginPaint(hwnd, &ps);

		pShellImageData->GetSize(&size);
		SetRect(&rcDest, 0, 0, size.cx, size.cy);
		SetRect(&rcSrc, 0, 0, size.cx, size.cy);
		
		pShellImageData->Draw(hdc, &rcDest, &rcSrc);

		EndPaint(hwnd, &ps);

		return 0;
	}

	case WM_DESTROY:
		if (pShellImageDataFactory != NULL)
			pShellImageDataFactory->Release();
		CoUninitialize();
		PostQuitMessage(0);
		return 0;

	default:
		break;

	}

	return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

WM_CREATEではまず、CoCreateInstanceを呼び出してIShellImageDataFactoryを取得しています。 このとき、第1引数にはCLSID_ShellImageDataFactoryを指定します。 次に、CreateImageFromFileを呼び出してIShellImageDataを取得し、 Decodeを呼び出してイメージをデコードします。 デコードは描画の前に1回だけ行えばよいため、WM_PAINTで呼び出す必要はありません。

WM_PAINTでは、Drawを呼び出してイメージの描画を行っています。 GetSizeを呼び出すとイメージのサイズを取得できるため、 これを基に描画先(rcDest)と描画元(rcSrc)の位置を指定しています。 描画元の位置に関しては今回のようになることが多いと思われますが、 描画先の位置に関しては当然プログラムによって異なることになります。 たとえば、GetClientRectでrcDestを初期化したら描画先がクライアント領域全体となり、 イメージは拡大されてクライアント領域全体に描画されることになります。

IShellImageDataには、イメージを回転させるRotateというメソッドもあります。 このメソッドは通常、Decodeを呼び出した後に呼び出すことになると思われます。

pShellImageData->Decode(SHIMGDEC_DEFAULT, 0, 0);
pShellImageData->Rotate(90);

Rotateに指定できる値は、90、180、270のいずれかになっています。


戻る