EternalWindows
ShellImageData / アニメーションの描画

アニメーションGIFのような画像ファイルにはファイル内に複数の画像が格納されていますが、 ShellImageDataではこうしたファイルについても扱うことができます。 ファイル内の画像はページと呼ばれる単位で管理されており、 NextPageを呼び出すことで次のページ(画像)を参照することができます。

HRESULT IShellImageData::NextPage(VOID);

このメソッドに引数はありません。

画像ファイルに格納されているページ数は、GetPageCountで取得することができます。

HRESULT IShellImageData::GetPageCount(
  ULONG *pcPages
);

pcPagesは、ページ数を受け取る変数のアドレスを指定します。 変数に格納された値が1より大きい場合は、アニメーションが可能であると判断できます。

今回のプログラムは、アニメーションGIFを描画しています。

#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;
		DWORD   dwDelay;

		CoInitialize(NULL);

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

		pShellImageData->GetDelay(&dwDelay);
		SetTimer(hwnd, 1, dwDelay, NULL);

		return 0;
	}

	case WM_TIMER: {
		ULONG uCurrentPage, uPageCount;
		
		pShellImageData->GetCurrentPage(&uCurrentPage);
		pShellImageData->GetPageCount(&uPageCount);

		if (uCurrentPage == uPageCount - 1)
			pShellImageData->SelectPage(0);
		else
			pShellImageData->NextPage();

		InvalidateRect(hwnd, NULL, TRUE);

		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:
		KillTimer(hwnd, 1);
		if (pShellImageDataFactory != NULL)
			pShellImageDataFactory->Release();
		CoUninitialize();
		PostQuitMessage(0);
		return 0;

	default:
		break;

	}

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

WM_CREATEでは、GetDelayを呼び出してアニメーションの間隔を取得しています。 これを基にSetTimerを呼び出せば、指定した間隔でWM_TIMERが送られることになるため、 そこで現在のページを次のページに進ませればよいことになります。

case WM_TIMER: {
	ULONG uCurrentPage, uPageCount;
	
	pShellImageData->GetCurrentPage(&uCurrentPage);
	pShellImageData->GetPageCount(&uPageCount);

	if (uCurrentPage == uPageCount - 1)
		pShellImageData->SelectPage(0);
	else
		pShellImageData->NextPage();

	InvalidateRect(hwnd, NULL, TRUE);

	return 0;
}

ページを1つ進ませるにはNextPageを呼び出しますが、 既に最後のページに到達している場合は、SelectPageに0を指定して先頭のページに戻る必要があります。 最後のページかどうかの判定は、GetCurrentPageで取得した現在のページの番号が、 GetPageCountで取得したページ総数-1に一致した場合としています。 InvalidateRectによってWM_PAINTが生成され、新しいページによる描画が行われます。


戻る