1

I have a bunch of squares and each has a specific identity/symbol I need to identify. So far I have something like:

#include <iostream>
#include <windows.h>

using namespace std;

int main() {
    HDC dc = GetDC(0);
    COLORREF color;
    int sum, x, y;

    while (true) {
        sum = 0;
        Sleep(100);
        for (x = 512; x < 521; x++) {
            for (y = 550; y < 565; y++) {
                color = GetPixel(dc, x, y);
                sum = GetRValue(color) + GetBValue(color) + GetGValue(color);
            }
        }
        cout << "SUM: " << sum << endl;
    }

    return 0;
}

Obviously it only scans one block so far. The problem is somehow even though it's only just over 100 pixels, it takes an INSANELY long time. I can't even imagine what could be going on. It takes well over a second, maybe two seconds, for each repetition. What can I do? There has to be a faster way to do this. If I can't query individual pixels, would there be a way to get a region of the screen? The zone is not inside my program's window.

Jonathan Potter
  • 36,172
  • 4
  • 64
  • 79
Lupe
  • 319
  • 1
  • 3
  • 16
  • 7
    Blit to a DIB, then read the memory directly. – Jonathan Potter Oct 10 '15 at 03:09
  • More amazing than that you always know the answers to my questions is that you seem to always be the first to answer, I think three or four times now as I lost an account. Thanks again – Lupe Oct 10 '15 at 03:19
  • 4
    Before reworking all your code you might want to just try exchanging which loop is inner and which is outer. Accessing pixels in non-sequential (memory) order can have a pretty severe impact on caching. – Cheers and hth. - Alf Oct 10 '15 at 03:27
  • Yeah get and set pixel functions are slow. It is because of the order of the info. Basically: get and set pixel is only useful when you need color mouse is on (or whatever you're working on) – Evan Carslake Oct 10 '15 at 04:16
  • Only the value of `sum` in the last iteration is being output, because you overwrite it each iteration. Did you mean `sum += GetRValue...`? – i_am_jorf Oct 10 '15 at 04:46
  • Oops yeah I edited a bit to paste it because I have some other stuff in there but that's what I'm doing. I'd think it'd be possible writing a driver in assembly but these speeds are just unfathomable. – Lupe Oct 10 '15 at 18:14
  • @Cheersandhth.-Alf: `GetPixel` is slow. Exchanging the order of access, to get a more sequential memory access pattern may speed up the loop by such a tiny fraction, that it will drown in statistical noise. Striving for sequential memory access is usually a good idea, but it won't make much of a difference here. – IInspectable Oct 11 '15 at 16:18
  • Create a DIB Section Image (32 bits), and access its bits directly (RGBA) – milevyo Oct 12 '15 at 19:11

1 Answers1

1

Jonathan comments in the question to use DIB, but there's no answer showing how. For the sake of completeness, here's Mister B's code:

COLORREF getcolor(POINT pt) {
    HDC hDc = GetDC(0);
    HDC hDcmem = CreateCompatibleDC(0);
    HBITMAP hBmp = CreateCompatibleBitmap(hDc, 1, 1);
    SelectObject(hDcmem, hBmp);
    BitBlt(hDcmem, 0, 0, 1, 1, hDc, pt.x, pt.y, SRCCOPY);
    LPBITMAPINFO lpbmi = new BITMAPINFO;
    lpbmi->bmiHeader.biBitCount = 24;
    lpbmi->bmiHeader.biCompression = BI_RGB;
    lpbmi->bmiHeader.biPlanes = 1;
    lpbmi->bmiHeader.biHeight = 1;
    lpbmi->bmiHeader.biWidth = 1;
    lpbmi->bmiHeader.biSize = sizeof(BITMAPINFO);
    BYTE lpvBits[4];
    GetDIBits(hDcmem, hBmp, 0, 1, lpvBits, lpbmi, DIB_RGB_COLORS);
    COLORREF currColor = RGB(lpvBits[2], lpvBits[1], lpvBits[0]);
    delete lpbmi;
    DeleteObject(hBmp);
    DeleteDC(hDcmem);
    ReleaseDC(0, hDc);
    return currColor;
}

This is still very slow as Ben points out in a comment, and the solution is to use these building blocks to copy the entire area at once. Your code would become something like this:

#include <iostream>
#include <vector>
#include <windows.h>

int main() {
    int const startX = 512;
    int const endX = 521;
    int const startX = 550;
    int const endY = 565;
    int const width = endX - startX;
    int const height = endY - startY;

    HDC const hDc = GetDC(0);
    HDC const hDcmem = CreateCompatibleDC(0);
    HBITMAP const hBmp = CreateCompatibleBitmap(hDc, width, height);
    auto const oldBmp = SelectObject(hDcmem, hBmp);

    BITMAPINFO bmi{};
    bmi.bmiHeader.biBitCount = 32;
    bmi.bmiHeader.biCompression = BI_RGB;
    bmi.bmiHeader.biPlanes = 1;
    bmi.bmiHeader.biHeight = height;
    bmi.bmiHeader.biWidth = width;
    bmi.bmiHeader.biSize = sizeof(BITMAPINFO);

    std::vector<RGBQUAD> pixels(height * width);

    while (true) {
        Sleep(100);

        BitBlt(hDcmem, 0, 0, width, height, hDc, startX, startY, SRCCOPY);
        GetDIBits(hDcmem, hBmp, 0, height, &pixels[0], &bmi, DIB_RGB_COLORS);
        int sum = 0;
        for (int i = 0; i < height * width; ++i) {
            sum = pixels[i].R + pixels[i].G + pixels[i].B;
        }

        std::cout << "SUM: " << sum << std::endl;
    }

    SelectObject(hDcmem, oldBmp);
    DeleteObject(hBmp);
    DeleteDC(hDcmem);
    ReleaseDC(0, hDc);

    return 0;
}
Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
Lonami
  • 5,945
  • 2
  • 20
  • 38
  • 1
    This is even worse than `GetPixel`. Most of it is not necessary either, you do need the `GetDIBits` call but you should use it to copy the entire rectangle you need to memory and then loop through it in memory. Also using dynamic allocation (`new`/`delete`) for the BITMAPINFO is just unnecessary. Also you need to `SelectObject` the original bitmap back in before destroying the bitmap and the DC. Sorry I know you didn't write these bugs, but that code cannot be used without a LOT of fixes, making it not useful to post as an answer. – Ben Voigt Oct 25 '20 at 13:49
  • 1
    Yeah, I realized after trying it out that it is still very slow, but at least it's used to demonstrate how you can use the call (of course, if you need an area, I presume you would modify it to select the entire area). The comment in the answer didn't give any code so it's hard to start without any pointers, and this at least gives pointers. – Lonami Oct 25 '20 at 14:08
  • @BenVoigt I have updated the answer providing more direction on how one would copy an entire area. Would you please consider reviewing it and taking out the downvote? – Lonami Oct 25 '20 at 14:19
  • 2
    I revised your new code to fix a mismatch between the pixel spacing and `biBitCount` and to deselect the bitmap before deleting it. – Ben Voigt Oct 25 '20 at 19:03
  • Thanks, I don't really write C++, so I adapted the Mister B's code to what I thought was necessary to make it copy the entire area with the little knowledge I have. My goal with the answer was to give pointers and document Mister B's answer somewhere else (here, in StackOverflow) so people could work from there, not necessarily provide the best code. In the end I guess we both are happy with the result. – Lonami Oct 25 '20 at 19:19
  • 1
    StackOverflow already some examples and discussion for using `GetDiBits` at https://stackoverflow.com/q/3688409/103167 – Ben Voigt Oct 25 '20 at 19:28