Windows GetDIBits не возвращает ожидаемые значения - PullRequest
0 голосов
/ 28 января 2019

В настоящее время я пишу небольшую программу, которая сканирует экран и ищет пиксели.Моя проблема в том, что функция GetDIBits не отображает правильный скриншот экрана.

Копирование растрового изображения в буфер обмена приводит к правильному изображению экрана в буфере обмена.Я решил распечатать вывод функции в файл BMP, чтобы получить представление о том, что происходит, и это явно не то, что я ожидаю.

Я также упомяну, что у меня есть 3 монитора, которые могут объяснить, почему он ведет себя не так, как ожидалось.

class Test {
    int screenWidth;
    int screenHeight;
    HWND targetWindow;
    HDC targetDC;
    HDC captureDC;
    RGBQUAD *pixels;
    HBITMAP captureBitmap;


    bool TakeScreenshot() {
        ZeroMemory(pixels, screenHeight*screenWidth);
        screenWidth = GetSystemMetrics(SM_CXSCREEN);
        screenHeight = GetSystemMetrics(SM_CYSCREEN);

        targetWindow = GetDesktopWindow();
        targetDC = GetDC(NULL);


        captureDC = CreateCompatibleDC(targetDC);

        captureBitmap = CreateCompatibleBitmap(targetDC, screenWidth, screenHeight);
        HGDIOBJ old = SelectObject(captureDC, captureBitmap);
        if (!old)
            printf("Error selecting object\n");

        OpenClipboard(NULL);
        EmptyClipboard();
        SetClipboardData(CF_BITMAP, captureBitmap);
        CloseClipboard();

        if (BitBlt(captureDC, 0, 0, screenWidth, screenHeight, targetDC, 0, 0, SRCCOPY)) {
            BITMAPINFO bmi = { 0 };
            bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
            bmi.bmiHeader.biWidth = screenWidth;
            bmi.bmiHeader.biHeight = -screenHeight;
            bmi.bmiHeader.biPlanes = 1;
            bmi.bmiHeader.biBitCount = 32;
            bmi.bmiHeader.biCompression = BI_RGB;
            bmi.bmiHeader.biSizeImage = 0;

            if (!SelectObject(captureDC, old))
                printf("Error unselecting object\n");
            if (!GetDIBits(captureDC,
                captureBitmap,
                0,
                screenHeight,
                pixels,
                &bmi,
                DIB_RGB_COLORS
            )) {
                printf("%s: GetDIBits failed\n", __FUNCTION__);
                return false;
            }

        }
        else {
            printf("%s: BitBlt failed\n", __FUNCTION__);
            return false;
        }
        return true;
    }
    // This is from somewhere on stackoverflow - can't find where.
    void MakePicture() {
        typedef struct                       /**** BMP file header structure ****/
        {
            unsigned int   bfSize;           /* Size of file */
            unsigned short bfReserved1;      /* Reserved */
            unsigned short bfReserved2;      /* ... */
            unsigned int   bfOffBits;        /* Offset to bitmap data */
        } BITMAPFILEHEADER;

        BITMAPFILEHEADER bfh;
        BITMAPINFOHEADER bih;

        unsigned short bfType = 0x4d42;
        bfh.bfReserved1 = 0;
        bfh.bfReserved2 = 0;
        bfh.bfSize = 2 + sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + 2560 * 1440 * 3;
        bfh.bfOffBits = 0x36;

        bih.biSize = sizeof(BITMAPINFOHEADER);
        bih.biWidth = screenWidth;
        bih.biHeight = screenHeight;
        bih.biPlanes = 1;
        bih.biBitCount = 24;
        bih.biCompression = 0;
        bih.biSizeImage = 0;
        bih.biXPelsPerMeter = 5000;
        bih.biYPelsPerMeter = 5000;
        bih.biClrUsed = 0;
        bih.biClrImportant = 0;

        FILE *file;
        fopen_s(&file, "test.bmp", "wb");
        if (!file)
        {
            printf("Could not write file\n");
            return;
        }

        /*Write headers*/
        fwrite(&bfType, 1, sizeof(bfType), file);
        fwrite(&bfh, 1, sizeof(bfh), file);
        fwrite(&bih, 1, sizeof(bih), file);

        /*Write bitmap*/
        for (int y = 0; y < screenHeight; y++)
        {
            for (int x = 0; x < screenWidth; x++)
            {
                unsigned char r = pixels[x + y].rgbRed;
                unsigned char g = pixels[x + y].rgbGreen;
                unsigned char b = pixels[x + y].rgbBlue;
                fwrite(&b, 1, 1, file);
                fwrite(&g, 1, 1, file);
                fwrite(&r, 1, 1, file);
            }
        }
        fclose(file);
    }
    Test() {
        screenWidth = GetSystemMetrics(SM_CXSCREEN);
        screenHeight = GetSystemMetrics(SM_CYSCREEN);
        pixels = new RGBQUAD[screenWidth * screenHeight];
    }
    ~Test() {
        //cleanup
    }
};

