C++使用GetDIBits()读取像素

6
我正在尝试创建一个函数,其等效于Windows API GetPixel() 函数,但我想要创建一个屏幕的位图,然后读取该缓冲区。以下是我得到的代码(大部分是从谷歌搜索中复制粘贴的),但当我运行它时,它只打印出0。我认为我的大部分代码都是正确的,而我的问题在于我不知道如何读取BYTE变量。因此,我的问题是,在我的for循环中,我需要做什么才能使它打印出一些随机颜色(R、G或B)?
#include <Windows.h>
#include <iostream>
#include <math.h>
#include <stdio.h>

using namespace std;

int main() {

    HDC hdc,hdcMem;

    hdc = GetDC(NULL);
    hdcMem = CreateCompatibleDC(hdc); 

    HBITMAP hBitmap = CreateCompatibleBitmap(hdc, 1680, 1050);

    BITMAPINFO MyBMInfo = {0};
    MyBMInfo.bmiHeader.biSize = sizeof(MyBMInfo.bmiHeader); 
    // Get the BITMAPINFO structure from the bitmap
    if(0 == GetDIBits(hdcMem, hBitmap, 0, 0, NULL, &MyBMInfo, DIB_RGB_COLORS)) {
        cout << "error" << endl;
    }

    // create the bitmap buffer
    BYTE* lpPixels = new BYTE[MyBMInfo.bmiHeader.biSizeImage];

    MyBMInfo.bmiHeader.biSize = sizeof(MyBMInfo.bmiHeader);
    MyBMInfo.bmiHeader.biBitCount = 32;  
    MyBMInfo.bmiHeader.biCompression = BI_RGB;  
    MyBMInfo.bmiHeader.biHeight = abs(MyBMInfo.bmiHeader.biHeight); 

    // get the actual bitmap buffer
    if(0 == GetDIBits(hdc, hBitmap, 0, MyBMInfo.bmiHeader.biHeight, (LPVOID)lpPixels, &MyBMInfo, DIB_RGB_COLORS)) {
        cout << "error2" << endl;
    }

    for(int i = 0; i < 100; i++) {
        cout << (int)lpPixels[i] << endl;
    }

    return 0;
}
  • Windows 7(操作系统)
  • C::B 13.12(控制台应用程序)
  • 编译器:mingw32-gcc
  • 链接库gdi32

1
可能是GetDIBits和使用X,Y循环遍历像素的重复问题。 - sashoalm
3个回答

7

根据约定,我将添加一个新的答案,并提供完整工作代码片段(我已添加了缺失的lpPixels清理功能)。请查看我的先前答案和@enhzflep所做的回答中的讨论。

#include <Windows.h>
#include <iostream>
#include <math.h>
#include <stdio.h>
using namespace std;

HBITMAP GetScreenBmp( HDC hdc) {
    // Get screen dimensions
    int nScreenWidth = GetSystemMetrics(SM_CXSCREEN);
    int nScreenHeight = GetSystemMetrics(SM_CYSCREEN);

    // Create compatible DC, create a compatible bitmap and copy the screen using BitBlt()
    HDC hCaptureDC  = CreateCompatibleDC(hdc);
    HBITMAP hBitmap = CreateCompatibleBitmap(hdc, nScreenWidth, nScreenHeight);
    HGDIOBJ hOld = SelectObject(hCaptureDC, hBitmap); 
    BOOL bOK = BitBlt(hCaptureDC,0,0,nScreenWidth, nScreenHeight, hdc,0,0,SRCCOPY|CAPTUREBLT); 

    SelectObject(hCaptureDC, hOld); // always select the previously selected object once done
    DeleteDC(hCaptureDC);
    return hBitmap;
}

int main() {
    HDC hdc = GetDC(0);

    HBITMAP hBitmap = GetScreenBmp(hdc);

    BITMAPINFO MyBMInfo = {0};
    MyBMInfo.bmiHeader.biSize = sizeof(MyBMInfo.bmiHeader); 

    // Get the BITMAPINFO structure from the bitmap
    if(0 == GetDIBits(hdc, hBitmap, 0, 0, NULL, &MyBMInfo, DIB_RGB_COLORS)) {
        cout << "error" << endl;
    }

    // create the bitmap buffer
    BYTE* lpPixels = new BYTE[MyBMInfo.bmiHeader.biSizeImage];

    // Better do this here - the original bitmap might have BI_BITFILEDS, which makes it
    // necessary to read the color table - you might not want this.
    MyBMInfo.bmiHeader.biCompression = BI_RGB;  

    // get the actual bitmap buffer
    if(0 == GetDIBits(hdc, hBitmap, 0, MyBMInfo.bmiHeader.biHeight, (LPVOID)lpPixels, &MyBMInfo, DIB_RGB_COLORS)) {
        cout << "error2" << endl;
    }

    for(int i = 0; i < 100; i++) {
        cout << (int)lpPixels[i];
    }

    DeleteObject(hBitmap);
    ReleaseDC(NULL, hdc);
    delete[] lpPixels;
    return 0;
}

