c++windowsbitmapgdi+

Bitmap info regularly returns series of same wrong values before returning correct pixel colors


An update:

It turns out that this problem is actually a lot more of a problem than I thought. I wrote a little extra to the main() so that the screenCap() function there runs multiple times using a for loop and calling values from an array for the capture's centre pixel. When I ran this, every other capture that got couted returned c0c0c. Admittedly, I could just make it so that the function runs again to get the right values since it does run fast, or at least fast enough for my purposes, but, I'd prefer to avoid any possible complications with that, like speed or memory, if that could be a problem. These c0c0c values really seem to be persistent here, not any of the other ones.


Hello there, and apologies if the problem I'm describing is something really obvious. I'm new to programming for Windows and using GDI+.

I'm writing a program that reads an n*n area on screen and sends the pixels it gets to a 2D vector for later calculations. I've been couting the vector so that I could cross-reference it with an image version to make sure it's getting the right data, mostly just for debugging purposes. It usually works, but every once in a while, I get the same sequence of odd values: a vector full of c0c0c, a vector full of c0c0c, a vector full of 0, and finally, a vector of somewhat random values. After that, running the program returns the correct values of the bitmap.

Below is a simplified version of my code, without any of the image saving or other stuff:

#include <windows.h>
#include <iostream>
#include <vector>
#include <iomanip>
#include <gdiplus.h>
#include <gdiplusheaders.h>

using namespace Gdiplus;
using std::cout;

BITMAPINFOHEADER createBitmapHeader(int w, int h) { 
    //Manually creating the header for the bitmap.
    BITMAPINFOHEADER bi = { 0 };

    bi.biSize = sizeof(BITMAPINFOHEADER);
    bi.biWidth = w;
    bi.biHeight = -h;
    bi.biPlanes = 1;
    bi.biBitCount = 32;
    bi.biCompression = BI_RGB;
    bi.biSizeImage = 0;
    bi.biXPelsPerMeter = 0;
    bi.biYPelsPerMeter = 0;
    bi.biClrUsed = 0;
    bi.biClrImportant = 0;
    return bi;
}

HBITMAP screenCap(HWND hWnd, unsigned int x, unsigned int y, int gridSize) {
    //x and y here are the centre pixel of the bitmap. 
    //gridSize tells how big a square the bitmap is supposed to be
    if (gridSize % 2 == 0) {
        gridSize++; //this just makes sure that gridSize is odd
    }

    HDC hwinDC = GetDC(hWnd);
    HDC hwinCDC = CreateCompatibleDC(hwinDC);
    SetStretchBltMode(hwinCDC, COLORONCOLOR);
    //These give the coords of the bottom-left corner of the bitmap
    int capx = x - (gridSize - 1) / 2; 
    int capy = y - (gridSize - 1) / 2; 
    

    HBITMAP hbwin = CreateCompatibleBitmap(hwinDC, gridSize, gridSize);
    BITMAPINFOHEADER bi = createBitmapHeader(gridSize, gridSize);
    SelectObject(hwinCDC, hbwin);

    //this makes the buffer for the bitmap data to go into
    DWORD dwBmpSize = ((gridSize * bi.biBitCount + 31) / 32) * 4 * gridSize;
    HANDLE hDIB = GlobalAlloc(GHND, dwBmpSize);
    int* lpbitmap = (int*)GlobalLock(hDIB);
    //creates the vector for the buffer to be sent to
    std::vector<std::vector<int>> refGrid;
    refGrid.resize(gridSize);
    for (int i = 0; i < refGrid.size(); i++) {
        refGrid[i].resize(gridSize);
    }
    
    //stretchBlt the data to hwinCDC so that GetDIBits can read it
    StretchBlt(hwinCDC, 0, 0, gridSize, gridSize, hwinDC, capx, capy, gridSize, gridSize, SRCCOPY);
    //and send that to lpbitmap
    GetDIBits(hwinCDC, hbwin, 0, gridSize, lpbitmap, (BITMAPINFO*)&bi, DIB_RGB_COLORS);

    for (int i = 0; i < refGrid.size(); i++) {
        for (int j = 0; j < refGrid.size(); j++) {
            refGrid.at(i).at(j) = lpbitmap[i * refGrid.size() + j] - 0xff000000; 
            //this sends everything to the vector refGrid
            //subtracting 0xff000000 gets rid of the alpha value, which I don't need
        }
    }

    //now we cout the vector. I could just cout the lpbitmap, but this is more 
    //readable and is better for the calculations I'll need to do later
    for (int i = 0; i < refGrid.size(); i++) {
        for (int j = 0; j < refGrid.size(); j++) {
            std::cout << std::hex << refGrid[i][j] << " ";
        }
        std::cout << "\n";
    }
    
    //and finally, get rid of the DCs and return hbwin
    DeleteDC(hwinCDC);
    ReleaseDC(hWnd, hwinDC);
    return hbwin;
}

//the main call
int main() {

    GdiplusStartupInput gdiplusStartupInput;
    ULONG_PTR gdiplusToken;
    GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
    HWND hWnd = GetDesktopWindow();
    HBITMAP hBmp = screenCap(hWnd, 960, 100, 5);
        
    GdiplusShutdown(gdiplusToken);
    return 0;
}

I've mainly been working in 5x5 grids. These first vectors seem to be constant across anything I capture:

1st and 2nd run:

c0c0c c0c0c c0c0c c0c0c c0c0c
c0c0c c0c0c c0c0c c0c0c c0c0c
c0c0c c0c0c c0c0c c0c0c c0c0c
c0c0c c0c0c c0c0c c0c0c c0c0c
c0c0c c0c0c c0c0c c0c0c c0c0c

