デスクトップ画面を取得してみる

さて、今回はAPIを用いてデスクトップ画面を取得してみたいと思います。デスクトップ画面の画像は、スクリーンセーバーなどのソフトウェアで使われることと思います。また、画像を保存するツールなどにも使えそうです。さらに、おまけとして、フォアグラウンドウィンドウの画像の取得もやってみましょう。


画像の取得には画像をコピーするAPIで実現することができますが、そのためにはいろいろと前知識が必要になるので、まずは、それらから説明していきましょう。

GDI

Windowsは、グラフィックスの描画のためにGDI(Graphics Device Interface)というグラフィックス描画機能を提供しています。このGDIは、gdi32.dllによって提供されるさまざまなAPIと、描画のための情報を管理しているデバイスコンテキストによって構成されています。

ウィンドウにAPIを使って文字や絵を描画したり、画像コピーを行なったりするには、それぞれの機能を果たすAPIを使うことになります。しかしこのとき、GDIの提供するAPIを使うためにはデバイスコンテキストのハンドルが必要になります。デバイスコンテキストとはWindowsが描画のための情報を内部的に管理しているメモリ上のデータベースのことです。これはWindowsの持つオブジェクトの1つで、ハンドルにより管理されています。

HSPウィンドウのデバイスコンテキストのハンドル

HSPでGDIの機能を使うからには、HSPのウィンドウに対して操作できなければ意味がありません。そのためには、HSPウィンドウのデバイスコンテキストを取得する必要があります。HSPウィンドウのデバイスコンテキストのハンドルは、HSPウィンドウ情報を格納する BMSCR 構造体のメンバの中に含まれています。現在描画中のウィンドウのデバイスコンテキストを取得するには以下のようにします。

mref bmscr, 67      ; BMSCR構造体取得
hdc = bmscr.4       ; デバイスコンテキストのハンドル取得

さて、ここで注意しなければならないことがあります。上に示した方法で取得したデバイスコンテキストは、実際のウィンドウ画面のデバイスコンテキストではなく、メモリ上のビットマップのデバイスコンテキストであるため、何か描画を行なったときに実際のウィンドウに反映させるためには redraw 命令を実行する必要がある、ということです。いったいどういうことかというと、本来、Windows上で動くアプリケーションのウィンドウには、ウィンドウが再描画される必要が生じたら(例えば他のウィンドウに隠れていたウィンドウが前面に現れたときなど)、Windowsから「ウィンドウのこの部分を再描画してくださいよー」というメッセージ(WM_PAINT メッセージ)が送られるのですが、このときにアプリケーションはウィンドウを描き直さなくてはいけないわけです。そこでHSPのウィンドウは、現在デスクトップ上に表示されているウィンドウがとは別にメモリ上に仮想的な画面を用意しておいてそこに描画をしていき、再描画の必要性が生じたらこれを実際のウィンドウ上にコピーする、というわけです。

実際に、「redraw 0」 を実行して描画を行なうと画面に反映されませんが、この状態でHSPのウィンドウをいったん他のウィンドウに隠してからもう一度前面に表示させると、隠れていた部分が自動的に再描画されますよね。これはウィンドウに WM_PAINT メッセージが送られたことを意味しています。したがって、HSPウィンドウが描画先となるようなAPIを使用した場合には強制的に再描画を行なうために redraw 命令を実行しましょう。もちろん、ウィンドウが表示されているときだけ実行すれば十分ですが。

デスクトップ画面のデバイスコンテキストのハンドルの取得

コピーはHSPウィンドウと、デスクトップウィンドウの間で行なうので、デスクトップウィンドウのデバイスコンテキストのハンドルもまた取得する必要があります。これを取得するには、次の2つの方法があります。


どちらの方法もあまり難しくはないので、どちらを使ってもよいでしょう。注意しなければならないのは、最初の方法でコピーをした場合は、コピー後に ReleaseDC 関数を使ってデバイスコンテキストを解放しなければならないということです。同様に、2番目の方法でコピーをした場合は、 DeleteDC 関数を呼び出さなくてはなりません。

