ウィンドウキャプチャ

以前にやったデスクトップ画面全体のキャプチャに続き、今回は任意のウィンドウのキャプチャを行ってみましょう。とりあえずは、最前面でアクティブになっているウィンドウ(フォアグラウンドウィンドウ)のキャプチャを行ってみることにします。

ウィンドウキャプチャは、次のような手順で行います。

  1. 対象となるウィンドウのハンドルを取得する
  2. ウィンドウのサイズを取得する
  3. ウィンドウのデバイスコンテキストを取得する
  4. 画像をHSPの画面にコピーする
  5. デバイスコンテキストを解放する

そこで、まずは対象となるウィンドウのハンドルを取得する方法から説明していきましょう。

ウィンドウハンドルを取得する

フォアグラウンドウィンドウ

デスクトップ上の最前面でアクティブになっているウィンドウのことをフォアグラウンドウィンドウと呼びます。フォアグラウンドウィンドウのハンドルを取得するには、user32.dllが提供するGetForegroundWindow関数を呼び出します。

HWND GetForegroundWindow(VOID);

この関数は引数を持ちません。関数の戻り値として、現在のフォアグラウンドウィンドウのハンドルが返ります。どのウィンドウもアクティブでない場合、戻り値は0 (NULL) になります。

デスクトップウィンドウ

フォアグラウンドウィンドウの代わりに、デスクトップウィンドウのハンドルを取得することもできます。デスクトップウィンドウはデスクトップ画面全体を覆っているウィンドウのことで、このデスクトップウィンドウのキャプチャを行うことで、以前にも説明したスクリーンキャプチャを行うことができます。こちらは、以前のものとは別の手続きでスクリーンキャプチャを行うものです。

デスクトップウィンドウのハンドルを取得するにはGetDesktopWindow関数を使います。この関数はuser32.dllにより提供されており、次のように定義されています。

HWND GetDesktopWindow(VOID);

この関数もまた引数を持ちません。関数の戻り値として、デスクトップウィンドウのハンドルが返ります。


実際のところ、後でウィンドウに対するデバイスコンテキストを取得する際に、ウィンドウハンドルの代わりに0 (NULL) を指定することによってデスクトップ画面に対するデバイスコンテキストを取得することができるので、画面のサイズをウィンドウハンドルからではなくHSP標準関数ginfoから取得するのであれば、あえてデスクトップウィンドウのハンドルを取得する必要はないのですが。

ウィンドウのサイズを取得する

対象ウィンドウのハンドルを取得できたら、次にそのウィンドウのサイズを取得してしまうことにします。今回のウィンドウキャプチャでは、クライアント領域(アプリケーションの描画領域)だけの画像の取得と、非クライアント領域(タイトルバーやメニューなど)を含めたウィンドウ全体の画像の取得をそれぞれ行います。ウィンドウのサイズはそれぞれ異なるので、それらのサイズを取得する手続きも異なります。

クライアント領域のみのサイズ

対象ウィンドウのクライアント領域(アプリケーションによる描画領域)のみのサイズを取得したい場合には、user32.dllより提供されているGetClientRect関数を使います。

BOOL GetClientRect(
    HWND   hWnd,   // window handle
    LPRECT pRect   // window rectangle
);

最初のhWndパラメータには、対象ウィンドウのハンドルを指定します。

次のpRectパラメータには、RECT構造体のアドレスを指定します。以前にも記述しましたが、RECT構造体は以下のように定義されています。

typedef struct tagRECT {
    LONG left;       // upper-left X
    LONG top;        // upper-left Y
    LONG right;      // lower-right X
    LONG bottom;     // lower-right Y
} RECT, *PRECT, *NPRECT, *LPRECT;

GetClientRect関数にRECT構造体のアドレスを渡すと、この構造体にウィンドウのクライアント領域の座標が格納されます。クライアント座標は常にクライアント領域の左上の点を基準としているので、leftメンバとtopメンバは常に0になります。rightメンバとbottomメンバがそれぞれ右下の点のクライアント座標(x座標とy座標)であり、これらがそのままクライアント領域の幅と高さを表しています。

非クライアント領域を含めたサイズ

