Windows GetDIBits没有返回期望值

时间:2019-01-28 02:34:54

标签: c++ windows winapi screenshot pixel

我目前正在写一个小程序,扫描像素的屏幕和外观。我的问题是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 :(得分:1)

GetDIBits function参考,备注部分:

  

不能将hbmp参数标识的位图选择为   应用程序调用此功能时的设备上下文。

在调用GetBIBits之前取消选择位图。

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

记住要添加清理代码(还原位图,销毁位图,销毁或释放设备上下文)。

答案 1 :(得分:1)

BitBlt执行实际的复制。剪贴板函数应在BitBlt

之后调用

还要注意,在多显示器设置中,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);
}

答案 2 :(得分:1)

其他错误:

    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;