実際にコピーしてみる

さて、実際にコピーを行なってみましょう。コピーを行なうには、 BitBlt 関数を呼び出します。

BOOL BitBlt(
    HDC   hdcDest,    // コピー先デバイスコンテキスト
    int   nXDest,     // コピー先x座標
    int   nYDest,     // コピー先y座標
    int   nWidth,     // コピーする幅
    int   nHeight,    // コピーする高さ
    HDC   hdcSource,  // コピー元デバイスコンテキスト
    int   nXSource,   // コピー元x座標
    int   nYSource,   // コピー元y座標
    DWORD dwRaster    // ラスタオペレーションコード
);

はじめの8個の引数は、コピー元およびコピー先のデバイスコンテキストのハンドルと座標、およびコピーするサイズになります。

第9引数(dwRaster パラメータ)は、ラスターオペレーションコード(コピー元およびコピー先の色データをどのように結合するかを定義する値)です。今回はコピー元からコピー先へそのままコピーするので、 0x00CC0020 (SRCCOPY) を指定します。


    #include "llmod.as"

    screen 0, 150, 50
    screen 2, dispx, dispy, 0,,, 600, 400
    mref bmscr, 67              ; ウィンドウID2のBMSCR構造体を取得
    gsel 0, -1                  ; いったん消しておく
    gsel 2, -1
    wait 50

    temp = "DISPLAY"
    getptr pm.0, temp           ; "DISPLAY"文字列へのポインタ
    pm.1 = 0, 0, 0              ; 他の引数を 0 に
    dllproc "CreateDCA", pm, 4, D_GDI
    hdcDesktop = dllret         ; デスクトップのデバイスコンテキスト

    pm.0 = bmscr.4              ; コピー先(HSPウィンドウID2)
    pm.1 = 0, 0                 ; コピー先座標
    pm.3 = dispx, dispy         ; コピーするサイズ(ディスプレイのサイズ)
    pm.5 = hdcDesktop           ; コピー元(デスクトップウィンドウ)
    pm.6 = 0, 0                 ; コピー元座標
    pm.8 = $CC0020              ; コピー方法(SRCCOPY)
    dllproc "BitBlt", pm, 9, D_GDI

    dllproc "DeleteDC", hdcDesktop, 1, D_GDI

    gsel 2, 1                   ; 再び表示
    gsel 0, 1

    dialog "bmp", 17
    if stat {
        gsel 2
        bmpsave refstr          ; ビットマップ保存
    }
    stop

上のスクリプトを実行させてみましょう。デスクトップ画面をビットマップとして保存することができます。


ちなみに、 BitBlt 関数の引数でコピー元とコピー先とを入れ替えると、HSPウィンドウに描画されている画像がデスクトップ上に直接コピーできてしまったりします。この場合は終了しても描画したものが自動的に消えないので、終了時に

bgscr 2, dispx, dispy, 0, 0, 0

などとでもして、画面をいったん消したほうがいいでしょう。

フォアグラウンドウィンドウの画面を取得してみる

さて、おまけのフォアグラウンドウィンドウ画像取得です。フォアグラウンドウィンドウというのは、デスクトップ上で最前面でアクティブになっているウィンドウのことを言います。

フォアグラウンドウィンドウの画像をHSPウィンドウにコピーするには、まずフォアグラウンドウィンドウのハンドルを取得することが必要であることがわかります。フォアグラウンドウィンドウのハンドルを取得するには GetForegroundWindow 関数を使います。この関数はフォアグラウンドウィンドウ(前面に出ている作業中ウィンドウ)のハンドルを返します。

HWND GetForegroundWindow(VOID);

似たような関数に GetActiveWindow というAPI関数がありますが、これは呼び出しスレッドの持つウィンドウの中だけからアクティブウィンドウを取得するものなので、ここでは使いません。