ウィンドウのクライアント領域だけでなく、タイトルバー、メニューバー、スクロールバーといった非クライアント領域も含めた対象ウィンドウ全体のサイズを取得するには、GetWindowRect関数を使います。この関数はuser32.dllによって提供されており、以下のように定義されています。

BOOL GetWindowRect(
    HWND   hWnd,   // window handle
    LPRECT pRect   // window rectangle
);

2つの引数hWndパラメータとpRectパラメータについては、上記のGetClientRect関数と同じです。GetWindowRect関数の場合には、クライアント領域の座標ではなく、ウィンドウ全体の座標がRECT構造体に格納されます。

GetWindowRect関数で取得される座標はスクリーン座標で表されています。これは、スクリーンの左上の点を基準(座標ゼロ)とするものです。そのため、実際のウィンドウの幅はrightメンバからleftメンバを引いた値に、高さはbottomメンバからtopメンバを引いた値になります。

ウィンドウのデバイスコンテキストを取得する

次に、対象ウィンドウの描画を行っているデバイスコンテキストのハンドルを取得しなければいけません。これについても、クライアント領域(アプリケーションの描画領域)だけのデバイスコンテキストの場合と、非クライアント領域(タイトルバーやメニューなど)を含めたウィンドウ全体のデバイスコンテキストの場合とで、手続きが異なります。

クライアント領域のみのデバイスコンテキスト

ウィンドウのクライアント領域のみを取得したい場合には、クライアント領域のみに割り当てられているデバイスコンテキストのハンドルを取得します。これには、user32.dllより提供されているGetDC関数を使います。

HDC GetDC(
    HWND hWnd     // window handle
);

hWndパラメータにはウィンドウハンドルを指定します。ここで指定されたウィンドウのクライアント領域に対するデバイスコンテキストのハンドルが戻り値として取得されます。

hWndパラメータに0 (NULL) を指定することもできます。この場合には、デスクトップ画面に対するデバイスコンテキストのハンドルが取得されます。

非クライアント領域を含めたデバイスコンテキスト

非クライアント領域も含めたウィンドウ全体に対するデバイスコンテキストのハンドルを取得するには、GetWindowDC関数を使います。この関数はuser32.dllによって提供されており、以下のように定義されています。

HDC GetWindowDC(
    HWND hWnd     // window handle
);

hWndパラメータにはウィンドウハンドルを指定します。ここで指定されたウィンドウの全領域に対するデバイスコンテキストのハンドルが戻り値として取得されます。

この関数の場合も、hWndパラメータに0 (NULL) を指定することによって、デスクトップ画面に対するデバイスコンテキストのハンドルを取得することができます。

画像のコピー

対象ウィンドウのサイズとデバイスコンテキストのハンドルを取得できたら、2つのデバイスコンテキスト間での画像コピーを行います。スクリーンキャプチャの際にも説明したように、画像のコピーにはgdi32.dllが提供するBitBlt関数を使います。

BOOL BitBlt(
    HDC   hdcDest,   // destination DC
    int   nXDest,    // destination X-coord
    int   nYDest,    // destination Y-coord
    int   nWidth,    // width
    int   nHeight,   // height
    HDC   hdcSource, // source DC
    int   nXSource,  // source X-coord
    int   nYSource,  // source Y-coord
    DWORD dwRaster   // raster operation code
);

コピー元として取得したデバイスコンテキストのハンドルを指定し、コピー先のデバイスコンテキストのハンドルとしてはシステム変数hwndの値を指定します。ラスターオペレーションコード(dwRasterパラメータ)は前回と同じく0x00CC0020 (SRCCOPY) と0x40000000 (CAPTUREBLT) の組み合わせを指定します。

デバイスコンテキストの解放

画像のコピーが終了したら、取得したデバイスコンテキストを解放しなければいけません。これには、user32.dllReleaseDC関数を呼び出します。

int ReleaseDC(
    HWND hWnd,  // window handle
    HDC  hDC    // DC handle
);

最初のhWndパラメータには対象ウィンドウのハンドルを、次のhDCパラメータにはGetDC関数やGetWindowDC関数で取得されたデバイスコンテキストのハンドルを指定します。


