Обновление:
Оказывается, эта проблема на самом деле гораздо серьезнее, чем я думал. Я немного дописал main()
, чтобы функция screenCap()
запускалась несколько раз, используя цикл for
и вызывая значения из массива для центрального пикселя захвата. Когда я запустил это, каждый второй захват, который был обработан, возвращал c0c0c. По общему признанию, я мог бы просто сделать так, чтобы функция запускалась снова, чтобы получить правильные значения, поскольку она выполняется быстро или, по крайней мере, достаточно быстро для моих целей, но я бы предпочел избежать любых возможных осложнений с этим, таких как скорость или память, если это может быть проблемой. Эти значения c0c0c действительно кажутся здесь постоянными, а не какие-либо другие.
Здравствуйте, и извините, если проблема, которую я описываю, действительно очевидна. Я новичок в программировании для Windows и использовании GDI+.
Я пишу программу, которая считывает область n*n на экране и отправляет полученные пиксели в двумерный вектор для последующих вычислений. Я продумывал вектор, чтобы иметь возможность сопоставить его с версией изображения, чтобы убедиться, что он получает правильные данные, в основном только для целей отладки. Обычно это работает, но время от времени я получаю одну и ту же последовательность нечетных значений: вектор, полный c0c0c, вектор, полный c0c0c, вектор, полный 0, и, наконец, вектор, полный случайных значений. После этого запуск программы возвращает правильные значения растрового изображения.
Ниже приведена упрощенная версия моего кода без сохранения изображений и прочего:
#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;
}
В основном я работаю с сетками 5х5. Эти первые векторы кажутся постоянными во всем, что я снимаю:
1-й и 2-й запуск:
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
3-й запуск:
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
Затем четвертый прогон имеет разные значения для разных захватов.
Для полностью белого изображения (со всеми пикселями ffffff) я получаю:
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
Но для изображения с растровым изображением, скажем:
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
Я получил:
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
Любые прогоны после этих четырех возвращают правильные значения RGB. Я также должен отметить, что аналогичные значения возвращаются в программе, которая использует getPixel()
для выполнения того же самого, но медленнее.
Я осмотрелся и почти ничего не нашел по этому поводу. Я заметил, что этот цикл, кажется, начинается, когда окно программы, созданное main()
, находится в крайней левой верхней точке и продолжается вниз вправо, когда я закрываю и снова запускаю программу. Я сомневаюсь, что это ошибка окна, но я также недостаточно знаю о внутренней работе графического интерфейса Windows, чтобы исключить это. То же самое верно и для вышеупомянутой версии getPixel()
, единственной общей ссылки, кроме вычисленных значений. Если бы кто-нибудь мог сказать мне, почему появляются эти значения и как это можно исправить, я был бы признателен.
Я не уверен, по какой причине, но я увидел что-то не так в вашем коде. 1) pixel row in BMP has a padding.
Я не увидел в вашем коде кода для расчета заполнения. 2) 1st pixel of BMP pixel table it's on the bottom.
Мы должны читать снизу вверх, кажется, что ваш код это учитывает. 3) pixel binary format in BMP is B-G-R not R-G-B.
Формат BMP
Пожалуйста, объясните логику внутри скриншота.
Ваш код немного запутан, и я не могу его понять (отчасти потому, что я не очень хорошо знаю GDI+; я знаю GDI лишь немного). Похоже, вы хотите захватить прямоугольную область экрана и сохранить ее в BITMAP, а затем вывести содержимое пикселей в массив.
Вот некоторые области вашего кода, которые нуждаются в улучшении:
HBITMAP screenCap(HWND hWnd, unsigned int x, unsigned int y, int gridSize)
ES.106: Не пытайтесь избежать отрицательных значений, используя беззнаковые значения.
Зачем использовать GlobalAlloc
вместо new
, unique_ptr
или vector
?std::vector<std::vector<int>>
занимает больше места и имеет худшую производительность доступа по сравнению с одномерным std::vector<int>
.
Растягивать изображение не нужно, поэтому используйте BitBlt
вместо StretchBlt
. Это улучшает производительность и читаемость.
В остальном ваша программа, кажется, может работать. Однако как узнать, какую часть экрана захватила ваша программа? Мне кажется, что это может быть причиной «сбоя» вашей программы: ваша программа работает правильно, но область, которую вы хотите захватить, не соответствует области, которую ваша программа фактически захватывает. c0c0c
— цвет окна консоли.
Следующая программа захватит пиксели указанной области на экране, выведет их, нарисует захваченную область в верхнем левом углу экрана и инвертирует область захвата.
#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. Мой английский не очень хорош, поэтому прошу извинить за грамматические ошибки.
На самом деле я понял, что значение c0c0c было цветом консоли по умолчанию, но я не мог понять, как мои координаты могли определяться консолью. Я только что провел еще несколько тестов, и кажется, что (0,0) на экране на самом деле является верхним левым углом, а не левым нижним, как я думал! c0c0c — это окно, а 0 — заголовок. Также я приму к сведению и другие ваши замечания. Они кажутся лучше, чем решения, которые я придумал.
Прежде чем использовать захваченные пиксели, всегда не забывайте проверять фактическое место захвата. Если вам нужно проверить захваченные пиксели, вы можете просто нарисовать их прямо на экране, что гораздо проще, чем сохранять в файл.
Я не уверен (в последний раз я писал код GDI, должно быть, более 15 лет назад), но вы устанавливаете тип растрового изображения и формат пикселей вручную. Но я думаю, вам нужно создать растровое изображение, соответствующее формату растрового изображения вашего дисплея. Learn.microsoft.com/en-us/windows/win32/api/wingdi/…