Вот результат, который дает код (вместоснимок экрана): enter image description here

Похоже, что он берет несколько пикселей от верхней части моего экрана и растягивает их в изображение.Снимок экрана: открытая Visual Studio (оранжевая часть - это уведомления).

Если я добавлю на экран гигантский красный квадрат (255, 0, 0), если его высота не равна 0, пикселимассив не будет содержать ни одного красного пикселя.

Ответы [ 3 ]

0 голосов
/ 29 января 2019

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

Также обратите внимание, что в настройках mutli-монитора SM_CXSCREEN/Y... задает размер для основного монитора.Используйте SM_XVIRTUALSCREEN/XV... для всего экрана.SM_XVIRTUALSCREEN/Y даст координату X / Y (обычно это ноль)

Обязательно отпустите все маркеры и удалите использованные объекты, когда вы закончите.Фактически нет необходимости объявлять targetDC и т. Д. Членами класса.

Если приложение не поддерживает DPI, растровое изображение может выглядеть меньше в зависимости от настроек DPI.Позвоните SetProcessDPIAware() в начале программы для быстрого исправления или установите манифест.

Как отмечено в комментарии, с SetClipboardData(CF_BITMAP, captureBitmap); система принимает captureBitmap.Не вызывайте эту функцию и не делайте копию растрового изображения для передачи в буфер обмена.

int main()
{
    int screenWidth = GetSystemMetrics(SM_CXVIRTUALSCREEN);
    int screenHeight = GetSystemMetrics(SM_CYVIRTUALSCREEN);
    int screen_x = GetSystemMetrics(SM_XVIRTUALSCREEN);
    int screen_y = GetSystemMetrics(SM_YVIRTUALSCREEN);

    screenWidth = GetSystemMetrics(SM_CXSCREEN);
    screenHeight = GetSystemMetrics(SM_CYSCREEN);
    screen_x = 0;
    screen_y = 0;

    RGBQUAD* pixels = new RGBQUAD[screenWidth * screenHeight];

    DWORD size = screenWidth * screenHeight * 4;

    ZeroMemory(pixels, size);

    HDC targetDC = GetDC(NULL);
    HDC captureDC = CreateCompatibleDC(targetDC);
    HBITMAP captureBitmap = CreateCompatibleBitmap(targetDC, screenWidth, screenHeight);
    HGDIOBJ old = SelectObject(captureDC, captureBitmap);

    if(!BitBlt(captureDC, 0, 0, screenWidth, screenHeight, targetDC,
        screen_x, screen_y, SRCCOPY))
        printf("BitBlt error\n");

    SelectObject(captureDC, old);

    BITMAPINFO bmi = { 0 };
    bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
    bmi.bmiHeader.biWidth = screenWidth;
    bmi.bmiHeader.biHeight = -screenHeight;
    bmi.bmiHeader.biPlanes = 1;
    bmi.bmiHeader.biBitCount = 32;
    bmi.bmiHeader.biCompression = BI_RGB;
    bmi.bmiHeader.biSizeImage = 0;

    if(OpenClipboard(NULL))
    {
        EmptyClipboard();
        SetClipboardData(CF_BITMAP, 
                    CopyImage(captureBitmap, IMAGE_BITMAP, 0, 0, LR_DEFAULTSIZE));
        CloseClipboard();
    }

    if(!GetDIBits(targetDC,
        captureBitmap,
        0,
        screenHeight,
        pixels,
        &bmi,
        DIB_RGB_COLORS
    ))
        printf("%s: GetDIBits failed\n", __FUNCTION__);

    BITMAPFILEHEADER filehdr = { 'MB', 54 + size, 0, 0, 54 };
    std::ofstream f("test.bmp", std::ios::binary);
    f.write((char*)&filehdr, sizeof(filehdr));
    f.write((char*)&bmi, sizeof(bmi));
    f.write((char*)pixels, size);

    //cleanup:      
    SelectObject(captureDC, old);
    DeleteObject(captureBitmap);
    DeleteDC(captureDC);
    ReleaseDC(0, targetDC);
}
0 голосов
/ 29 января 2019