1つ注意しておくべきことは、以前のスクリーンキャプチャではCreateDC関数を使って取得したデスクトップ画面のデバイスコンテキストをDeleteDCで解放していましたが、今回のウィンドウキャプチャではGetDC関数またはGetWindowDC関数で取得したデバイスコンテキストをReleaseDC関数で解放しているということです。これらの関数の対応を間違えてはいけません。

サンプルスクリプト

今回のスクリプトでは、モジュール機能を使用しています。モジュール内で新たに定義されている命令は、指定されたウィンドウの画像をHSPのバッファウィンドウにコピーするCaptureWindow命令、アクティブなウィンドウの画像をコピーするCaptureActiveWindow命令、デスクトップ画面の画像をコピーするCaptureDesktopScreen命令の3つです。CaptureActiveWindow命令とCaptureDesktopScreen命令については、対象ウィンドウ(フォアグラウンドウィンドウやデスクトップウィンドウ)のハンドルを取得した直後にCaptureWindow命令を実行するという手続きをとっています。

#module

; ======== API 関数の定義 ========
#uselib "user32.dll"
#cfunc GetDC "GetDC" int
#cfunc GetWindowDC "GetWindowDC" int
#func ReleaseDC "ReleaseDC" int,int
#cfunc GetForegroundWindow "GetForegroundWindow"
#cfunc GetDesktopWindow "GetDesktopWindow"
#func GetWindowRect "GetWindowRect" int,int
#func GetClientRect "GetClientRect" int,int

#uselib "gdi32.dll"
#func BitBlt "BitBlt" int,int,int,int,int,int,int,int,int

#define NULL        0
#define SRCCOPY     0x00CC0020
#define CAPTUREBLT  0x40000000

; ======== 指定されたウィンドウのキャプチャ ========
#deffunc CaptureWindow  int hwndTarget, int clientOnly
; ウィンドウのサイズ取得
dim rect, 4     ; RECT構造体
if clientOnly {
    GetClientRect hwndTarget, varptr(rect)
} else {
    GetWindowRect hwndTarget, varptr(rect)
}
if stat == 0 {
    return 1      ; エラー発生時 stat = 1
}
sx = rect(2) - rect(0)
sy = rect(3) - rect(1)

; 現在の描画先バッファウィンドウをコピー先として初期化
buffer ginfo_sel, sx, sy, 0

; デバイスコンテキストのハンドル取得
if clientOnly {
    hdcTarget = GetDC(hwndTarget)
} else {
    hdcTarget = GetWindowDC(hwndTarget)
}

; 画像をコピー
BitBlt hdc, 0, 0, sx, sy, hdcTarget, 0, 0, SRCCOPY | CAPTUREBLT

; デバイスコンテキスト解放
ReleaseDC hwndTarget, hdcTarget
return 0          ; 正常終了時 stat = 0


; ======== アクティブウィンドウのキャプチャ ========
#deffunc CaptureActiveWindow int clientOnly

; アクティブウィンドウのハンドルを取得してウィンドウキャプチャ
CaptureWindow GetForegroundWindow(), clientOnly
return


; ======== デスクトップ画面のキャプチャ ========
#deffunc CaptureDesktopScreen

; デスクトップウィンドウのハンドルを取得してウィンドウキャプチャ
CaptureWindow GetDesktopWindow()
return

#global

; ===== ここからメインスクリプト =====
screen 0, 300, 150
objsize 300, 50
button "Active Window (Full)", *capture
button "Active Window (Client Area)", *capture
button "Desktop Screen", *capture
stop

*capture
; 押されたボタンのオブジェクトIDを取得
pressedButtonId = stat

; ===== 画面のキャプチャ =====
gsel 0, -1      ; ウィンドウをいったん消す
buffer 2, 1, 1  ; コピー用バッファ画面(サイズはダミー、とりあえず1×1)
wait 50         ; アクティブウィンドウ取得は十分なwaitが必要

; 押されたボタンを判別して処理を決定
switch pressedButtonId
 case 0
    ; Active Window (Full)
    CaptureActiveWindow 0
    swbreak
 case 1
    ; Active Window (Client Area)
    CaptureActiveWindow 1
    swbreak
 case 2
    ; Desktop Screen
    CaptureDesktopScreen
    swbreak
swend

gsel 0, 1
dialog "bmp", 17, "ビットマップファイル"
if stat {
    gsel 2 : bmpsave refstr
}
stop