EternalWindows
ビットマップ / スクリーンキャプチャ

ビットマップのフォーマットを理解していれば、 単にビットマップファイルからデータを取得するだけではなく、 ビットマップを保存することもできます。 今回は、スクリーン(デスクトップ)に表示されている画像をキャプチャし、 これをファイルとして保存するプログラムを作成します。 まずは、キャプチャに使用できる関数を確認します。

// デスクトップの壁紙をhdcに描画する。メモリデバイスコンテキストの指定は不可。
PaintDesktop(hdc);

// デスクトップの壁紙とアイコンをhdcに描画する。GetDesktopWindowの指定は不可。
PrintWindow(GetShellWindow(), hdc, 0);

// デスクトップの壁紙とアイコンに加えて、表示されている他のウインドウも描画する。
BitBlt(hdc, 0, 0, nWidth, nHeight, GetWindowDC(GetDesktopWindow()), 0, 0, SRCCOPY);

3番目の方法が最も多く使用されているのではないかと思われます。 GetDesktopWindowは、デスクトップのウインドウハンドルを返す関数であり、 このハンドルをGetWindowDCに指定すれば、デスクトップのデバイスコンテキストを取得することができます。 後は、これをBitBltに指定すれば、アプリケーションが用意したデバイスコンテキストに、 デスクトップの内容がコピーされることになります。

今回のプログラムは、デスクトップに表示されている画像をビットマップファイルとして保存します。

#include <windows.h>

void DrawCursor(HDC hdc);
BOOL WriteBitmap(LPTSTR lpszFileName, int nWidth, int nHeight, LPVOID lpBits);
HBITMAP CreateBackbuffer(int nWidth, int nHeight);

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	HDC     hdc;
	HWND    hwndDesk;
	RECT    rc;
	BITMAP  bm;
	HBITMAP hbmp;
	HBITMAP hbmpPrev;

	hwndDesk = GetDesktopWindow();
	GetWindowRect(hwndDesk, &rc);

	hdc = CreateCompatibleDC(NULL);
	hbmp = CreateBackbuffer(rc.right, rc.bottom);
	hbmpPrev = (HBITMAP)SelectObject(hdc, hbmp);

	BitBlt(hdc, 0, 0, rc.right, rc.bottom, GetWindowDC(hwndDesk), 0, 0, SRCCOPY);
	DrawCursor(hdc);

	GetObject(hbmp, sizeof(BITMAP), &bm);
	if (WriteBitmap(TEXT("capture.bmp"), rc.right, rc.bottom, bm.bmBits))
		MessageBox(NULL, TEXT("ファイルを作成しました。"), TEXT("OK"), MB_OK);
	else
		MessageBox(NULL, TEXT("ファイルの作成に失敗しました。"), NULL, MB_ICONWARNING);

	SelectObject(hdc, hbmpPrev);
	DeleteObject(hbmp);
	DeleteDC(hdc);

	return 0;
}

void DrawCursor(HDC hdc)
{
	int        x, y;
	CURSORINFO cursorInfo;
	ICONINFO   iconInfo;

	cursorInfo.cbSize = sizeof(CURSORINFO);
	GetCursorInfo(&cursorInfo);

	GetIconInfo(cursorInfo.hCursor, &iconInfo);

	x = cursorInfo.ptScreenPos.x - iconInfo.xHotspot;
	y = cursorInfo.ptScreenPos.y - iconInfo.yHotspot;
	DrawIcon(hdc, x, y, cursorInfo.hCursor);
}

BOOL WriteBitmap(LPTSTR lpszFileName, int nWidth, int nHeight, LPVOID lpBits)
{
	HANDLE           hFile;
	DWORD            dwResult;
	DWORD            dwSizeImage;
	BITMAPFILEHEADER bmfHeader;
	BITMAPINFOHEADER bmiHeader;

	hFile = CreateFile(lpszFileName, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
	if (hFile == INVALID_HANDLE_VALUE)
		return FALSE;
	
	dwSizeImage = nHeight * ((3 * nWidth + 3) / 4) * 4;

	ZeroMemory(&bmfHeader, sizeof(BITMAPFILEHEADER));
	bmfHeader.bfType    = *(LPWORD)"BM";
	bmfHeader.bfSize    = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + dwSizeImage;
	bmfHeader.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);

	WriteFile(hFile, &bmfHeader, sizeof(BITMAPFILEHEADER), &dwResult, NULL);

	ZeroMemory(&bmiHeader, sizeof(BITMAPINFOHEADER));
	bmiHeader.biSize        = sizeof(BITMAPINFOHEADER);
	bmiHeader.biWidth       = nWidth;
	bmiHeader.biHeight      = nHeight;
	bmiHeader.biPlanes      = 1;
	bmiHeader.biBitCount    = 24;
	bmiHeader.biSizeImage   = dwSizeImage;
	bmiHeader.biCompression = BI_RGB;
	
	WriteFile(hFile, &bmiHeader, sizeof(BITMAPINFOHEADER), &dwResult, NULL);

	WriteFile(hFile, lpBits, dwSizeImage, &dwResult, NULL);

	CloseHandle(hFile);
	
	return TRUE;
}

HBITMAP CreateBackbuffer(int nWidth, int nHeight)
{
	LPVOID           lp;
	BITMAPINFO       bmi;
	BITMAPINFOHEADER bmiHeader;

	ZeroMemory(&bmiHeader, sizeof(BITMAPINFOHEADER));
	bmiHeader.biSize      = sizeof(BITMAPINFOHEADER);
	bmiHeader.biWidth     = nWidth;
	bmiHeader.biHeight    = nHeight;
	bmiHeader.biPlanes    = 1;
	bmiHeader.biBitCount  = 24;

	bmi.bmiHeader = bmiHeader;

	return CreateDIBSection(NULL, (LPBITMAPINFO)&bmi, DIB_RGB_COLORS, &lp, NULL, 0);
}

デスクトップの画像を受け取れるだけのDIBセクションを作成するために、 GetWindowRectでデスクトップのサイズを取得しておきます。 CreateCompatibleDCでメモリデバイスコンテキストを作成したら、 これにCreateBackbufferで作成したDIBセクションを割り当て、 BitBltによってデスクトップの画像をコピーします。 これにより、DIBセクションのビットイメージにはデスクトップの画像が反映されたことになります。

DrawCursorは、現在表示されているカーソルをデバイスコンテキストに描画します。 システムグローバルなカーソルは、GetCursorInfoに指定するCURSORINFO構造体から取得することができ、 この構造体にはカーソルのハンドルとカーソルの位置を表すメンバが格納されています。 よって、これをDrawIconに指定すれば、カーソルが表示されている位置にカーソルを描画することができます。 ただし、カーソルの位置を格納するptScreenPosは、カーソルのホットスポット(先端)を指していないため、 描画位置がカーソルのホットスポットになるように、ホットスポットの値だけ引いています。 ホットスポットは、GetIconInfoから取得することができます。

WriteBitmapは、ファイルを作成してビットマップに必要なデータを書き込む関数です。 既に述べたように、24ビットのビットマップに必要なデータは、 BITMAPFILEHEADER構造体とBITMAPINFOHEADER構造体、 そしてビットイメージですから、これらを順番に書き込んでいくことになります。 各構造体の詳しい説明は、前々節を参照してください。


戻る