4

背景情報: この MFC アプリケーションをコーディングして長い間使用しており、ユーザーが Print Screen/Alt+Print Screen キーを押すと、スクリーンショットが自動的にハード ディスクに保存されます。Windows 7 RC を数週間使用している今まで、Aero に関連するものを使用するのを先延ばしにしてきました。

問題: 標準の GetDC/BitBlt メソッドを使用してウィンドウの内容をキャプチャしています。通常の全画面グラブを実行している間、この方法で問題はありません(開いているウィンドウの数などに関係なく)。前景ウィンドウ (Alt+PrintScreen) をキャプチャしようとすると、問題が発生します。以下に 2 つの例を示します。

例 1 http://indiecodelabs.com/extern/example1.jpg

例 2 http://indiecodelabs.com/extern/example2.jpg

ご覧のとおり、境界線があるべき場所にゴミが入っています。これは上部に向かってより顕著になり、両方のスクリーンショットでツールバーが重複していることがわかります。

私はこれについて何時間もグーグルで検索してきましたが、DWM では BitBtl/GetDC メソッドが機能しないという記事しか見つかりませんでしたが、私たち (開発者) が何をすべきかを説明する記事は 1 つも見つかりませんでした。 DWM で実行する場合、アプリで同じ機能を維持できます。

ヘルプ、ポインタ、提案は大歓迎です。

4

2 に答える 2

3
BOOL CaptureWindow(const CString& filename)
{
    HWND hWnd = NULL;
    hWnd = ::GetForegroundWindow();   
    if(!hWnd)
    {
        return FALSE;
    }
    CRect rect;
    GetWindowRect(hWnd, &rect);
    rect.NormalizeRect();
    return DoCapture(CPoint(rect.left, rect.top), CSize(rect.Width(), rect.Height()), filename);
}

BOOL DoCapture(const POINT& coords, const SIZE& areaSize, const CString& filename)
{
    CDC dc;
    HDC hdc = GetDC(NULL);  // <-- We use this instead of GetWindowDC. 
                            // This is the only thing I had to change other than 
                            // getting the window coordinates in CaptureWindow()
    dc.Attach(hdc);

    // Create a memory DC into which the bitmap will be captured
    CDC memDC;
    memDC.CreateCompatibleDC(&dc);

    // If there is already a bitmap, delete it as we are going to replace it
    CBitmap bmp;
    bmp.DeleteObject();

    ICONINFO info;
    GetIconInfo((HICON)::GetCursor(), &info);   

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

    bmp.CreateCompatibleBitmap(&dc, areaSize.cx, areaSize.cy);
    CBitmap * oldbm = memDC.SelectObject(&bmp);

    // Before we copy the image in, we blank the bitmap to
    // the background fill color
    memDC.FillSolidRect(&CRect(0,0,areaSize.cx, areaSize.cy), RGB(255,255,255));

    // Copy the window image from the window DC into the memory DC
    memDC.BitBlt(0, 0, areaSize.cx, areaSize.cy, &dc, coords.x, coords.y, SRCCOPY|CAPTUREBLT);

    // This part captures the mouse cursor and paints it on the image.
    if(programSettings.bWantCursor) 
    {    
        int osVersion = OSCheck::GetMajorOSVersion(); // For some reason cursor icons in 
                                                      // versions older than Vista are not
                                                      // top-aligned. So we compensate. 
        int offsetX = (osVersion >= 6) ? 0 : 10;
        int offsetY = (osVersion >= 6) ? 0 : 10;        

        CPoint cursorOffset(cursor.ptScreenPos.x - coords.x - offsetX, cursor.ptScreenPos.y - coords.y - offsetY);

        // Now draw the image of the cursor that we captured during
        // the mouse move. DrawIcon will draw a cursor as well.
        memDC.DrawIcon(cursorOffset, (HICON)cursor.hCursor);
    }
    memDC.SelectObject(oldbm);  

    Bitmap outputBitMap(bmp, NULL);

    // Optionally copy the image to the clipboard.
    if(programSettings.bWantClipboard)
    {
        if(OpenClipboard(NULL))
        {
            EmptyClipboard();
            SetClipboardData(CF_BITMAP, bmp);
            CloseClipboard();
        }
    }

    BOOL success = DumpImage(&outputBitMap, filename);

    DeleteObject(bmp.Detach());
    DeleteDC(dc.Detach());
    DeleteDC(memDC.Detach());
    return success;
}

参考までに: DumpImage は Gdi::Bitmap の Save メソッドをほとんど使用しています。この例には関係のないアプリ固有のコードが含まれているため、省略されています。また、追加のボーナスは、スクリーングラブにカーソルを含める方法を知りたい場合は、コードもそこにあることです. それが役に立てば幸い。また、従来のアプローチとは対照的に、これには、キャプチャされたウィンドウの上にある可能性のあるオーバーレイされたウィンドウも含まれます。また、このコードをフル スクリーン キャプチャに使用する場合は、ビデオ ゲーム ウィンドウがキャプチャされないことに注意してください。DirectX に頼らずにそれを適切に行う方法を実際に考えています。これは、Aero モードで実行している場合に Windows Vista/7 にのみ影響します。

于 2009-12-16T13:52:52.460 に答える
2

残念ながら正確な答えがわからない素晴らしい質問です。私の最初のアイデアは、デスクトップ全体をつかみ、そこから興味深い部分を切り取ることでした。

QT 4.5 のソースを調べて、その方法を確認したところ、次のようなものが見つかりました。GetClientRect を GetWindowRect に切り替えて、QT ボイラープレート コードを取り除くと、必要なものが得られるはずです。しかし、それはハックのように見えます:)


QPixmap QPixmap::grabWindow(WId winId, int x, int y, int w, int h )
{
    RECT r;
    GetClientRect(winId, &r);  
    if (w < 0) w = r.right - r.left;
    if (h < 0) h = r.bottom - r.top;  
    // Create and setup bitmap
    HDC display_dc = GetDC(0);
    HDC bitmap_dc = CreateCompatibleDC(display_dc);
    HBITMAP bitmap = CreateCompatibleBitmap(display_dc, w, h);
    HGDIOBJ null_bitmap = SelectObject(bitmap_dc, bitmap);

    // copy data
    HDC window_dc = GetDC(winId);
    BitBlt(bitmap_dc, 0, 0, w, h, window_dc, x, y, SRCCOPY);

    // clean up all but bitmap
    ReleaseDC(winId, window_dc);
    SelectObject(bitmap_dc, null_bitmap);
    DeleteDC(bitmap_dc);

    QPixmap pixmap = QPixmap::fromWinHBITMAP(bitmap);

    DeleteObject(bitmap);
    ReleaseDC(0, display_dc);

    return pixmap;
}
于 2009-07-19T16:01:40.823 に答える