Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

BitBlt ignores CAPTUREBLT and seems to always capture a cached copy of the target

I am trying to capture screenshots using the BitBlt function. However, every single time I capture a screenshot, the non-client area NEVER changes no matter what I do. It's as if it's getting some cached copy of it. The client area is captured correctly.

If I close and then re-open the window, and take a screenshot, the non-client area will be captured as it is. Any subsequent captures after moving/resizing the window have no effect on the captured screenshot. Again, the client area will be correct.

Furthermore, the CAPTUREBLT flag seems to do absolutely nothing at all. I notice no change with or without it. Here is my capture code:

QPixmap WindowManagerUtils::grabWindow(WId windowId, GrabWindowFlags flags, int x, int y, int w, int h)
{
    RECT r;

    switch (flags)
    {
        case WindowManagerUtils::GrabWindowRect:
            GetWindowRect(windowId, &r);
            break;
        case WindowManagerUtils::GrabClientRect:
            GetClientRect(windowId, &r);
            break;
        case WindowManagerUtils::GrabScreenWindow:
            GetWindowRect(windowId, &r);
            return QPixmap::grabWindow(QApplication::desktop()->winId(), r.left, r.top, r.right - r.left, r.bottom - r.top);
        case WindowManagerUtils::GrabScreenClient:
            GetClientRect(windowId, &r);
            return QPixmap::grabWindow(QApplication::desktop()->winId(), r.left, r.top, r.right - r.left, r.bottom - r.top);
        default:
            return QPixmap();
    }

    if (w < 0)
    {
        w = r.right - r.left;
    }

    if (h < 0)
    {
        h = r.bottom - r.top;
    }

#ifdef Q_WS_WINCE_WM
    if (qt_wince_is_pocket_pc())
    {
        QWidget *widget = QWidget::find(winId);
        if (qobject_cast<QDesktopWidget*>(widget))
        {
            RECT rect = {0,0,0,0};
            AdjustWindowRectEx(&rect, WS_BORDER | WS_CAPTION, FALSE, 0);
            int magicNumber = qt_wince_is_high_dpi() ? 4 : 2;
            y += rect.top - magicNumber;
        }
    }
#endif

    // Before we start creating objects, let's make CERTAIN of the following so we don't have a mess
    Q_ASSERT(flags == WindowManagerUtils::GrabWindowRect || flags == WindowManagerUtils::GrabClientRect);

    // Create and setup bitmap
    HDC display_dc = NULL;
    if (flags == WindowManagerUtils::GrabWindowRect)
    {
        display_dc = GetWindowDC(NULL);
    }
    else if (flags == WindowManagerUtils::GrabClientRect)
    {
        display_dc = GetDC(NULL);
    }

    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 = NULL;
    if (flags == WindowManagerUtils::GrabWindowRect)
    {
        window_dc = GetWindowDC(windowId);
    }
    else if (flags == WindowManagerUtils::GrabClientRect)
    {
        window_dc = GetDC(windowId);
    }

    DWORD ropFlags = SRCCOPY;
#ifndef Q_WS_WINCE
    ropFlags = ropFlags | CAPTUREBLT;
#endif

    BitBlt(bitmap_dc, 0, 0, w, h, window_dc, x, y, ropFlags);

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

    QPixmap pixmap = QPixmap::fromWinHBITMAP(bitmap);

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

    return pixmap;
}

Most of this code comes from Qt's QWidget::grabWindow function, as I wanted to make some changes so it'd be more flexible. Qt's documentation states that:

The grabWindow() function grabs pixels from the screen, not from the window, i.e. if there is another window partially or entirely over the one you grab, you get pixels from the overlying window, too.

However, I experience the exact opposite... regardless of the CAPTUREBLT flag. I've tried everything I can think of... nothing works. Any ideas?

like image 551
Jake Petroules Avatar asked May 24 '10 19:05

Jake Petroules


1 Answers

Your confusion about BitBlt with CAPTUREBLT behaviour comes from the fact that official BitBlt documentation is unclear and misleading.

It states that
"CAPTUREBLT -- Includes any windows that are layered on top of your window in the resulting image. By default, the image only contains your window."

What actually means (at least for any windows OS without Aero enabled) "CAPTUREBLT -- Includes any layered(!) windows (see WS_EX_LAYERED extended window style) that overlap your window. Non-layered windows that overlap your window is never included."

Windows without WS_EX_LAYERED extended window style that overlap your window is not included with or without CAPTUREBLT flag (at least for any windows OS without Aero enabled).

QT developers also misunderstood BitBlt/CAPTUREBLT documentation so QT documentation is actually wrong about QPixmap::grabWindow behaviour on WIN32 platform without Aero enabled.

ADD:

If you want to capture your window as it is on the screen you have to capture the entire desktop with CAPTUREBLT flag and then extract the rectangle with your window. (QT developers should do the same thing). It will work correctly in both cases: with and without Aero enabled/available.

like image 95
Serge Dundich Avatar answered Nov 02 '22 02:11

Serge Dundich