c++ read pixels with GetDIBits()
Asked Answered
I

3

6

I'm trying to create a function which is equivalent to the windows API GetPixel() function, but I want to create a bitmap of my screen and then read that buffer.

This is what I've got (Mostly copy pasted from google searches), when I run it it only prints out 0's. I think I've got most of it right, and that my issue is that I don't know how to read the BYTE variable.

So my question is, what do I need to do in order to get it to print out some random colors (R,G or B) with my for loop?

#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 (Console Application)
  • Compiler: mingw32-gcc
  • Library gdi32 linked
Iglesias answered 7/10, 2014 at 10:24 Comment(1)
possible duplicate of GetDIBits and loop through pixels using X, YDemos
E
8

As agreed, I'm adding a new answer with the working code snippet (I added the missing cleanup of lpPixels). See the discussions in my previous answer and the one made by @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;
}
Elasmobranch answered 7/10, 2014 at 14:27 Comment(0)
S
4

Basically, you need to have drawn some pixels in order to get back a result other than 0.

At present, the 4th line of code in your main creates an empty (blank, 0-initialized) image. You then get information about the size of this image with your first call to GetDIBits. You then get the actual (blank) pixels with your second call to GetDIBits.

To fix, just load a bitmap file from disk into your hBitmap and select this bitmap into your hdcMem.

I.e, change

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

to something like this.

HBITMAP hBitmap = (HBITMAP)LoadImage(NULL, "xpButton.bmp", IMAGE_BITMAP, 0,0, LR_LOADFROMFILE);
HBITMAP old = (HBITMAP) SelectObject(hdcMem, hBitmap);

(make sure you use a valid bmp file name. Mine exists in the same folder as the .cpp file, since this is the 'current' directory when you run via the IDE. If you wish to run via explorer, place another copy of the bmp in the same folder as your exe)

Here's the bmp I've used (which has been converted to a png after upload to SO):

enter image description here

And here's the first 10 iterations through the loop.

255
5
253
0
255
5
253
0
255
5

Note that the pixel at 0,0 has the colour of: rgb(253,5,255) and it's an 8bit image, so there's no alpha channel, hence it has the value 0. The pixels are stored as [BGRA], [BGRA], [BGRA], etc, etc. I'll leave it to you to fix the (non-existant) clean-up section of your program. Windows will de-allocate the memory you've used here, but you absolutely should not get into the habit of not freeing any memory you've allocated. :)

Susceptible answered 7/10, 2014 at 11:23 Comment(3)
I appreciate your effort, but I want to read my current screen, not a saved BMP image.Iglesias
No worries. Well in that case, make the bitmap that you create the same size as the screen and select it into the hdcMem. Now, do a bitblt from the hdc of the desktop into your hdcMem. After this is done, you can move onto line 5 of your main. You can use GetDC(HWND_DESKTOP) to get the desktop's hdc. You can then bitblt from this hdc to your memdc. Simples.Susceptible
Could you make another answer based on your comment where you go into detail a bit more?Iglesias
E
2

Your code seems a bit confused. Too many snippets I guess :). Still, you're quite close: The first GetDIBits() call is in order to get the properties of the bitmap filled in, as the comment in your code suggests. You are using an unnecessary MemDC for this - which is probably from a snippet that wants to do a BitBlt with the screen.

You then can use the filled in structure to get the actual bitmap pixels with the second GetDIBits() call, but what you're doing is replacing the properties with hard coded values again, making the first GetDIBits() call useless.

So: Drop the MemDC - you don't need it - and replace hdcMem with hdc in the first call to GetDIBits(), then remove all the statements that overwrite bmiHeader members after the first GetDIBits call and you should get your pixels.

Oh, and of course don't forget to call ReleaseDC()/DeleteObject() on the dc and bitmap and delete[] the buffer :)

Elasmobranch answered 7/10, 2014 at 11:8 Comment(7)
I appreciate your detailed answer, but your suggested changes causes it to crash now. codepad.org/EyRod1XA What site do you recommend copy pasting c++ code on? And lastly, what happens if I don't delete my used objects? Do they not get deleted and get their taken memory freed automatically when the code is finished running?Iglesias
Further investigation: cout << (int)lpPixels[i] << endl; is causing it to crash, how do I properly read the byte variable? (I'm slightly aware of 'padding' and that it reads from bottom right)Iglesias
Never mind - I tried a bit myself and it seems that @Susceptible is right, you have to first copy the pixels via BitBlt() - I'm not sure why now, but my GDI knowledge has become a bit rusty. I added a code snippet that works - it is based on your code with a helper function to get the copied bitmap: codepad.org/jQKJPm4uElasmobranch
As to why to use the resource releasing functions: This is good practice and the earlier you start heeding this, the better. If you no longer use it, release it right away - you only borrow system resources and you never know how many are free and how many are needed by other processes / functions in your own program. So give them back. In C++ there is no automatic garbage collection that cleans up things that leave the current scope. If you don't clean up you will cause a resource and/or memory leak.Elasmobranch
Alright thanks, could you give me some details on how I would implement bitBlt? @Susceptible didn't give me enough details and I struggle connecting the dots, if you make a new answer I'll accept it (If it works of course).Iglesias
See my posted snippet - the function GetScreenBmp() shows how to BitBlt correctly. BTW I myself forgot to delete[] lpPixels. Test if it works and if so I'll make a complete answer with the code for all to see directly.Elasmobranch
Worked great! Thanks a lot, add it as an answer and I'll accept it.Iglesias

© 2022 - 2024 — McMap. All rights reserved.