how to mirror a HBITMAP - c++

how to flip a HBITMAP horizontally?As an option, I thought to get an array of colors from BITMAP and write them to another BITMAP, but somehow it's too busy. Are there built-in functions or other options to do this?

You can use StretchBlt with a negative dimension as below:
HBITMAP FlipBitmapHorizontally(HBITMAP hbm) {
BITMAP bm;
GetObject(hbm, sizeof(BITMAP), &bm);
int wd = bm.bmWidth;
int hgt = bm.bmHeight;
HDC hdcScr = GetDC(NULL);
HDC hdcFlipped = CreateCompatibleDC(hdcScr);
HBITMAP hbmFlipped = CreateCompatibleBitmap(hdcScr, wd, hgt);
HGDIOBJ oldFlipped = SelectObject(hdcFlipped, hbmFlipped);
HDC hdcSrc = CreateCompatibleDC(hdcScr);
HGDIOBJ oldSrc = SelectObject(hdcSrc, hbm);
StretchBlt(hdcFlipped, wd, 0, -wd, hgt, hdcSrc, 0, 0, wd, hgt, SRCCOPY);
SelectObject(hdcSrc, oldSrc);
DeleteDC(hdcSrc);
SelectObject(hdcFlipped, oldFlipped);
DeleteDC(hdcFlipped);
ReleaseDC(NULL, hdcScr);
return hbmFlipped;
}

Related

Can I call the DC of the hidden child dialog?

There are three child dialogs attached to the bottom of the main dialog.
When dialog1 is in the SW_SHOW state and dialog2 is in SW_HIDE, I want to bring dialog2 DC, screen and capture it.
The entire screen is captured, but if the child dialog changes, the changed dialog is captured.
Any help would be appreciated.
MainTrendGraphDlg *pDlg = (MainTrendGraphDlg*)::AfxGetMainWnd();
// MainTrendGraphDlg is dialog2
CClientDC hdcSys(pDlg);
int x_size = ::GetDeviceCaps(hdcSys, HORZRES);
int y_size = ::GetDeviceCaps(hdcSys, VERTRES);
HBITMAP hBitmap; // <-- The image represented by hBitmap
// Initialize DCs
HDC hdcMem = CreateCompatibleDC(hdcSys); // Create compatible DC
void *ptrBitmapPixels; // <-- Pointer variable that will contain the potinter for the pixels
// Create hBitmap with Pointer to the pixels of the Bitmap
BITMAPINFO bi; HDC hdc;
ZeroMemory(&bi, sizeof(BITMAPINFO));
bi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bi.bmiHeader.biWidth = x_size;
bi.bmiHeader.biHeight = -y_size; //negative so (0,0) is at top left
bi.bmiHeader.biPlanes = 1;
bi.bmiHeader.biBitCount = 32;
hdc = GetDC(NULL);
hBitmap = CreateDIBSection(hdc, &bi, DIB_RGB_COLORS, &ptrBitmapPixels, NULL, 0);
// ^^ The output: hBitmap & ptrBitmapPixels
// Set hBitmap in the hdcMem
SelectObject(hdcMem, hBitmap);
// Set matBitmap to point to the pixels of the hBitmap
matDlgCap = Mat(y_size, x_size, CV_8UC4, ptrBitmapPixels, 0);
// ^^ note: first it is y, then it is x. very confusing
// Now update the pixels using BitBlt
BitBlt(hdcMem, 0, 0, x_size, y_size, hdcSys, 0, 0, SRCCOPY);
imwrite("ScreenShot.jpg", matDlgCap, JPEG_QUALITY);

Creating a new bitmap with given dimensions filled with given color

I'm creating a program using the Win32 API, and I need to create a new bitmap filled with one color and with given dimensions.
Here is my code:
m_hBitmap =( HBITMAP ) CreateCompatibleBitmap(hDC, iWidth, iHeight);
HDC hDCn = CreateCompatibleDC( hDC );
SelectObject( hDCn, m_hBitmap );
ExtFloodFill(hDCn, 0, 0, crColor, FLOODFILLSURFACE);
DeleteDC( hDCn );
The bitmap dimensions match, but the bitmap is always black regardless of the crColor parameter.
Use FillRect() instead of ExtFloodFill().
Also, you need to de-select the bitmap before you delete the DC, otherwise the original bitmap created and selected into the DC by CreateCompatibleDC() will be leaked.
Try this:
m_hBitmap = CreateCompatibleBitmap(hDC, iWidth, iHeight);
HDC hDCn = CreateCompatibleDC(hDC);
HBITMAP hOld = (HBITMAP) SelectObject(hDCn, m_hBitmap); // <-- SAVE OLD BITMAP!
//ExtFloodFill(hDCn, 0, 0, crColor, FLOODFILLSURFACE);
RECT r;
r.left = r.top = 0;
r.right = iWidth;
r.bottom = iHeight;
HBRUSH hBrush = CreateSolidBrush(crColor);
FillRect(hDCn, &r, hBrush);
DeleteObject(hBrush);
SelectObject(hDCn, hOld); // <-- RESTORE OLD BITMAP
DeleteDC(hDCn);

