Related
I'm using this function to make a screenshot of a window. It works fine but at games like Minecraft it's zoomed in.
Normal
Screenshot
cv::Mat getMat(HWND hWND) {
HDC deviceContext = GetDC(hWND);
HDC memoryDeviceContext = CreateCompatibleDC(deviceContext);
RECT windowRect;
GetClientRect(hWND, &windowRect);
int height = windowRect.bottom;
int width = windowRect.right;
HBITMAP bitmap = CreateCompatibleBitmap(deviceContext, width, height);
SelectObject(memoryDeviceContext, bitmap);
//copy data into bitmap
BitBlt(memoryDeviceContext, 0, 0, width, height, deviceContext, 0, 0, SRCCOPY);
//specify format by using bitmapinfoheader!
BITMAPINFOHEADER bi;
bi.biSize = sizeof(BITMAPINFOHEADER);
bi.biWidth = width;
bi.biHeight = -height;
bi.biPlanes = 1;
bi.biBitCount = 32;
bi.biCompression = BI_RGB;
bi.biSizeImage = 0; //because no compression
bi.biXPelsPerMeter = 1;
bi.biYPelsPerMeter = 2;
bi.biClrUsed = 3;
bi.biClrImportant = 4;
cv::Mat mat = cv::Mat(height, width, CV_8UC4); // 8 bit unsigned ints 4 Channels -> RGBA
//transform data and store into mat.data
GetDIBits(memoryDeviceContext, bitmap, 0, height, mat.data, (BITMAPINFO*)&bi, DIB_RGB_COLORS);
//clean up!
DeleteObject(bitmap);
DeleteDC(memoryDeviceContext); //delete not release!
ReleaseDC(hWND, deviceContext);
return mat;
}
According to the documentation, windowRect.bottom is not the height. Or windowRect.right is not the width. https://learn.microsoft.com/en-us/windows/win32/api/windef/ns-windef-rect
So, you should do something like this:
int height = windowRect.bottom - windowRect.top;
int width = windowRect.right - windowRect.left;
EDIT1:
I have found an issue similar to yours. Maybe it could help you as well.
ClientRect mysteriously smaller than WindowRect?
Cheers.
I am trying to get HBIPMAP working by reusing the HBITMAP and the HDC for performance reasons.
This is a small test project I wanted to do to learn more about CPU based Rasterization. For the window, im using SDL2.
The below code works if we comment out:
DeleteDC(hdcMem);
hdcMem = CreateCompatibleDC(device);
I can not find any example in the year 2018+.
mBackBuffer is just a Vector(DWORD)
void Device::createDeviceFromHWND(const HWND& hwnd, const int& width, const int& height)
{
// This is hacked code for an example.
auto device = GetDC(hwnd);
DWORD colorSize = 4; // ARGB;
// Create page section
// https://learn.microsoft.com/en-us/windows/desktop/memory/creating-named-shared-memory
HANDLE hMapFile;
LPCTSTR pBuf;
// https://learn.microsoft.com/en-us/windows/desktop/api/winbase/nf-winbase-createfilemappinga
hMapFile = CreateFileMappingA
(
INVALID_HANDLE_VALUE,
NULL,
PAGE_READWRITE,
0,
width * height * colorSize,
NULL
);
if (hMapFile == NULL)
{
return;
}
DWORD* buffer = (DWORD*)MapViewOfFile(
hMapFile,
FILE_MAP_ALL_ACCESS,
0,
0,
width * height * colorSize
);
BITMAPINFOHEADER header;
memset(&header, 0, sizeof(BITMAPINFOHEADER));
// https://msdn.microsoft.com/en-us/02f8ed65-8fed-4dda-9b94-7343a0cfa8c1
header.biSize = sizeof(BITMAPINFOHEADER);
header.biWidth = width;
header.biHeight = height;
header.biPlanes = 1;
header.biBitCount = 32;
header.biCompression = BI_RGB;
header.biSizeImage = width * height * sizeof(BYTE);
header.biXPelsPerMeter = 0;
header.biYPelsPerMeter = 0;
header.biClrUsed = 0;
header.biClrImportant = 0;
tagBITMAPINFO bitmap;
memset(&bitmap, 0, sizeof(tagBITMAPINFO));
// https://learn.microsoft.com/en-us/windows/desktop/api/wingdi/ns-wingdi-tagbitmapinfo
tagRGBQUAD RGBQUAD;
memset(&RGBQUAD, 0, sizeof(tagRGBQUAD));
bitmap.bmiHeader = header;
bitmap.bmiColors[0] = RGBQUAD;
LPVOID p;
// https://learn.microsoft.com/en-us/windows/desktop/api/wingdi/nf-wingdi-createdibsection
auto hBitMap = CreateDIBSection
(
device,
&bitmap,
DIB_RGB_COLORS,
&p,
hMapFile,
0
);
for (DWORD i = 0; i < width * height; ++i)
{
buffer[i] = 0xFF0000;
}
HDC hdcMem = CreateCompatibleDC(device);
auto oldHBITMAP = (HBITMAP)SelectObject(hdcMem, hBitMap);
BitBlt(
device,
0,
0,
width,
height,
hdcMem,
0,
0,
SRCCOPY
);
DeleteDC(hdcMem);
for (DWORD i = 0; i < width * height; ++i)
{
buffer[i] = 0;
}
hdcMem = CreateCompatibleDC(device);
BitBlt(
device,
400,
300,
width,
height,
hdcMem,
0,
0,
SRCCOPY
);
}
The output is a red screen, but you should see the black section in corner right.
There are several issues here, some not related to bitmap.
The handle from GetDC should be cleaned up by ReleaseDC when the handle is no longer needed.
The handle from CreateFileMapping should be cleaned up by CloseHandle, and MapViewOfFile should be cleaned up by UnmapViewOfFile.
HBITMAP handle must be cleaned up by DeleteObject
It is recommend to cleanup after SelectObject by calling SelectOject(hMemDC, oldHBitmap)
If you don't restore the old bitmap, and try to delete hMemDC, Windows cannot fulfill the request because there is another bitmap selected in device context. Windows will try to fix this error but it may fail if the code is too convoluted.
Note that Windows gives you a limit of 10,000 GDI handles. The application will crash very quickly if you don't manage these handles properly. Refer to WinAPI documentation for these functions. If in doubt, use the task manager to monitor "GDI handles" for your program.
The code should work as expected once you fix these problems, see the example below.
This of course is for demonstration only. In real world application you probably want to save the HBITMAP in the heap, instead of stack, as well as some other values. You want to minimize repeated creating these handles.
As noted in other answers and comments, painting should be done in response to WM_PAINT, where you get HDC from BeginPaint (and cleanup with EndPaint). Therefore you should avoid GetDC/ReleaseDC
void Device::createDeviceFromHWND(const HWND& hwnd, const int& width, const int& height)
{
auto hdc = GetDC(hwnd);
auto hMapFile = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0,
width * height * sizeof(DWORD), NULL);
auto buffer = (DWORD*)MapViewOfFile(hMapFile, FILE_MAP_ALL_ACCESS, 0, 0,
width * height * sizeof(DWORD));
BITMAPINFOHEADER biheader = { sizeof(biheader), width, height, 1, 32, BI_RGB };
LPVOID bits;
auto hbitmap = CreateDIBSection(hdc, (BITMAPINFO*)&biheader, DIB_RGB_COLORS,
&bits, hMapFile, 0);
for(int i = 0; i < width * height; ++i)
buffer[i] = 0xFF0000;
auto memdc = CreateCompatibleDC(hdc);
auto oldhbitmap = SelectObject(memdc, hbitmap);
BitBlt(hdc, 0, 0, width, height, memdc, 0, 0, SRCCOPY);
for(int i = 0; i < width * height; ++i)
buffer[i] = 0;
BitBlt(hdc, 0, 0, 100, 100, memdc, 0, 0, SRCCOPY);
SelectObject(memdc, oldhbitmap); //<- ***EDIT***
//oldhbitmap is selected in to memdc, now we can destroy hbitmap and memdc
DeleteObject(hbitmap);
DeleteDC(memdc);
ReleaseDC(hwnd, hdc);
UnmapViewOfFile(buffer);
CloseHandle(hMapFile);
}
Side note, you don't gain anything by using reference operator & for constant values. Just change the function prototype as follows:
void createDeviceFromHWND(const HWND hwnd, const int width, const int height);
Also, this can be done without CreateFileMapping, and use buffer shown below. buffer will be valid as long as hbitmap is valid.
void test(const HWND hwnd, const int w, const int h)
{
auto hdc = GetDC(hwnd);
//use the negative value of height, so bitmap bits are not upside-down
BITMAPINFOHEADER bi = { sizeof(bi), w, -h, 1, 32, BI_RGB };
DWORD* buffer;
auto hbitmap = CreateDIBSection(hdc, (BITMAPINFO*)&bi, DIB_RGB_COLORS,
(void**)&buffer, NULL, 0);
auto memdc = CreateCompatibleDC(hdc);
auto oldbmp = SelectObject(memdc, hbitmap);
for(int i = 0; i < w * h; ++i) buffer[i] = 0xFF0000;
BitBlt(hdc, 0, 0, w, h, memdc, 0, 0, SRCCOPY);
//draw black square on top-left
for(int y = 0; y < 100; y++)
for(int x = 0; x < 100; x++)
buffer[y * w + x] = 0;
BitBlt(hdc, 0, 0, 100, 100, memdc, 0, 0, SRCCOPY);
//cleanup:
SelectObject(memdc, oldbmp);
DeleteObject(hbitmap); //<- buffer is not valid after hbitmap is destroyed
DeleteDC(memdc);
ReleaseDC(hwnd, hdc);
}
This approach is wrong. When the target window gets WM_PAINT all your work will be undone.
Always paint your windows the way you want them with WM_PAINT and BeginPaint.
You are not editing the bitmap here:
for (unsigned int i = 0; i < width * height; ++i)
{
mBackBuffer[i] = 0;
}
just filling the array, the bitmap was created from, with nulls.
In C++, I want to create a simple transparent image with Gdiplus and save it as a png. I have the following code:
// These variables are class members and got initialized and are used elsewhere
BITMAPINFO bmi;
HDC hdc;
void* pvBits;
ZeroMemory(&bmi, sizeof(BITMAPINFO));
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bmi.bmiHeader.biWidth = some_width;
bmi.bmiHeader.biHeight = some_height;
bmi.bmiHeader.biPlanes = 1;
bmi.bmiHeader.biBitCount = 32;
bmi.bmiHeader.biCompression = BI_RGB;
bmi.bmiHeader.biSizeImage = ((((bmi.bmiHeader.biWidth * bmi.bmiHeader.biBitCount) + 31) & ~31) >> 3) * bmi.bmiHeader.biHeight;
HBITMAP hBM = CreateDIBSection(hDC, &bmi, DIB_RGB_COLORS, &pvBits, NULL, 0x0);
FillMemory(pvBits, bmi.bmiHeader.biSizeImage, 255);
HGDIOBJ oldObj = SelectObject(hDC, hBM);
ReleaseDC(NULL, hDC);
GdiFlush();
GdiPlusBitmap* bitmap = new Gdiplus::Bitmap(&bmi, pvBits);
When I save the image to png, I can see that there is an alpha channel, but its set to 0, so nothing transparent (RGB is all set). I also tried changing the 255 to 0, but that only gives me a black image with no transpirancy. Why is the Fillmemory call not filling the alpha channel, or am I missing something else?
FillMemory(pvBits, bmi.bmiHeader.biSizeImage, 255) will fill the memory with solid white color. If you write over the image with GDI functions, the alpha values remain unchanged. You won't see any transparency even if the file supports it.
To create 32-bit image you only need Gdiplus::Bitmap(w, h, PixelFormat32bppARGB). There is no need for BITMAPINFO and CreateDIBSection
If you mix GDI+ with GDI functions, you may want to reset alpha after writing with GDI functions. For example:
void test()
{
int w = 100;
int h = 100;
int bitcount = 32;
int size = ((((w * bitcount) + 31) & ~31) >> 3) * h;
BITMAPINFO bmi = { 0 };
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bmi.bmiHeader.biWidth = w;
bmi.bmiHeader.biHeight = -h;
bmi.bmiHeader.biPlanes = 1;
bmi.bmiHeader.biBitCount = bitcount;
bmi.bmiHeader.biCompression = BI_RGB;
bmi.bmiHeader.biSizeImage = size;
HDC hdc = GetDC(0);
BYTE* pvBits = NULL;
HBITMAP hbitmap = CreateDIBSection(hdc, &bmi, DIB_RGB_COLORS,
(void**)&pvBits, NULL, 0x0);
FillMemory(pvBits, size, 0);
auto memdc = CreateCompatibleDC(hdc);
auto oldbmp = SelectObject(memdc, hbitmap);
SetBkColor(memdc, RGB(255, 0, 0));
TextOut(memdc, 0, 0, L"123", 3);
//GDI cleanup, don't delete hbitmap yet
SelectObject(memdc, oldbmp);
DeleteDC(memdc);
ReleaseDC(0, hdc);
//make the non-zero colors transparent:
for(int i = 0; i < size; i += 4)
{
int n = *(int*)(pvBits + i);
if (n != 0)
pvBits[i + 3] = 255;
}
CLSID clsid_png;
CLSIDFromString(L"{557cf406-1a04-11d3-9a73-0000f81ef32e}", &clsid_png);
Gdiplus::Bitmap* bitmap = new Gdiplus::Bitmap(w, h, PixelFormat32bppARGB);
Gdiplus::BitmapData data;
bitmap->LockBits(&Gdiplus::Rect(0, 0, w, h),
Gdiplus::ImageLockModeWrite, PixelFormat32bppARGB, &data);
memcpy(data.Scan0, pvBits, size);
bitmap->UnlockBits(&data);
//safe to delete hbitmap
DeleteObject(hbitmap);
bitmap->Save(L"test.png", &clsid_png);
delete bitmap;
}
Your code suffers from the same hdc problems as in previous question. A filling image with 0 should give a completely transparent black image as expected.
But I think it would be simpler to create bitmap directly specifying dimensions and pixel format and set content later:
Gdiplus::Bitmap* bitmap = new Gdiplus::Bitmap(width, height, PixelFormat32bppARGB);
In transparent images, the value 0 means "full transparent" and 255 means
"full opaque".
Need to convert a color bitmap to a grey scale. Here is the approach:
HDC hdcWindow = GetDC(hWnd);
HBITMAP hDIBBitmap;
{
// Create a compatible DC which is used in a BitBlt from the window DC
HDC hdcMemDC = CreateCompatibleDC(hdcWindow);
SelectObject(hdcMemDC, bmHatch);
BITMAPINFO bmi;
memset(&bmi, 0, sizeof(BITMAPINFO));
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bmi.bmiHeader.biWidth = bm.bmWidth;
bmi.bmiHeader.biHeight = bm.bmHeight; // top-down
bmi.bmiHeader.biPlanes = bm.bmPlanes;
bmi.bmiHeader.biBitCount = bm.bmBitsPixel;
UINT* pBits;
HBITMAP hDIBBitmap = CreateDIBSection(hdcMemDC, &bmi, DIB_RGB_COLORS, (void**)&pBits, NULL, NULL);
for (int i = 0; i < bm.bmWidth; i++) {
for (int j = 0; j < bm.bmHeight; j++) {
UINT val = pBits[i + j * bm.bmWidth];
if (val != 0)
{
COLORREF clr = val;
UINT newVal = (GetRValue(clr) + GetBValue(clr) + GetGValue(clr)) / 3;
pBits[i + j * bm.bmWidth] = newVal;
}
}
}
SelectObject(hdcMemDC, hDIBBitmap);
// draw content of memory bitmap in the window
BitBlt(hdcWindow, 0, 0, bm.bmWidth, bm.bmHeight, hdcMemDC, 0, 0, SRCCOPY);
DeleteObject(hDIBBitmap);
DeleteDC(hdcMemDC);
}
ReleaseDC(hWnd, hdcWindow);
In the above code sample, the input bitmap is given by bm which is a Bitmap instance.
Created a compatible DC. Loaded the bitmap into using the selectObject statement.
Then wanted to change the bits by creating the DIB section for traversing the bitmap values. After changing the values, the hDIBBitmap is selected and then finally drawing using the BitBlt function.
When I comment out the following line, I can see the original bitmap rendered correctly:
SelectObject(hdcMemDC, hDIBBitmap);
What I observed was the pBits is always 0 and hence the transformed gray scale image is not being rendered and instead getting a black picture.
Please advise on what is wrong with this approach.
CreateDIBSection is creating a blank bitmap with that usage. None of the bits from the original bitmap are used. If you paint it you get a black bitmap.
If you comment out SelectObject(hdcMemDC, hDIBBitmap) then this new hDIBBitmap is ignored as well. You print the original bitmap which was selected in device context earlier: SelectObject(hdcMemDC, bmHatch)
To convert HBITMAP to gray scale use the following code. Note this will not work for palette bitmaps.
Converting 24-bit or 32-bit HBITMAP to grayscale:
void grayscale(HBITMAP hbitmap)
{
BITMAP bm;
GetObject(hbitmap, sizeof(bm), &bm);
if(bm.bmBitsPixel < 24)
{
DebugBreak();
return;
}
HDC hdc = GetDC(HWND_DESKTOP);
DWORD size = ((bm.bmWidth * bm.bmBitsPixel + 31) / 32) * 4 * bm.bmHeight;
BITMAPINFO bmi
{sizeof(BITMAPINFOHEADER),bm.bmWidth,bm.bmHeight,1,bm.bmBitsPixel,BI_RGB,size};
int stride = bm.bmWidth + (bm.bmWidth * bm.bmBitsPixel / 8) % 4;
BYTE *bits = new BYTE[size];
GetDIBits(hdc, hbitmap, 0, bm.bmHeight, bits, &bmi, DIB_RGB_COLORS);
for(int y = 0; y < bm.bmHeight; y++) {
for(int x = 0; x < stride; x++) {
int i = (x + y * stride) * bm.bmBitsPixel / 8;
BYTE gray = BYTE(0.1 * bits[i+0] + 0.6 * bits[i+1] + 0.3 * bits[i+2]);
bits[i+0] = bits[i+1] = bits[i+2] = gray;
}
}
SetDIBits(hdc, hbitmap, 0, bm.bmHeight, bits, &bmi, DIB_RGB_COLORS);
ReleaseDC(HWND_DESKTOP, hdc);
delete[]bits;
}
Usage
//convert to grayscale
//this should be called once
grayscale(hbitmap);
//paint image
HDC memdc = CreateCompatibleDC(hdc);
HBITMAP oldbmp = SelectObject(memdc, hbitmap);
BITMAP bm;
GetObject(hbitmap, sizeof(bm), &bm);
BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, memdc, 0, 0, SRCCOPY);
...
According to MSDN documentation on GDI function RealizePalette():
The RealizePalette function modifies the palette for the device
associated with the specified device context. If the device context is
a memory DC, the color table for the bitmap selected into the DC is
modified.
Behavior described in italics doesn't seem to work in the following code:
void test_color_tbl_modify(HWND hWnd) {
// initialize BITMAPINFO struct for a mono bitmap(red&white)
const int bmp_w = 16, bmp_h = 32;
std::vector<BYTE> vBytes(sizeof(BITMAPINFO) + sizeof(RGBQUAD) * 2); // for mono bitmaps, bmiColors member of BITMAPINFO contains two RGBQUAD elems
BITMAPINFO* pbmi = reinterpret_cast<BITMAPINFO*>(vBytes.data());
{
BITMAPINFOHEADER bih{};
{
bih.biSize = sizeof(BITMAPINFOHEADER);
bih.biWidth = bmp_w;
bih.biHeight = bmp_h;
bih.biPlanes = 1;
bih.biBitCount = 1;
bih.biCompression = BI_RGB;
bih.biSizeImage = ((bih.biWidth * bih.biBitCount + 31) & ~31) / 8 // each scanline aligned on DWORD boundary
* bih.biHeight;
bih.biClrUsed = 2;
bih.biClrImportant = 0;
}
pbmi->bmiHeader = bih;
pbmi->bmiColors[0] = RGBQUAD{0,0,255,0}; // red
pbmi->bmiColors[1] = RGBQUAD{255,255,255,0}; // white
}
// create a mono DDB
HDC hdc = GetDC(hWnd);
HDC hdcmem = CreateCompatibleDC(hdc); // has 1x1 mono bitmap selected into it by default
HBITMAP hbitmap = CreateCompatibleBitmap(hdcmem, bmp_w, bmp_h); // creates a mono-bitmap(see above)
// draw something
HGDIOBJ hOldBmp = SelectObject(hdcmem, hbitmap);
for(int y = 0; y < bmp_h; ++y) {
for(int x = 0; x < bmp_w; ++x) {
COLORREF col = x & 1 && y & 1 ? RGB(255, 255, 255) : RGB(0, 0, 0);
SetPixel(hdcmem, x, y, col);
}
}
// blit image to client area by creating a DIB section and
// copying DIBits to it from hbitmap(works as expected: red&white image)
{
// get DIBits
std::vector<BYTE> vDIBits_buf(pbmi->bmiHeader.biSizeImage);
void* pvBits = vDIBits_buf.data();
SelectObject(hdcmem, hOldBmp); // hbitmap must NOT be selected into a DC before GetDIBits() call as per documentation
if(!GetDIBits(hdc, hbitmap,
0, (UINT)bmp_h,
pvBits, pbmi, DIB_RGB_COLORS)) { // resets biClrUsed & biClrImportant to 0, and pbmi->bmiColors to black&white -- why??
MessageBox(hWnd, L"GetDIBits has failed", L"Failed", MB_OK);
}
// reset members modified(for some reason) by GetDIBits()
// back to their original values
pbmi->bmiHeader.biClrUsed = 2;
pbmi->bmiColors[0] = RGBQUAD{0,0,255,0};
pbmi->bmiColors[1] = RGBQUAD{255,255,255,0};
// copy DIBits from hbitmap to hDIBSecion
void* pvBits_dest = nullptr;
HBITMAP hDIBSecion = CreateDIBSection(hdc, pbmi, DIB_RGB_COLORS, &pvBits_dest, NULL, 0);
memcpy(pvBits_dest, pvBits, pbmi->bmiHeader.biSizeImage);
// blit hDIBSecion to client area
HGDIOBJ hOldBmp = SelectObject(hdcmem, hDIBSecion);
BitBlt(hdc, 0, 0, bmp_w, bmp_h, hdcmem, 0, 0, SRCCOPY); // blits a red&white image as expected
// clean up
SelectObject(hdcmem, hOldBmp);
DeleteObject(hDIBSecion);
}
// blit image to client area by modifying color table of hbitmap
// (does not work as expected: black&white image instead of green&black)
{
// initialize palette(green&black)
std::vector<BYTE> log_palette_buf(sizeof(LOGPALETTE) + sizeof(PALETTEENTRY) * 2);
LOGPALETTE* plplt = reinterpret_cast<LOGPALETTE*>(log_palette_buf.data());
plplt->palNumEntries = 2;
plplt->palVersion = 0x0300;
PALETTEENTRY pe_black{0,0,0,0};
PALETTEENTRY pe_green{0,255,0,0};
plplt->palPalEntry[0] = pe_green;
plplt->palPalEntry[1] = pe_black;
// modify color table of hbitmap to green&black
HGDIOBJ hOldBmp = SelectObject(hdcmem, hbitmap); // select hbitmap into memory DC first
HPALETTE hp = CreatePalette(plplt);
HPALETTE hp_old = SelectPalette(hdcmem, hp, FALSE);
RealizePalette(hdcmem); // supposed to modify color table of selected bitmap(hbitmap) according to documentation
// blit hbitmap to client area
BitBlt(hdc, bmp_w, 0, bmp_w, bmp_h, hdcmem, 0, 0, SRCCOPY); // ???blits a black&white image???
// clean-up
SelectObject(hdcmem, hOldBmp);
SelectPalette(hdcmem, hp_old, FALSE);
DeleteObject(hp);
}
// clean up
DeleteObject(hbitmap);
DeleteDC(hdcmem);
ReleaseDC(hWnd, hdc);
}
What am I missing?