How can I keep reusing HBITMAP and HDC continually? - c++

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.

Related

Converting Color Bitmap to Grayscale in C++

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

Win32/GDI: How to use RealizePalette() to modify color table?

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?

How to draw RGB pixel data from memory with GDI in C++

I have a pointer to RGB data (640x480x3 bytes) that I want to draw into a window using BitBlt or something else equally fast. How do I convert the RGB data into something usable with BitBlt (for example).
Here is what I tried so far (without success)
unsigned char *buf = theVI->getPixels(0);
int size = theVI->getSize(0);
int h = theVI->getHeight(0);
int w = theVI->getWidth(0);
HDC dc = GetDC(hwnd);
HDC dcMem = CreateCompatibleDC(dc);
HBITMAP bmp = CreateBitmap(w, h, 1, 24, buf);
SelectObject(dcMem, bmp);
BitBlt(dc, 0, 0, w, h, dcMem, 0, 0, SRCCOPY);
Thanks
UPDATE: this is the working code...
HDC dc = GetDC(hwnd);
BITMAPINFO info;
ZeroMemory(&info, sizeof(BITMAPINFO));
info.bmiHeader.biBitCount = 24;
info.bmiHeader.biWidth = w;
info.bmiHeader.biHeight = h;
info.bmiHeader.biPlanes = 1;
info.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
info.bmiHeader.biSizeImage = size;
info.bmiHeader.biCompression = BI_RGB;
StretchDIBits(dc, 0, 0, w, h, 0, 0, w, h, buf, &info, DIB_RGB_COLORS, SRCCOPY);
ReleaseDC(hwnd, dc);
You can render your bytes ( that are so called DIB device independent bitmap ) to HDC using StretchDIBits API function.
And yet check DIB article in MSDN

Getting RGB data of a minimised window

I want to get RGB data of a particular part of a running window which is minimised and I'm not quite sure how to do it. Here is my piece of code for getting bitmap info of a window for a specified rectangle.
BYTE* LoadBMPFromHandle(HWND hwnd, int leftTopX, int leftTopY, int width, int height)
{
BITMAPINFO bmi;
BITMAPINFOHEADER bmpiHeader;
HDC dc, memdc;
HBITMAP hbWnd;
bmpiHeader.biSize = sizeof(BITMAPINFOHEADER);
bmpiHeader.biPlanes = 1;
bmpiHeader.biBitCount = 24;
bmpiHeader.biCompression = BI_RGB;
bmpiHeader.biSizeImage = 0;
bmpiHeader.biXPelsPerMeter = 3780;
bmpiHeader.biYPelsPerMeter = 3780;
bmpiHeader.biClrUsed = 0;
bmpiHeader.biClrImportant = 0;
bmpiHeader.biWidth = width;
bmpiHeader.biHeight = height;
bmi.bmiHeader = bmpiHeader;
dc = GetDC(hwnd);
memdc = CreateCompatibleDC(dc);
hbWnd = CreateCompatibleBitmap(dc, width, height);
SelectObject(memdc, hbWnd);
BitBlt(memdc, 0, 0, width, height, dc, leftTopX, leftTopY, SRCCOPY);
int paddedWidth = width + (4-(width%4))%4;
BYTE* BGRwnd = new BYTE [paddedWidth*height*3];
if( 0 == GetDIBits(dc, hbWnd, 0, height, BGRwnd, &bmi, DIB_RGB_COLORS) )
{
cout <<"GetDIBits function fail..." << endl;
DeleteDC(memdc);
DeleteObject(hbWnd);
ReleaseDC(hwnd, dc);
exit(0);
}
DeleteDC(memdc);
DeleteObject(hbWnd);
ReleaseDC(hwnd, dc);
return BGRwnd;
}
However, this function would only work if there is no another window overlapping with the one I have a handle to.
hwnd - handle to my window
LoadBMPFromHandle has to retrieve BGR (blue green red) data of a rectangle with upper left coordinates (leftTopX, leftTopY) and dimesions width x height.
Thank You!

Loading bitmap in particular way

I have a function that essentially takes a screen shot and saves a pointer to it as a structure. I would like to use the same structure for bitmaps that I load from files.
typedef struct _BITMAPCAPTURE {
HBITMAP hbm;
LPDWORD pixels;
INT width;
INT height;
} BITMAPCAPTURE;
BOOL CaptureScreen(BITMAPCAPTURE* bmpCapture)
{
BOOL bResult = FALSE;
if(!bmpCapture)
return bResult;
ZeroMemory(bmpCapture, sizeof(BITMAPCAPTURE));
HDC hdcScreen = GetDC(NULL);
HDC hdcCapture = CreateCompatibleDC(NULL);
int nWidth = GetSystemMetrics(SM_CXVIRTUALSCREEN),
nHeight = GetSystemMetrics(SM_CYVIRTUALSCREEN);
// Bitmap is stored top down as BGRA,BGRA,BGRA when used as
// DWORDs endianess would change it to ARGB.. windows COLORREF is ABGR
LPBYTE lpCapture;
BITMAPINFO bmiCapture = { {
sizeof(BITMAPINFOHEADER), nWidth, -nHeight, 1, 32, BI_RGB, 0, 0, 0, 0, 0,
} };
bmpCapture->hbm = CreateDIBSection(hdcScreen, &bmiCapture,
DIB_RGB_COLORS, (LPVOID *)&lpCapture, NULL, 0);
if(bmpCapture->hbm){
HBITMAP hbmOld = (HBITMAP)SelectObject(hdcCapture, bmpCapture->hbm);
BitBlt(hdcCapture, 0, 0, nWidth, nHeight, hdcScreen, 0, 0, SRCCOPY);
SelectObject(hdcCapture, hbmOld);
bmpCapture->pixels = (LPDWORD)lpCapture;
bmpCapture->width = nWidth;
bmpCapture->height = nHeight;
bResult = TRUE;
}
DeleteDC(hdcCapture);
DeleteDC(hdcScreen);
return bResult;
}
The handle to the bitmap, as well as the width and height are all easy enough to get but I'm unsure of how to get the pixels.