Off-screen drawing GDI+

I have a problem - I need to draw two png files, one on the other. When I do it usual way, there is a "blinking" effect (first image overdraws the second one for small time period). I use GDI+ library and my WM_PAINT handling looks like this:
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint( hwnd, & ps );
displayImage(firstImage, hwnd);
displayImage(secondImage, hwnd);
EndPaint( hwnd, & ps );
break;
}
displayImage function:
void displayImage(HBITMAP mBmp, HWND mHwnd)
{
RECT myRect;
BITMAP bm;
HDC screenDC, memDC;
HBITMAP oldBmp;
BLENDFUNCTION bf;
GetObject(mBmp, sizeof(bm), &bm);
bf.BlendOp = AC_SRC_OVER;
bf.BlendFlags = 0;
bf.SourceConstantAlpha = 0xff;
bf.AlphaFormat = AC_SRC_ALPHA;
screenDC = GetDC(mHwnd);
GetClientRect(mHwnd, &myRect);
if (mBmp == NULL)
FillRect(screenDC, &myRect, WHITE_BRUSH);
else
{
memDC = CreateCompatibleDC(screenDC);
oldBmp = (HBITMAP)SelectObject(memDC, mBmp);
AlphaBlend (screenDC, 0, 0, myRect.right,myRect.bottom, memDC, 0, 0, bm.bmWidth,bm.bmHeight, bf);
SelectObject(memDC, oldBmp);
DeleteDC(memDC);
ReleaseDC(mHwnd, screenDC);
}
}
Loading files to variables:
HBITMAP mLoadImg(WCHAR *szFilename)
{
HBITMAP result=NULL;
Gdiplus::Bitmap* bitmap = new Gdiplus::Bitmap(szFilename,false);
bitmap->GetHBITMAP(NULL, &result);
delete bitmap;
return result;
}
firstImage = mLoadImg(L"data\\img\\screen.png");
secondImage = mLoadImg(L"data\\img\\screen2.png");
I've heard that I should do a off-screen drawing. How should that look like?
You don't need all of that. You can use GDI+ directly:
static Gdiplus::Image *firstImage;
static Gdiplus::Image *secondImage;
case WM_CREATE: // or WM_INITDIALOG if it's dialog
{
firstImage = new Gdiplus::Image(L"data\\img\\screen.png");
secondImage = new Gdiplus::Image(L"data\\img\\screen2.png");
return 0;
}
case WM_PAINT:
{
PAINTSTRUCT ps = { 0 };
HDC hdc = BeginPaint(hwnd, &ps);
Gdiplus::Graphics gr(hdc);
gr.DrawImage(firstImage, 0, 0);
gr.DrawImage(secondImage, 0, 0);//<== this will draw transparently
EndPaint(hwnd, &ps);
return 0;
}
However, this code is still drawing 2 images back to back with possible flicker (like your original code). Use double-buffering in WM_PAINT so that only one BltBlt is done. Simply change to:
if (msg == WM_PAINT)
{
PAINTSTRUCT ps = { 0 };
HDC hdc = BeginPaint(hwnd, &ps);
RECT rc;
GetClientRect(hwnd, &rc);
HDC memdc = CreateCompatibleDC(hdc);
HBITMAP hbitmap = CreateCompatibleBitmap(hdc, rc.right, rc.bottom);
HGDIOBJ oldbmp = SelectObject(memdc, hbitmap);
FillRect(memdc, &rc, WHITE_BRUSH);
Gdiplus::Graphics gr(memdc);
gr.DrawImage(firstImage, 0, 0);
gr.DrawImage(secondImage, 0, 0);
BitBlt(hdc, 0, 0, rc.right, rc.bottom, memdc, 0, 0, SRCCOPY);
SelectObject(memdc, oldbmp);
DeleteObject(hbitmap);
DeleteDC(memdc);
EndPaint(hwnd, &ps);
return 0;
}
As for the original code:
void displayImage(HBITMAP mBmp, HWND mHwnd)
{
HDC hdc = GetDC(mHwnd);
...
}
You should change the function declaration to void displayImage(HBITMAP mBmp, HWND mHwnd, HDC hdc) then you can pass the hdc directly from WM_PAINT
First, change displayImage to take the HDC and RECT from the caller instead of the HWND.
void displayImage(HBITMAP mBmp, HDC hdc, const RECT &myRect)
{
if (mBmp == NULL)
FillRect(screenDC, &myRect, WHITE_BRUSH);
else
{
BITMAP bm;
GetObject(mBmp, sizeof(bm), &bm);
HDC memDC = CreateCompatibleDC(screenDC);
HBITMAP oldBmp = (HBITMAP)SelectObject(memDC, mBmp);
BLENDFUNCTION bf;
bf.BlendOp = AC_SRC_OVER;
bf.BlendFlags = 0;
bf.SourceConstantAlpha = 0xff;
bf.AlphaFormat = AC_SRC_ALPHA;
AlphaBlend(hdc, 0, 0, myRect.right, myRect.bottom, memDC, 0, 0, bm.bmWidth, bm.bmHeight, bf);
SelectObject(memDC, oldBmp);
DeleteDC(memDC);
}
}
Then, in the caller create a compatible DC and bitmap. These are your off-screen space for doing the compositing. Make the calls to displayImage with this new DC. This will compose the PNGs offscreen. Finally, blit the composed result to the actual window DC in one go.
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
RECT myRect;
GetClientRect(hwnd, &myRect);
// Create an off-screen DC for composing the images.
HDC hdcMem = CreateCompatibleDC(hdc);
HBITMAP hbmpMem = CreateCompatibleBitmap(hdc, myRect.right, myRect.bottom);
HBITMAP hbmpOld = (HBITMAP) SelectObject(hdcMem, hbmpMem);
// Compose the images to the offscreen bitmap.
displayImage(firstImage, hdcMem, myRect);
displayImage(secondImage, hdcMem, myRect);
// Blit the resulting composition to the window DC.
BitBlt(hdc, 0, 0, myRect.right, myRect.bottom,
hdcMem, 0, 0, SRCCOPY);
// Clean up the offscreen stuff.
SelectObject(hdcMem, hbmpOld);
DeleteObject(hbmpMem);
DeleteDC(hdcMem);
EndPaint(hwnd, &ps);
break;
}
Finally, if you're still seeing a flash of the background color, see Pavan Chandaka's answer.
Handle "WM_ERASEBKGND" message by your self.
Actually before loading the second image two things happen.
WM_ERASEBKGND is triggered first to fill the image area with, whatever current windows background color is.
WM_PAINT to render action.
Documentation says to avoid blink/Flickr, provide a default handler for "WM_ERASEBKGND".
Below is the link, go to "A Control That Doesn't Flicker". You have an example too.
https://msdn.microsoft.com/en-us/library/ms969905.aspx