3rd run:

0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
0 0 0 0 0

Then, the 4th run has some different values for different captures.

For an all white image (with all pixels being ffffff), I get:

f1f1f1 f1f1f1 f1f1f1 f1f1f1 f1f1f1
efefef efefef efefef efefef efefef
ebebeb ebebeb ebebeb ebebeb ebebeb
e5e5e5 e5e5e5 e5e5e5 e5e5e5 e5e5e5
dfdfdf dfdfdf dfdfdf dfdfdf dfdfdf

But for an image with a bitmap of, say:

ffffff ed1c24 ffffff ffffff ffffff
ffffff ed1c24 ffffff ffffff ffffff
ed1c24 ed1c24 ed1c24 ed1c24 ed1c24
ffffff ed1c24 ffffff ffffff ffffff
ffffff ed1c24 ffffff ffffff ffffff

I get:

f1f1f1 e01a22 f1f1f1 f1f1f1 f1f1f1
efefef de1a22 efefef efefef efefef
da1a21 da1a21 da1a21 da1a21 da1a21
e5e5e5 d51920 e5e5e5 e5e5e5 e5e5e5
dfdfdf cf181f dfdfdf dfdfdf dfdfdf

Any runs past those four return the correct RGB values. I should also note that similar values return with a program that uses getPixel() to do the same thing, but slower.

I looked around, and I found pretty much nothing on this. I did notice that this cycle seems to start when the program window, the one created by main(), is at its top-leftmost point and continues down-right as I close and run the program again. I doubt that it's the window's fault, but I also don't know enough about the inner workings of Windows GUI to rule it out. The same is true for the aforementioned getPixel() version too, the only common link other than the couted values. If anyone could tell me why these values show up, and how that could be remedied, it would be appreciated


Solution

  • Your code is a bit messy, and I can't quite understand it (partly because I don't know GDI+ very well; I only know a bit of GDI). It looks like you want to capture a rectangular area of the screen and save it to a BITMAP, then output the pixel content to an array.
    Here are some areas that need improvement in your code:

    HBITMAP screenCap(HWND hWnd, unsigned int x, unsigned int y, int gridSize) ES.106: Don't try to avoid negative values by using unsigned.
    Why use GlobalAlloc instead of new, unique_ptr, or vector?
    std::vector<std::vector<int>> consumes more space and has worse access performance compared to a one-dimensional std::vector<int>.
    You don't need to stretch the image, so you should use BitBlt instead of StretchBlt. This improves performance and readability.

    Other than that, your program seems can work. However, how do you know which part of the screen your program has captured? It seems to me that this might be the reason your program appears to 'fail': your program works correctly, but the area you want to capture is not consistent with the area your program actually captures. c0c0c is the console window's color.

    The following program will capture the pixels of a specified area on the screen, output them, draw the captured area to the top-left corner of the screen, and invert the capture area.

    #include <windows.h>
    #include <iostream>
    #include <vector>
    #include <string>
    #include <cassert>
    
    HBITMAP screenCap(HWND hWnd, int x,int y,int width,int height) {
        assert(x >= 0 && y >= 0);
        assert(width > 0 && height > 0);
        //capture screen
        HDC screenDC = GetDC(hWnd);
        HDC memDC = CreateCompatibleDC(screenDC);
        HBITMAP hBitmap = CreateCompatibleBitmap(screenDC, width, height);
        SelectObject(memDC, hBitmap);
        BitBlt(memDC, 0, 0, width, height, screenDC, x, y, SRCCOPY);
        ReleaseDC(hWnd, screenDC); screenDC = nullptr;
        //Store pixels in an array
        std::vector<RGBQUAD> buffer(width * height);
        GetBitmapBits(hBitmap, width * height * 4, buffer.data());// GetDIBits should be used here, but GetBitmapBits is easy to use, and also works.
        DeleteDC(memDC); memDC = nullptr;
        // output as string
        std::string out; 
        out.reserve(width * height * 7);
        const char c[17] = "0123456789abcdef";
        for (int i = 0; i < height; ++i) {
            for (int j = 0; j < width; ++j) {
                //Convert pixels to hexadecimal string
                out += c[buffer[i * width + j].rgbRed >> 4];
                out += c[buffer[i * width + j].rgbRed & 0x0f];
                out += c[buffer[i * width + j].rgbGreen >> 4];
                out += c[buffer[i * width + j].rgbGreen & 0x0f];
                out += c[buffer[i * width + j].rgbBlue >> 4];
                out += c[buffer[i * width + j].rgbBlue & 0x0f];
                out += '\t';
            }
            out.back() = '\n';
        }
        std::cout << out;
        return hBitmap;
    }
    int main() {
        int x = 960;
        int y = 100;
        int width = 10;
        int height = 10;
        HBITMAP hBitmap= screenCap(NULL, x,y,width,height);
        //Show image and show captured area
        HDC screenDC = GetDC(NULL);
        HDC memDC = CreateCompatibleDC(screenDC);
        SelectObject(memDC, hBitmap);
        while (true) {
            BitBlt(screenDC, 0, 0, width, height, memDC, 0, 0, SRCCOPY);    // Draw the captured content to the top-left corner of the screen
            BitBlt(screenDC, x, y, width, height, memDC, 0, 0, NOTSRCCOPY); // Highlight the captured area
            Sleep(100);
        }
        ReleaseDC(NULL, screenDC);
        DeleteDC(memDC);
        DeleteObject(hBitmap);
    }
    

    P.S. My English is not very good, so please excuse any grammatical errors.