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
Mandera
  • 2,647
  • 3
  • 21
  • 26
  • 1
    possible duplicate of [GetDIBits and loop through pixels using X, Y](http://stackoverflow.com/questions/3688409/getdibits-and-loop-through-pixels-using-x-y) – sashoalm Feb 06 '15 at 14:06

3 Answers3

7

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;
}
Nick Nougat
  • 181
  • 5
3

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. :)

enhzflep
  • 12,927
  • 2
  • 32
  • 51
  • I appreciate your effort, but I want to read my current screen, not a saved BMP image. – Mandera Oct 07 '14 at 12:03
  • 1
    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. – enhzflep Oct 07 '14 at 12:14
  • Could you make another answer based on your comment where you go into detail a bit more? – Mandera Oct 07 '14 at 12:38
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 :)

Nick Nougat
  • 181
  • 5
  • I appreciate your detailed answer, but your suggested changes causes it to crash now. http://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? – Mandera Oct 07 '14 at 11:40
  • 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) – Mandera Oct 07 '14 at 12:01
  • 1
    Never mind - I tried a bit myself and it seems that @enhzflep 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: http://codepad.org/jQKJPm4u – Nick Nougat Oct 07 '14 at 13:19
  • 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. – Nick Nougat Oct 07 '14 at 13:29
  • Alright thanks, could you give me some details on how I would implement bitBlt? @enhzflep 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). – Mandera Oct 07 '14 at 13:36
  • 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. – Nick Nougat Oct 07 '14 at 13:57
  • Worked great! Thanks a lot, add it as an answer and I'll accept it. – Mandera Oct 07 '14 at 14:05