SetDIBits fails on WM_PAINT when trying to draw

I am trying to draw plot using matrix, where each element is DWORD value, that represent pixel ARGB value. Idea behind the application is that one separate thread do the calculation (mat. formula), whose result will be the matrix with ARGB values. When WM_PAINT is raised I will use function to draw, using the filled matrix:
void DrawImage( HDC hDC, WORD wWidth, WORD wHeight )
{
HBITMAP hBitmap;
HDC hMemDC;
BITMAPINFO bi;
int iSize = sizeof( BITMAPINFO );
memcpy( &bi, dwBytes + 3, iSize);
hBitmap = CreateCompatibleBitmap(hDC, wWidth, wHeight);
hMemDC = CreateCompatibleDC( hDC );
if ( 0 == SetDIBits( hDC, hBitmap, 0,
wHeight, dwBytes, &bi, DIB_RGB_COLORS ) )
{
// error MSDN http://msdn.microsoft.com/en-us/library/
//windows/desktop/dd162973%28v=vs.85%29.aspx
}
hBitmap = (HBITMAP) SelectObject(hMemDC, hBitmap);
BitBlt(hDC, 0, 0, wWidth, wHeight, hMemDC, 0, 0, SRCCOPY);
DeleteObject(SelectObject(hMemDC, hBitmap));
DeleteDC(hMemDC);
}
so, when WM_PAINT is raised:
case WM_PAINT:
{
PAINTSTRUCT ps = { 0 };
HDC hDC = BeginPaint( hWnd, &ps );
DrawImage( hDC, iWidth, iHeight );
EndPaint( hWnd, &ps );
return 0L;
}
but nothing happens. The area is black. When SetDIBits returns 0 it means that "One or more of the input parameters is invalid" according to MSDN. I am out of ideas..
sizeof( BITMAPINFO ) includes only 1 pixel. BITMAPINFO is a variable-length structure so you need to compute its size dynamically and allocate sufficient memory dynamically.
You pointed me to investigate handling BITMAPINFO structure, and there it was. After I have changed one plane, 32 bits per pixel and width 800 and height 600 - it worked!
void DrawImage( HDC hDC, WORD wWidth, WORD wHeight )
{
HDC hMemDC;
BITMAPINFO bmi;
ZeroMemory(&bmi, sizeof(BITMAPINFO));
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bmi.bmiHeader.biBitCount = 32;
bmi.bmiHeader.biHeight = 600;
bmi.bmiHeader.biWidth = 800;
bmi.bmiHeader.biPlanes = 1;
hMemDC = CreateCompatibleDC( hDC );
HBITMAP hBitmap = CreateDIBSection( hMemDC, &bmi, DIB_RGB_COLORS,
(void**) &dwBytes, NULL, 0);
SetDIBits( hDC, hBitmap, 0, wHeight, dwBytes, &bmi, DIB_RGB_COLORS );
HBITMAP hOldBitmap = (HBITMAP) SelectObject( hMemDC, hBitmap );
BitBlt(hDC, 0, 0, wWidth, wHeight, hMemDC, 0, 0, SRCCOPY);
SelectObject( hMemDC, hBitmap );
DeleteObject( hBitmap );
DeleteDC(hMemDC);
}
I got proper result:

How can I take a screenshot in a windows application?

How can I take a screenshot of the current screen using Win32?
HDC hScreenDC = GetDC(nullptr); // CreateDC("DISPLAY",nullptr,nullptr,nullptr);
HDC hMemoryDC = CreateCompatibleDC(hScreenDC);
int width = GetDeviceCaps(hScreenDC,HORZRES);
int height = GetDeviceCaps(hScreenDC,VERTRES);
HBITMAP hBitmap = CreateCompatibleBitmap(hScreenDC,width,height);
HBITMAP hOldBitmap = static_cast<HBITMAP>(SelectObject(hMemoryDC,hBitmap));
BitBlt(hMemoryDC,0,0,width,height,hScreenDC,0,0,SRCCOPY);
hBitmap = static_cast<HBITMAP>(SelectObject(hMemoryDC,hOldBitmap));
DeleteDC(hMemoryDC);
DeleteDC(hScreenDC);
Use GetDC(NULL); to get a DC for the entire screen.
Use CreateCompatibleDC to create a DC compatible with the screen DC.
Use CreateCompatibleBitmap to create a bitmap compatible with the screen DC to hold the result.
Use SelectObject to select the compatible bitmap into the compatible DC.
Use BitBlt to copy from the screen DC to the compatible DC.
Use SelectObject to deselect the compatible bitmap from the compatible DC.
Use DeleteDC to delete the compatible DC.
When you create the compatible bitmap, you want it compatible with the screen DC, not the compatible DC.
For example:
HDC dcScreen = GetDC(0);
HDC dcTarget = CreateCompatibleDC(dcScreen);
HBITMAP bmpTarget = CreateCompatibleBitmap(dcScreen);
HGDIOBJ oldBmp = SelectObject(dcTarget, bmpTarget);
BitBlt(dcTarget, 0, 0, cx, cy, dcDesktop, x, y, SRCCOPY | CAPTUREBLT);
SelectObject(dcTarget, oldBmp);
DeleteDC(dcTarget);
ReleaseDC(dcScreen);
The other important part is to get the size, and location, of the entire virtual screen:
int x = GetSystemMetrics(SM_XVIRTUALSCREEN); //left (e.g. -1024)
int y = GetSystemMetrics(SM_YVIRTUALSCREEN); //top (e.g. -34)
int cx = GetSystemMetrics(SM_CXVIRTUALSCREEN); //entire width (e.g. 2704)
int cy = GetSystemMetrics(SM_CYVIRTUALSCREEN); //entire height (e.g. 1050)
void GetScreenShot(void)
{
int x1, y1, x2, y2, w, h;
// get screen dimensions
x1 = GetSystemMetrics(SM_XVIRTUALSCREEN);
y1 = GetSystemMetrics(SM_YVIRTUALSCREEN);
x2 = GetSystemMetrics(SM_CXVIRTUALSCREEN);
y2 = GetSystemMetrics(SM_CYVIRTUALSCREEN);
w = x2 - x1;
h = y2 - y1;
// copy screen to bitmap
HDC hScreen = GetDC(NULL);
HDC hDC = CreateCompatibleDC(hScreen);
HBITMAP hBitmap = CreateCompatibleBitmap(hScreen, w, h);
HGDIOBJ old_obj = SelectObject(hDC, hBitmap);
BOOL bRet = BitBlt(hDC, 0, 0, w, h, hScreen, x1, y1, SRCCOPY);
// save bitmap to clipboard
OpenClipboard(NULL);
EmptyClipboard();
SetClipboardData(CF_BITMAP, hBitmap);
CloseClipboard();
// clean up
SelectObject(hDC, old_obj);
DeleteDC(hDC);
ReleaseDC(NULL, hScreen);
DeleteObject(hBitmap);
}
Full code for saving a raw 24-bit lossless bitmap of all monitors at the current window station using Windows API:
BOOL WINAPI SaveBitmap(WCHAR *wPath)
{
BITMAPFILEHEADER bfHeader;
BITMAPINFOHEADER biHeader;
BITMAPINFO bInfo;
HGDIOBJ hTempBitmap;
HBITMAP hBitmap;
BITMAP bAllDesktops;
HDC hDC, hMemDC;
LONG lWidth, lHeight;
BYTE *bBits = NULL;
HANDLE hHeap = GetProcessHeap();
DWORD cbBits, dwWritten = 0;
HANDLE hFile;
INT x = GetSystemMetrics(SM_XVIRTUALSCREEN);
INT y = GetSystemMetrics(SM_YVIRTUALSCREEN);
ZeroMemory(&bfHeader, sizeof(BITMAPFILEHEADER));
ZeroMemory(&biHeader, sizeof(BITMAPINFOHEADER));
ZeroMemory(&bInfo, sizeof(BITMAPINFO));
ZeroMemory(&bAllDesktops, sizeof(BITMAP));
hDC = GetDC(NULL);
hTempBitmap = GetCurrentObject(hDC, OBJ_BITMAP);
GetObjectW(hTempBitmap, sizeof(BITMAP), &bAllDesktops);
lWidth = bAllDesktops.bmWidth;
lHeight = bAllDesktops.bmHeight;
DeleteObject(hTempBitmap);
bfHeader.bfType = (WORD)('B' | ('M' << 8));
bfHeader.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
biHeader.biSize = sizeof(BITMAPINFOHEADER);
biHeader.biBitCount = 24;
biHeader.biCompression = BI_RGB;
biHeader.biPlanes = 1;
biHeader.biWidth = lWidth;
biHeader.biHeight = lHeight;
bInfo.bmiHeader = biHeader;
cbBits = (((24 * lWidth + 31)&~31) / 8) * lHeight;
hMemDC = CreateCompatibleDC(hDC);
hBitmap = CreateDIBSection(hDC, &bInfo, DIB_RGB_COLORS, (VOID **)&bBits, NULL, 0);
SelectObject(hMemDC, hBitmap);
BitBlt(hMemDC, 0, 0, lWidth, lHeight, hDC, x, y, SRCCOPY);
hFile = CreateFileW(wPath, GENERIC_WRITE | GENERIC_READ, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
WriteFile(hFile, &bfHeader, sizeof(BITMAPFILEHEADER), &dwWritten, NULL);
WriteFile(hFile, &biHeader, sizeof(BITMAPINFOHEADER), &dwWritten, NULL);
WriteFile(hFile, bBits, cbBits, &dwWritten, NULL);
CloseHandle(hFile);
DeleteDC(hMemDC);
ReleaseDC(NULL, hDC);
DeleteObject(hBitmap);
return TRUE;
}
There is a MSDN sample, Capturing an Image, for capturing an arbitrary HWND to a DC (you could try passing the output from GetDesktopWindow to this). But how well this will work under the new desktop compositor on Vista/Windows 7, I don't know.