Дополнительная ошибка:

    for (int y = 0; y < screenHeight; y++)
    {
        for (int x = 0; x < screenWidth; x++)
        {
            unsigned char r = pixels[x + y].rgbRed;
            unsigned char g = pixels[x + y].rgbGreen;
            unsigned char b = pixels[x + y].rgbBlue;
            fwrite(&b, 1, 1, file);
            fwrite(&g, 1, 1, file);
            fwrite(&r, 1, 1, file);
        }
    }

Я думаю, что это должно быть

    for (int y = 0; y < screenHeight; y++)
    {
        for (int x = 0; x < screenWidth; x++)
        {
            unsigned char r = pixels[x + y*screenWidth].rgbRed;
            unsigned char g = pixels[x + y*screenWidth].rgbGreen;
            unsigned char b = pixels[x + y*screenWidth].rgbBlue;
            fwrite(&b, 1, 1, file);
            fwrite(&g, 1, 1, file);
            fwrite(&r, 1, 1, file);
        }
    }

Но строки требуют заполнения до множителей 4 байта:

    // Important: each row has to be padded to multiple of DWORD.
    // Valid only for 24 bits per pixel bitmaps.
    // Remark: 32 bits per pixel have rows always aligned (padding==0)
    int padding = 3 - (screenWidth*3 + 3)%4;
    // or
    // int padding = 3 - ((screenWidth*3 + 3) & 3);
    for (int y = 0; y < screenHeight; y++)
    {
        for (int x = 0; x < screenWidth; x++)
        {
            unsigned char r = pixels[x + y*screenWidth].rgbRed;
            unsigned char g = pixels[x + y*screenWidth].rgbGreen;
            unsigned char b = pixels[x + y*screenWidth].rgbBlue;
            fwrite(&b, 1, 1, file);
            fwrite(&g, 1, 1, file);
            fwrite(&r, 1, 1, file);
        }
        // Important: each row has to be padded to multiple of DWORD.
        fwrite("\0\0\0\0", 1, padding, file);
    }

Настроить файлразмер (действителен для 24 бит на пиксель):

    bfh.bfSize =
            2
            + sizeof(BITMAPFILEHEADER)
            + sizeof(BITMAPINFOHEADER)
            + ((screenWidth*3 + 3) & ~3) * screenHeight;
0 голосов
/ 28 января 2019

Функция GetDIBits ссылка, раздел примечаний:

Растровое изображение, указанное параметром hbmp, не должно выбираться в контексте устройства, когда приложение вызывает эту функцию.

Отмените выбор растрового изображения перед вызовом GetBIBits.

HBITMAP oldBitmap = SelectObject(captureDC, captureBitmap);
...
// Deselect captureBitmap by selecting oldBitmap.
SelectObject(captureDC, oldBitmap);

Не забудьте добавить код очистки (восстановить растровое изображение, уничтожить растровое изображение, уничтожить или освободить контексты устройства).

...