3

基本上,你需要绘制一些像素才能获得除0以外的结果。

目前,你主要代码的第四行创建了一个空白的(0初始化)图像。然后,你使用第一个GetDIBits调用获取了有关该图像大小的信息。接下来,使用第二个GetDIBits调用获取了实际(空白)像素。

要解决这个问题,只需从磁盘加载位图文件到你的hBitmap,并将此位图选择到你的hdcMem中。

即,修改为

HBITMAP hBitmap = CreateCompatibleBitmap(hdc, 1680, 1050);

转换成这样。
HBITMAP hBitmap = (HBITMAP)LoadImage(NULL, "xpButton.bmp", IMAGE_BITMAP, 0,0, LR_LOADFROMFILE);
HBITMAP old = (HBITMAP) SelectObject(hdcMem, hBitmap);

请确保使用有效的bmp文件名。我的bmp文件与.cpp文件位于同一文件夹中,因为这是通过IDE运行时的“当前”目录。如果您希望通过资源管理器运行,请在exe文件所在的文件夹中放置另一个bmp副本。
这是我使用的bmp文件(上传到SO后已转换为png): enter image description here 以下是循环的前10个迭代结果。
255
5
253
0
255
5
253
0
255
5

请注意,像素点0,0的颜色为:rgb(253,5,255),它是一张8位的图片,所以没有alpha通道,因此它的值为0。像素点被存储为[BGRA],[BGRA],[BGRA]等等。 我会留给您去修复程序中不存在的清理部分。Windows将释放您在这里使用的内存,但您绝对不应该养成不释放任何已分配内存的习惯。 :)

我很感激你的努力,但我想要读取当前屏幕,而不是一个保存的BMP图像。 - Mandera
1
没问题。在这种情况下,创建的位图应该与屏幕大小相同,并将其选择到hdcMem中。现在,从桌面的hdc复制到您的hdcMem中。完成后,您可以继续执行主要的第5行。您可以使用GetDC(HWND_DESKTOP)获取桌面的hdc。然后,您可以从此hdc复制到您的memdc。简单吧。 - enhzflep
你能不能再根据你的评论提供另一个回答,详细阐述一下? - Mandera

2
你的代码似乎有点混乱。我猜你用了太多片段 :). 不过,你已经很接近了: 第一个GetDIBits()调用是为了获取填充位图的属性,正如你代码中的注释所示。 你在使用一个不必要的MemDC - 这可能来自想要与屏幕进行BitBlt的片段。
然后,你可以使用填充好的结构体通过第二个GetDIBits()调用获取实际的位图像素,但你正在用硬编码的值替换属性,使得第一个GetDIBits()调用无用。
所以:放弃MemDC - 你不需要它 - 并将第一次调用GetDIBits()中的hdcMem替换为hdc,然后删除所有在第一次GetDIBits()调用之后覆盖bmiHeader成员的语句,这样就可以得到像素了。
哦,当然别忘了在DC和位图上调用ReleaseDC()/DeleteObject()并删除缓冲区delete[] :)

进一步调查:cout << (int)lpPixels[i] << endl; 导致程序崩溃,如何正确读取字节变量?(我稍微了解“填充”和它从右下角读取的方式) - Mandera
1
没关系 - 我自己试了一下,似乎 @enhzflep 是对的,你必须先通过 BitBlt() 复制像素 - 现在我不确定为什么,但是我的 GDI 知识有点生疏了。 我添加了一个工作的代码片段 - 它基于你的代码,并使用一个辅助函数来获取复制的位图:http://codepad.org/jQKJPm4u - Nick Nougat
为什么要使用资源释放功能:这是良好的编程习惯,您越早开始遵循它,效果就越好。如果你不再使用它,立即释放它-你只借用系统资源,而你永远不知道有多少个资源是空闲的,有多少个资源被其他进程/函数在程序中所需。所以请归还它们。在C++中没有自动垃圾回收来清除离开当前作用域的内容。如果您没有清理,则会导致资源和/或内存泄漏。 - Nick Nougat
好的,谢谢。你能告诉我如何实现 bitBlt 吗?@enhzflep 给我的细节不够,我很难理解,如果你能提供一个新的答案,我将接受它(当然前提是它有效)。 - Mandera
非常好用!非常感谢,将其添加为答案,我会接受它。 - Mandera
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接