Win32
画面を BMP として取り込む(マルチスクリーン対応)


HomeProgramming TipsWin32SDK Tips[SDK-009]

 ダウンロード : スクリーンキャプチャサンプル(2007/01/14)  

画面全体を取り込むには、まず対象の HDC を取得する必要があります。
マルチスクリーンの場合、どの画面を取り込みの対象とするかを決めます。

一番分かりやすいのはマウスカーソルの位置の画面を取り込む仕様ですね。
マルチスクリーンでは、マウスカーソルは全てのスクリーンが接続された座標で表現されます。
そのため、まずは自分がどの画面にいるのかを調べる必要があります。

それを簡単に実現するのが MonitorFromPoint() 関数です。
この関数を使ってまず取り込む対象のモニタのハンドルを取得します。


    // マウスポインタ位置のモニタハンドルを取得する
    POINT posMouse;
    ::GetCursorPos(&posMouse);
    HMONITOR hMonitor = ::MonitorFromPoint(posMouse, MONITOR_DEFAULTTONEAREST);


モニタハンドルが取得できれば、そこから HDC と画面サイズを取得できます。
ここで使用する関数は GetMonitorInfo() です。
MONITORINFOEX 構造体を指定することで、ディスプレイモニタの名前が得られます。
この名前を使えば CreateDC() を使用して対象モニタの HDC を得ることが出来ます。


    // モニタの情報を取得する
    MONITORINFOEX stMonInfoEx;
    stMonInfoEx.cbSize = sizeof(stMonInfoEx);
    ::GetMonitorInfo(hMonitor, &stMonInfoEx);

    // 目的のデバイスコンテキストを取得する
    HDC hDC = ::CreateDC(TEXT("DISPLAY"), stMonInfoEx.szDevice, NULL, NULL);
    int nMonW = stMonInfoEx.rcMonitor.right  - stMonInfoEx.rcMonitor.left;   // 横幅
    int nMonH = stMonInfoEx.rcMonitor.bottom - stMonInfoEx.rcMonitor.top;    // 縦幅


さて、実際のキャプチャ動作です。
BITMAP は構造的に「ファイルヘッダ」「情報部ヘッダ」「パレット」「画像データ」となります。
取り込みには 24bit フルカラーを使用するのがオールマイティですしパレットも不要です。

ファイルヘッダは実際のファイル保存で作ればいいので、まずは情報部ヘッダを用意します。
メモリの確保には GlobalAlloc() を使用します。名称的に少々ややこしいので注意してください。
データが取得できたら必要な情報をヘッダのメンバに登録してやります。
そして、CreateDIBSection() 関数にて実際の画像データを確保するエリアを作ります。


    // DIB 画像格納用メモリを取得する
    LPBITMAPINFO lpDIB = (LPBITMAPINFO)::GlobalAlloc(GPTR, sizeof(BITMAPINFO));

    // DIBSection の BITMAPINFO を初期化する
    ::ZeroMemory(&lpDIB->bmiHeader, sizeof(BITMAPINFOHEADER));
    lpDIB->bmiHeader.biSize        = sizeof(BITMAPINFOHEADER);
    lpDIB->bmiHeader.biWidth       = nMonW;
    lpDIB->bmiHeader.biHeight      = nMonH;
    lpDIB->bmiHeader.biPlanes      = 1;
    lpDIB->bmiHeader.biBitCount    = 24;
    lpDIB->bmiHeader.biCompression = BI_RGB;
    lpDIB->bmiHeader.biSizeImage   = nMonW * nMonH * 3;  // 24bit Color フル画面

    // 画面キャプチャ用の DIBSection を作成する
    LPBYTE lpBMP;
    HBITMAP hDIB = ::CreateDIBSection(
        hDC, lpDIB, DIB_RGB_COLORS, (LPVOID*)&lpBMP, NULL, 0
    );


これで画面キャプチャに必要な入れ物は出来ました。
CreateCompatibleDC() 関数にてコピー用の DC を用意します。
取得した hDIB を DC に割り当てて BitBlt() で画像を一気にコピーします。
これで画像として画面をキャプチャできました。


    // 画面全体を DIB にコピーする
    HDC hMemDC = ::CreateCompatibleDC(hDC);
    HGDIOBJ hOldDIB = ::SelectObject(hMemDC, hDIB);            
    ::BitBlt(hMemDC, 0, 0, nMonW, nMonH, hDC, 0, 0, SRCCOPY);


さて、後はこのデータをファイルとして保存するだけです。
保存に必要な BITMAPFILEHEADER 構造体を用意します。
先頭に "BM" とシグネチャを書いた上で、bfSize、bfOffBits のみに正しいデータを書き込みます。
残りのメンバはゼロクリアします。私は構造体変数宣言時に "BM" のシグネチャを作るついでにゼロクリアしています。
BITMAPFILEHEADER、BITMAPINFOHEADER、DIB 画像データ本体の順にファイルに書き出していきます。
これでファイルとして保存が完了しました。


    // BMP ファイルヘッダー部分を初期化する
    BITMAPFILEHEADER   bmFileHeader  = { (WORD)(TEXT('B') | TEXT('M') << 8) };
    LPBITMAPINFOHEADER pbmInfoHeader = &lpDIB->bmiHeader;
    bmFileHeader.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
    bmFileHeader.bfSize    = bmFileHeader.bfOffBits + pbmInfoHeader->biSizeImage;

    // bmp として保存する
    // pzPath : 保存するファイルパスが格納されている配列の先頭ポインタ
    DWORD dwSize;
    HANDLE hFile = ::CreateFile(
        szPath, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL
    );
    ::WriteFile(hFile, &bmFileHeader, sizeof(BITMAPFILEHEADER), &dwSize, NULL);
    ::WriteFile(hFile, lpDIB, sizeof(BITMAPINFOHEADER), &dwSize, NULL);
    ::WriteFile(hFile, lpBMP, lpDIB->bmiHeader.biSizeImage, &dwSize, NULL);

    // ファイルを閉じる
    ::CloseHandle(hFile);


最後に動的に取得したメモリを解放します。


    // 後片付け
    ::SelectObject(hMemDC, hOldDIB);
    ::DeleteDC(hDC);
    ::DeleteDC(hMemDC);
    ::DeleteObject(hDIB);
    ::GlobalFree(lpDIB);




 Copyright 2008 VALGUS. All Rights Reserved.