ウィンドウハンドルを取得できたら、 GetWindowDC 関数でデバイスコンテキストのハンドルを取得します。

HDC GetWindowDC(
    HWND hWnd   // ウィンドウハンドル
);

GetWindowDC 関数は、非クライアント領域(タイトルバーやウィンドウ境界など)を含めたウィンドウ全体のデバイスコンテキストを取得するものです。これに対して GetDC 関数はクライアント領域(描画領域)のデバイスコンテキストを取得します。したがって、クライアント領域のみを取得したい場合には GetDC 関数を使います。

非クライアント領域を含むウィンドウのサイズを得るには、 GetWindowRect 関数でウィンドウの座標を取得し、これからサイズを計算します。

BOOL GetWindowRect(
    HWND  hWnd,   // ウィンドウハンドル
    PRECT pRect   // RECT構造体のアドレス
);

第2引数(pRect パラメータ)が指し示す RECT 構造体にウィンドウの座標が格納されるので、 right メンバから left メンバを引いてウィンドウの幅を、 bottom メンバから top メンバを引いてウィンドウの高さを求めます。

クライアント領域のみのサイズを得るには代わりに GetClientRect 関数を使います。

BOOL GetClientRect(
    HWND   hWnd,    // ウィンドウハンドル
    PRECT  pRect    // RECT構造体のアドレス
);

この場合も先ほどと同様に第2引数(pRect パラメータ)が指し示す RECT 構造体にクライアント領域の座標が格納されます。この関数の場合は top メンバと left メンバは常に 0 となるので、 right メンバと bottom メンバがそのままクライアント領域の幅と高さになります。

先ほど説明したように、今回の場合もコピー処理が終わったら ReleaseDC 関数を呼び出してデバイスコンテキストを解放しなくてはなりません。


    #include "llmod.as"

    screen 0, 150, 50
    gsel 0, -1                  ; いったん消しておく
    wait 30                     ; 十分なウェイトを入れる必要がある

    dllproc "GetForegroundWindow", pm, 0, D_USER
    hwndForeground = dllret     ; フォアグラウンドウィンドウのハンドル

    pm.0 = hwndForeground
    getptr pm.1, rect           ; RECT構造体のアドレス
    dllproc "GetWindowRect", pm, 2, D_USER
    wide = rect.2 - rect.0      ; ウィンドウの幅
    high = rect.3 - rect.1      ; ウィンドウの高さ

    screen 2, wide, high, 0,,, 600, 400
    mref bmscr, 67              ; BMSCR構造体取得
    gsel 2, -1                  ; ウィンドウ非表示
    wait 50

    dllproc "GetWindowDC", hwndForeground, 1, D_USER
    hdcForeground = dllret      ; フォアグラウンドウィンドウの
                                ; デバイスコンテキスト

    pm.0 = bmscr.4              ; コピー先(HSPウィンドウID2)
    pm.1 = 0, 0                 ; コピー先座標
    pm.3 = wide, high           ; コピーするサイズ
                                ; (フォアグラウンドウィンドウのサイズ)
    pm.5 = hdcForeground        ; コピー元(フォアグラウンドウィンドウ)
    pm.6 = 0, 0                 ; コピー元座標
    pm.8 = $CC0020              ; コピー方法(SRCCOPY)
    dllproc "BitBlt", pm, 9, D_GDI

    pm.0 = hwndForeground       ; ウィンドウハンドル
    pm.1 = hdcForeground        ; デバイスコンテキストのハンドル
    dllproc "ReleaseDC", pm, 2, D_USER

    gsel 2, 1                   ; 再び表示
    gsel 0, 1

    dialog "bmp", 17
    if stat {
        gsel 2
        bmpsave refstr          ; ビットマップ保存
    }
    stop

このスクリプトを実行すると、フォアグラウンドウィンドウの画像を保存することができます。