Захват окна программы с помощью BitBlt всегда возвращает одно и то же изображение

Я написал следующий код (C++ Win32), чтобы захватить экран игрового окна и получить массив цветов пикселей из изображения. Функция autoB() делает свое дело.

Затем я рисую массив результатов в своем окне, чтобы визуально проверить, что я получил.

Проблема в том, что эта программа срабатывает только один раз после запуска компьютера, после первого раза она "кэширует" первый скриншот из игры и у меня всегда получается один и тот же массив пикселей. Даже если я закрываю и перезапускаю программу, я получаю тот же снимок экрана.

Игра не использует DirectX для рисования на экране, и я всегда могу делать скриншоты, используя Alt+PrtSc.

Любая помощь в понимании того, почему это происходит таким образом, приветствуется.

int getPixels(HDC *eClientHdcMem, HBITMAP *eClientBmp, unsigned char **lp) {

BITMAP bmpScreen;   
    BITMAPINFOHEADER bi;

GetObject(*eClientBmp, sizeof(BITMAP), &bmpScreen);

LONG bW = bmpScreen.bmWidth, bH = bmpScreen.bmHeight;

bi.biSize = sizeof(BITMAPINFOHEADER);    
bi.biWidth = bW;    
bi.biHeight = -bH;  
bi.biPlanes = 1;    
bi.biBitCount = 32;    
bi.biCompression = BI_RGB;    
bi.biSizeImage = 0;  
bi.biXPelsPerMeter = 0;    
bi.biYPelsPerMeter = 0;    
bi.biClrUsed = 0;    
bi.biClrImportant = 0;

DWORD dw = ((bW * bi.biBitCount + 31) / 32) * 4 * bH;
*lp = new unsigned char[dw];

return GetDIBits(*eClientHdcMem, *eClientBmp, 0, (UINT)bH, *lp, (BITMAPINFO *)&bi, DIB_RGB_COLORS);

}

void autoB() {
HWND hwnd;
HDC hDC0 = NULL, eClientHdcMem = NULL;
HBITMAP eClientBmp = NULL;
BITMAP bmp = {0};
unsigned char *lp = NULL, *sp = NULL;
WINDOWINFO wi;
wi.cbSize = sizeof(WINDOWINFO);
RECT vp;
int vpW, vpH;
long iW, iH;

if (!(hwnd = FindWindow(NULL,TEXT("Client")))) return;
if (!(hDC0 = GetDC(hwnd))) return;

GetWindowInfo(hwnd,&wi);
vp = wi.rcClient;
vpW = vp.right - vp.left;
vpH = vp.bottom - vp.top;

if (!(eClientBmp = CreateCompatibleBitmap(hDC0, vpW, vpH))) return;
if (!(eClientHdcMem = CreateCompatibleDC(hDC0))) return;
SelectObject(eClientHdcMem, eClientBmp);

BitBlt(eClientHdcMem, 0, 0, vpW, vpH, hDC0, 0, 0, SRCCOPY);

int res = getPixels(&eClientHdcMem, &eClientBmp, &lp);

DeleteObject(eClientBmp);
DeleteObject(eClientHdcMem);

    // begin testing
HDC sts = GetDC(hStats);
HBITMAP stsBmp = CreateCompatibleBitmap(sts, vpW, vpH);
HBITMAP stsBmpOld = (HBITMAP)SelectObject(sts, stsBmp);
unsigned char r,g,b;
for(unsigned int i=0;i<vpW;i++) {
    for(unsigned int j=0;j<vpH;j++) {
        r = lp[(vpW*j+i) * 4 + 2];
        g = lp[(vpW*j+i) * 4 + 1];
        b = lp[(vpW*j+i) * 4 + 0];
        SetPixel(sts,i,j,RGB(r,g,b));
    }
}
SelectObject(sts, stsBmpOld);
DeleteObject(stsBmp);
DeleteObject(stsBmpOld);
ReleaseDC(hStats,sts);
    // end testing

DeleteDC(eClientHdcMem);
ReleaseDC(hwnd,hDC0);

delete [] lp;
lp = NULL;
delete [] sp;
sp = NULL;

}

Единственный способ изменить скриншот — перезапустить игру. Затем снова делается первый снимок экрана, который будет отображаться снова и снова, независимо от того, что происходит в окне игры.


person user1481126    schedule 25.06.2012    source источник
comment
autoB когда-нибудь возвращается слишком рано?   -  person Chris O    schedule 26.06.2012
comment
Нет, это не так. Если я добавлю MessageBox после sp = NUll, я буду видеть его каждый раз при вызове функции.   -  person user1481126    schedule 26.06.2012


Ответы (2)


Вы уверены, что получаете те же пиксели, или вы просто видите одно и то же изображение на экране в окне отладки? Исходный код копирования изображения выглядит нормально, но в вашем «отладочном» коде, даже если вы вызываете SetPixel() напрямую, вам все равно нужно вызвать InvalidateRect(), чтобы заставить Windows отправить новое сообщение WM_PAINT. Если вы этого не делаете, вы, вероятно, просто смотрите на старое изображение, хотя новые биты действительно были захвачены (но не нарисованы).

person Myk Willis    schedule 25.06.2012
comment
SetPixel работает очень медленно, поэтому при каждом вызове этой функции я реально вижу, как она рисует растровое изображение в окне программы. Добавление InvalidateRect ничего не меняет. - person user1481126; 26.06.2012

Я столкнулся с той же проблемой и заметил, что переменная HDC (hDC0 в вашем случае) использовалась для вызова CreateCompatibleDC и, в свою очередь, BitBlt имеет значение. Если вы используете GetDC(NULL), вы получите изображение всего экрана, и в этом случае изображение обновляется каждый раз, а использование GetDC(myWindowHwnd) создает проблему вы упомянули, что всегда возвращается одно и то же изображение без какого-либо обновления.

Окно, которое я хочу захватить, представляет собой полноэкранное, самое верхнее окно, которое было создано следующим образом:

    hwnd = CreateWindowExA(0, "myWindow", "myWindow", WS_POPUP, x, y, w, h, NULL, NULL, wc.hInstance, NULL);

    SetWindowLong(hwnd, GWL_EXSTYLE, GetWindowLong(hwnd, GWL_EXSTYLE) | WS_EX_LAYERED | WS_EX_TRANSPARENT);
    SetLayeredWindowAttributes(hwnd, 0, 0xFF, LWA_ALPHA);
    ShowWindow(hwnd, SW_SHOW);
    SetWindowPos(hwnd, HWND_TOPMOST, x, y, w, h, SWP_NOACTIVATE);

Даже если окно отображается и находится над всеми другими окнами, функция BitBlt не может захватить обновленные кадры окна, пока оно обновляется другим потоком.

person Bemipefe    schedule 11.04.2019