Capturing a specific window using BitBlt return the black or transparent border which represent the classic gui layer - c++

I tried to capture a screenshot of specific window using BitBlt and save it to clipboard, but it returns the screenshot with the content inside window border only while the window border itself is remain transparent or blacked out with many weird behaviors.
The expected result should be the full window border with the full content:
This is the actual result:
This is what I tried:
Comment the SetLayeredWindowAttributes function before performing BitBlt but the result is the entire black screenshot.
Tried to set the delay after UpdateWindow function but the weird border remains the same.
Disabling some coded option but it didn't help...
Use PrintWindow but the result is the classic gui border, which is not the intended goal (and many article indicate about the problem with this function).
I don't have any idea why this happened nor able to find out due to lack of information of this command when searching Internet.
Is there anything wrong with my code?
void QuickScreenshot()
{
HWND windowHandle(::FindWindowW(0, L"Internet Download Manager 6.41"));
ANIMATIONINFO AnimationInfo{};
SystemParametersInfoW(SPI_GETANIMATION, sizeof(ANIMATIONINFO), &AnimationInfo, NULL);
auto DefaultAnimationInfo(AnimationInfo);
AnimationInfo.iMinAnimate = 0;
DefaultAnimationInfo.cbSize = sizeof(ANIMATIONINFO);
SystemParametersInfoW(SPI_SETANIMATION, sizeof(ANIMATIONINFO), &AnimationInfo, WM_SETTINGCHANGE);
auto originalExstyle(GetWindowLongW(windowHandle, GWL_EXSTYLE));
SetWindowLongW(windowHandle, GWL_EXSTYLE, WS_EX_LAYERED);
COLORREF windowColor;
BYTE windowAlpha, customAlpha(1);
DWORD flag;
WINDOWPLACEMENT windowState{};
windowState.length = sizeof(WINDOWPLACEMENT);
GetLayeredWindowAttributes(windowHandle, &windowColor, &windowAlpha, &flag);
SetLayeredWindowAttributes(windowHandle, windowColor, customAlpha, flag);
GetWindowPlacement(windowHandle, &windowState);
if (windowState.showCmd == SW_SHOWMINIMIZED)
{
if ((windowState.flags & WPF_RESTORETOMAXIMIZED) == WPF_RESTORETOMAXIMIZED)
{
ShowWindow(windowHandle, SW_MAXIMIZE);
}
else
{
ShowWindow(windowHandle, SW_SHOWNORMAL);
}
}
else
{
ShowWindow(windowHandle, windowState.showCmd);
}
RECT windowRectangleCoordinates;
GetWindowRect(windowHandle, &windowRectangleCoordinates);
InvalidateRect(windowHandle, &windowRectangleCoordinates, TRUE);
UpdateWindow(windowHandle);
Sleep(100);
HDC ScreenContentHandle(GetWindowDC(windowHandle));
HDC DestinationContentHandle(CreateCompatibleDC(ScreenContentHandle));
auto cx(windowRectangleCoordinates.right - windowRectangleCoordinates.left);
auto cy(windowRectangleCoordinates.bottom - windowRectangleCoordinates.top);
HBITMAP BitmapHandle(CreateCompatibleBitmap(ScreenContentHandle, cx, cy));
HGDIOBJ oldObject(SelectObject(DestinationContentHandle, BitmapHandle));
//PrintWindow(windowHandle, ScreenContentHandle, NULL);
BitBlt
(
DestinationContentHandle,
0, 0, cx, cy,
ScreenContentHandle,
0, 0,
SRCCOPY | CAPTUREBLT
);
SaveToClipboard(BitmapHandle);
SelectObject(DestinationContentHandle, oldObject);
DeleteDC(DestinationContentHandle));
ReleaseDC(NULL, ScreenContentHandle));
DeleteObject(BitmapHandle));
ShowWindow(windowHandle, SW_MINIMIZE);
SetLayeredWindowAttributes(windowHandle, windowColor, windowAlpha, flag);
SetWindowLongW(windowHandle, GWL_EXSTYLE, originalExstyle);
SystemParametersInfoW(SPI_SETANIMATION, sizeof(ANIMATIONINFO), &DefaultAnimationInfo, WM_SETTINGCHANGE);
}

Related

Capture Screenshot from another desktop

I have created a second desktop using CreateDesktop and im not switching to it. Also i have created some processes in it like Explorer.exe and Winrar.exe. Next i have a code which takes Screenshot of current desktop to clipboard. Both CreateDesktop and Screenshot works, But Screenshot of that new desktop or window returns a black bitmap:
This is the screenshot for a window in a desktop which returns current desktop:
// hwnd is handle to winrar or ... created in a new desktop retrieved by EnumDesktopWindow
RECT rc;
GetClientRect(hwnd, &rc);
const HDC hScreenDC = GetDC(nullptr);
const HDC hMemoryDC = CreateCompatibleDC(hScreenDC);
const int width = GetDeviceCaps(hScreenDC, HORZRES);
const int height = GetDeviceCaps(hScreenDC, VERTRES);
const HBITMAP hBitmap = CreateCompatibleBitmap(hScreenDC, width, height);
HBITMAP(SelectObject(hMemoryDC, hBitmap));
BitBlt(hMemoryDC, 0, 0, width, height, hScreenDC, 0, 0, SRCCOPY);
OpenClipboard(nullptr);
EmptyClipboard();
SetClipboardData(CF_BITMAP, hBitmap);
CloseClipboard();
DeleteDC(hMemoryDC);
DeleteDC(hScreenDC);
I have implemented both this methods in c# but same thing happens there.
There are great resources like:
Capture screenshot of hidden desktop
take a screenshot of a desktop created using createdesktop api
C# – SCREEN CAPTURE WITH VISTA DWM (SHARED DIRECT3D SURFACE)
Window Contents Capturing using WM_PRINT Message
how to capture screen from another desktop?(CreateDesktop)
Also this is like a dead topic, No new article, Explanation or solution to it.
I have read most of them but no luck, This was my closest try i think. Also language doesnt matter for me: C#, C++, Python or ... .
I found the solution, It is interesting but no perfect, Just resolves my needs.
After CreateDesktop by calling OpenDesktop then SetThreadDesktop then using the screenshot code you get the screenshot of the window which is created inside CreateDesktop, Also no need for Creating Explorer.exe inside it if you just want the window:
CreateDesktopW(L"NewDesktop"); // CreateDesktop code here. This is my function
const HDESK Handle = OpenDesktopW(L"NewDesktop", 0, 0, GENERIC_ALL);
SetThreadDesktop(Handle);
// Above ScreenShot code here ...
The screenshot code needs a PrintWindow:
RECT rc;
GetClientRect(hwnd, &rc);
const HDC hScreenDC = GetDC(nullptr);
const HDC hMemoryDC = CreateCompatibleDC(hScreenDC);
const int width = GetDeviceCaps(hScreenDC, HORZRES);
const int height = GetDeviceCaps(hScreenDC, VERTRES);
const HBITMAP hBitmap = CreateCompatibleBitmap(hScreenDC, width, height);
HBITMAP(SelectObject(hMemoryDC, hBitmap));
BitBlt(hMemoryDC, 0, 0, width, height, hScreenDC, 0, 0, SRCCOPY);
/// ADDED CODE
PrintWindow(hWnd, hMemoryDC, PW_CLIENTONLY);
///
OpenClipboard(nullptr);
EmptyClipboard();
SetClipboardData(CF_BITMAP, hBitmap);
CloseClipboard();
DeleteDC(hMemoryDC);
DeleteDC(hScreenDC);
Mine worked with a winrar.exe window inside a inactive desktop. You can try this then paste it to paint to see the result.
There is just one thing, The whole area of the screenshot bitmap is black except the window handle that i want which is fine by me. I think i should get handle of every window from bottom to top in order then mix them up.
All additions to this are appreciated.

How to draw a opaque image on the Aero glass window?

I have used DWM API to create a Aero glass window by calling DwmExtendFrameIntoClientArea.
void CMainFrame::OnActivate(UINT nState,CWnd* pWndOther,BOOL bMinimized )
{
CFrameWnd::OnActivate(nState,pWndOther,bMinimized);
BOOL fDwmEnabled = FALSE;
if (SUCCEEDED(DwmIsCompositionEnabled(&fDwmEnabled)))
{
if(nState == WA_ACTIVE )
{
MARGINS margins ={-1};
HRESULT hr = DwmExtendFrameIntoClientArea(m_hWnd, &margins);
if (!SUCCEEDED(hr))
TRACE0("Failed to DwmExtendFrameIntoClientArea\n");
}
}
}
And then, I draw a bitmap image on the window (I also have tried to call DrawThemeIcon and CImageList::Draw to draw the image).
void CMainFrame::DisplayBitmap( CBitmap *p, CDC *pDC)
{
CDC dcMemory;
BITMAP bm;
dcMemory.CreateCompatibleDC(pDC);
dcMemory.SelectObject(p);
p->GetBitmap(&bm);
pDC->BitBlt(100,100,bm.bmWidth,bm.bmHeight,&dcMemory,0,0,SRCCOPY);
}
void CMainFrame::OnNcPaint(){
CFrameWnd::OnNcPaint();
CDC* dc = GetWindowDC();
CRect rct;
GetWindowRect(&rct);
dc->FillSolidRect(0, 0, rct.right - rct.left, rct.bottom - rct.top, RGB(0, 0, 0));
DisplayBitmap(&bmpBtn,dc);
ReleaseDC(dc);
}
I found out the image is ugly and translucency. How to draw a opaque image on the Aero glass window?
Update:
I still need somebody who can provide another solution without using GDI+ library for me. Thanks!
Solution 1:
CMainFrame::CMainFrame()
{
GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken;
//Initialize GDI+.
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
m_pImage = m_pImage->FromFile (_T("lena.bmp"));
}
void CMainFrame::OnNcPaint(){
CFrameWnd::OnNcPaint();
CDC* dc = GetWindowDC();
CRect rct;
GetWindowRect(&rct);
dc->FillSolidRect(0, 0, rct.right - rct.left, rct.bottom - rct.top, RGB(0, 0, 0));
CPaintDC gdc(this);
Graphics g(gdc);
//I don't why the image will disappear sometimes when I move the window.
g.DrawImage ( m_pImage, 0, 0 );
ReleaseDC(dc);
}
The Result:

how to stretch a background image in win32 using visual studio c

I'm trying to create an application in Win32 api with c++ and I want to make it FullScreen without any bar , i succeeded but i still have a problem in the background image. The image is repeated but i want it to be stretched. Have you any idea?
below part from the code :
int WINAPI WinMain (HINSTANCE cetteInstance, HINSTANCE precedenteInstance,
LPSTR lignesDeCommande, int modeDAffichage)
{
HWND fenetrePrincipale;
MSG message;
WNDCLASS classeFenetre;
instance = cetteInstance;
classeFenetre.style = 0;
classeFenetre.lpfnWndProc = procedureFenetrePrincipale;
classeFenetre.cbClsExtra = 0;
classeFenetre.cbWndExtra = 0;
classeFenetre.hInstance = NULL;
classeFenetre.hIcon = LoadIcon(NULL, MAKEINTRESOURCE(IDI_APPLICATION));
classeFenetre.hCursor = LoadCursor(NULL, IDC_ARROW);
// classeFenetre.hbrBackground = (HBRUSH)(1 + COLOR_BTNFACE);
//classeFenetre.hbrBackground = CreatePatternBrush(LoadBitmap( instance, MAKEINTRESOURCE("images\Image1.bmp" ) ) );
HBITMAP hbmp = LoadBitmap(instance,MAKEINTRESOURCE(IDB_BITMAP1));
if(NULL == hbmp)
{
MessageBox(NULL,L"BitMap Loading Failed.",L"Error",MB_ICONEXCLAMATION | MB_OK);
}
else
{
HBRUSH hbr = CreatePatternBrush(hbmp);
if(NULL == hbr)
{
MessageBox(NULL,L"Brush Creation Failed.",L"Error",MB_ICONEXCLAMATION | MB_OK);
}
else
{
//StretchBlt();
HDC hdcMem = GetDC (NULL) ;
HDC wndHDC = GetDC (fenetrePrincipale) ;
StretchBlt(hdcMem, 0, 0, 800, 600, wndHDC, 0, 0, 1280, 1024, SRCCOPY);
classeFenetre.hbrBackground = hbr ;
}
}
classeFenetre.lpszMenuName = NULL;
classeFenetre.lpszClassName = L"classeF";
//fullscreen mode and delete minimize and max buttons
// On prévoit quand même le cas où ça échoue
if(!RegisterClass(&classeFenetre)) return FALSE;
//WS_OVERLAPPEDWINDOW
fenetrePrincipale = CreateWindow(L"classeF", L"Ma premiere fenetre winAPI !",WS_MAXIMIZE|WS_POPUP ,
CW_USEDEFAULT, CW_USEDEFAULT, 800, 630,
NULL,
NULL,//LoadMenu(instance, L"ID_MENU"),
cetteInstance,
NULL);
if (!fenetrePrincipale) return FALSE;
//ShowWindow(fenetrePrincipale, modeDAffichage);
ShowWindow(fenetrePrincipale,SW_MAXIMIZE);
UpdateWindow(fenetrePrincipale);
while (GetMessage(&message, NULL, 0, 0))
{
TranslateMessage(&message);
DispatchMessage(&message);
}
return message.wParam;
}
thanks
You haven't shown the exact code, but it appears that you load a bitmap, create a brush from it, and then set that brush as the brush for your window. Brushes would indeed lead to the repeating-image behavior you report. To get a stretched bitmap, you may skip any brush-related code. Instead, handle the WM_ERASEBKGND message sent to your window. In it, call StretchBlt to paint your bitmap onto the client area of your window. The HDC to paint to is given in the message's wParam argument.
Steps
1, CreateWindowEx to create the window
2, SetWindowPos to place your window on top of all windows and Fullscreen
3, On your windows's WindowProce handle WM_PAINT message
4, Load your bitmap
5, Create a memory dc using CreateCompatibleDC
6, Selet your bitmap into memory dc by calling SelectObject
7, Do the StretchBlt to your actual dc, using the prepared memory dc as the source, you should know the actual width and height of the bitmap for proper stretching

Creating a transparent window in C++ Win32

I'm creating what should be a very simple Win32 C++ app whose sole purpose it to ONLY display a semi-transparent PNG. The window shouldn't have any chrome, and all the opacity should be controlled in the PNG itself.
My problem is that the window doesn't repaint when the content under the window changes, so the transparent areas of the PNG are "stuck" with what was under the window when the application was initially started.
Here's the line where I setup the new window:
hWnd = CreateWindowEx(WS_EX_TOPMOST, szWindowClass, szTitle, WS_POPUP, 0, height/2 - 20, 40, 102, NULL, NULL, hInstance, 0);
For the call to RegisterClassEx, I have this set for the background:
wcex.hbrBackground = (HBRUSH)0;
Here is my handler for WM_PAINT message:
case WM_PAINT:
{
hdc = BeginPaint(hWnd, &ps);
Gdiplus::Graphics graphics(hdc);
graphics.DrawImage(*m_pBitmap, 0, 0);
EndPaint(hWnd, &ps);
break;
}
One thing to note is that the application is always docked to the left of the screen and doesn't move. But, what's underneath the application may change as the user opens, closes or moves windows under it.
When the application first starts, it looks perfect. The transparent (and simi-transparent) parts of the PNG show through perfectly. BUT, when the background underneath the application changes, the background DOESN'T update, it just stays the same from when the application first started. In fact, WM_PAINT (or WM_ERASEBKGND does not get called when the background changes).
I've been playing with this for quite a while and have gotten close to getting 100% right, but not quite there. For instance, I've tried setting the background to (HBRUSH) NULL_BRUSH and I've tried handling WM_ERASEBKGND.
What can be done to get the window to repaint when the contents under it changes?
I was able to do exactly what I wanted by using the code from Part 1 and Part 2 of this series:
Displaying a Splash Screen with C++
Part 1: Creating a HBITMAP archive
Part 2: Displaying the window archive
Those blog posts are talking about displaying a splash screen in Win32 C++, but it was almost identical to what I needed to do. I believe the part that I was missing was that instead of just painting the PNG to the window using GDI+, I needed to use the UpdateLayeredWindow function with the proper BLENDFUNCTION parameter. I'll paste the SetSplashImage method below, which can be found in Part 2 in the link above:
void SetSplashImage(HWND hwndSplash, HBITMAP hbmpSplash)
{
// get the size of the bitmap
BITMAP bm;
GetObject(hbmpSplash, sizeof(bm), &bm);
SIZE sizeSplash = { bm.bmWidth, bm.bmHeight };
// get the primary monitor's info
POINT ptZero = { 0 };
HMONITOR hmonPrimary = MonitorFromPoint(ptZero, MONITOR_DEFAULTTOPRIMARY);
MONITORINFO monitorinfo = { 0 };
monitorinfo.cbSize = sizeof(monitorinfo);
GetMonitorInfo(hmonPrimary, &monitorinfo);
// center the splash screen in the middle of the primary work area
const RECT & rcWork = monitorinfo.rcWork;
POINT ptOrigin;
ptOrigin.x = 0;
ptOrigin.y = rcWork.top + (rcWork.bottom - rcWork.top - sizeSplash.cy) / 2;
// create a memory DC holding the splash bitmap
HDC hdcScreen = GetDC(NULL);
HDC hdcMem = CreateCompatibleDC(hdcScreen);
HBITMAP hbmpOld = (HBITMAP) SelectObject(hdcMem, hbmpSplash);
// use the source image's alpha channel for blending
BLENDFUNCTION blend = { 0 };
blend.BlendOp = AC_SRC_OVER;
blend.SourceConstantAlpha = 255;
blend.AlphaFormat = AC_SRC_ALPHA;
// paint the window (in the right location) with the alpha-blended bitmap
UpdateLayeredWindow(hwndSplash, hdcScreen, &ptOrigin, &sizeSplash,
hdcMem, &ptZero, RGB(0, 0, 0), &blend, ULW_ALPHA);
// delete temporary objects
SelectObject(hdcMem, hbmpOld);
DeleteDC(hdcMem);
ReleaseDC(NULL, hdcScreen);
}
Use the SetLayeredWindowAttributesarchive function, this allows you to set a mask color that will become transparent, thus allowing the background to show through.
You will also need to configure your window with the layered flag, e.g.:
SetWindowLong(hwnd, GWL_EXSTYLE, GetWindowLong(hwnd, GWL_EXSTYLE) | WS_EX_LAYERED);
After that it's fairly simple:
// Make red pixels transparent:
SetLayeredWindowAttributes(hwnd, RGB(255,0,0), 0, LWA_COLORKEY);
When your PNG contains semi-transparent pixels that you want to blend with the background, this becomes more complicated. You could try looking at the approach in this CodeProject article:
Cool, Semi-transparent and Shaped Dialogs with Standard Controls for Windows 2000 and Above

BitBlt ignores CAPTUREBLT and seems to always capture a cached copy of the target

I am trying to capture screenshots using the BitBlt function. However, every single time I capture a screenshot, the non-client area NEVER changes no matter what I do. It's as if it's getting some cached copy of it. The client area is captured correctly.
If I close and then re-open the window, and take a screenshot, the non-client area will be captured as it is. Any subsequent captures after moving/resizing the window have no effect on the captured screenshot. Again, the client area will be correct.
Furthermore, the CAPTUREBLT flag seems to do absolutely nothing at all. I notice no change with or without it. Here is my capture code:
QPixmap WindowManagerUtils::grabWindow(WId windowId, GrabWindowFlags flags, int x, int y, int w, int h)
{
RECT r;
switch (flags)
{
case WindowManagerUtils::GrabWindowRect:
GetWindowRect(windowId, &r);
break;
case WindowManagerUtils::GrabClientRect:
GetClientRect(windowId, &r);
break;
case WindowManagerUtils::GrabScreenWindow:
GetWindowRect(windowId, &r);
return QPixmap::grabWindow(QApplication::desktop()->winId(), r.left, r.top, r.right - r.left, r.bottom - r.top);
case WindowManagerUtils::GrabScreenClient:
GetClientRect(windowId, &r);
return QPixmap::grabWindow(QApplication::desktop()->winId(), r.left, r.top, r.right - r.left, r.bottom - r.top);
default:
return QPixmap();
}
if (w < 0)
{
w = r.right - r.left;
}
if (h < 0)
{
h = r.bottom - r.top;
}
#ifdef Q_WS_WINCE_WM
if (qt_wince_is_pocket_pc())
{
QWidget *widget = QWidget::find(winId);
if (qobject_cast<QDesktopWidget*>(widget))
{
RECT rect = {0,0,0,0};
AdjustWindowRectEx(&rect, WS_BORDER | WS_CAPTION, FALSE, 0);
int magicNumber = qt_wince_is_high_dpi() ? 4 : 2;
y += rect.top - magicNumber;
}
}
#endif
// Before we start creating objects, let's make CERTAIN of the following so we don't have a mess
Q_ASSERT(flags == WindowManagerUtils::GrabWindowRect || flags == WindowManagerUtils::GrabClientRect);
// Create and setup bitmap
HDC display_dc = NULL;
if (flags == WindowManagerUtils::GrabWindowRect)
{
display_dc = GetWindowDC(NULL);
}
else if (flags == WindowManagerUtils::GrabClientRect)
{
display_dc = GetDC(NULL);
}
HDC bitmap_dc = CreateCompatibleDC(display_dc);
HBITMAP bitmap = CreateCompatibleBitmap(display_dc, w, h);
HGDIOBJ null_bitmap = SelectObject(bitmap_dc, bitmap);
// copy data
HDC window_dc = NULL;
if (flags == WindowManagerUtils::GrabWindowRect)
{
window_dc = GetWindowDC(windowId);
}
else if (flags == WindowManagerUtils::GrabClientRect)
{
window_dc = GetDC(windowId);
}
DWORD ropFlags = SRCCOPY;
#ifndef Q_WS_WINCE
ropFlags = ropFlags | CAPTUREBLT;
#endif
BitBlt(bitmap_dc, 0, 0, w, h, window_dc, x, y, ropFlags);
// clean up all but bitmap
ReleaseDC(windowId, window_dc);
SelectObject(bitmap_dc, null_bitmap);
DeleteDC(bitmap_dc);
QPixmap pixmap = QPixmap::fromWinHBITMAP(bitmap);
DeleteObject(bitmap);
ReleaseDC(NULL, display_dc);
return pixmap;
}
Most of this code comes from Qt's QWidget::grabWindow function, as I wanted to make some changes so it'd be more flexible. Qt's documentation states that:
The grabWindow() function grabs pixels
from the screen, not from the window,
i.e. if there is another window
partially or entirely over the one you
grab, you get pixels from the
overlying window, too.
However, I experience the exact opposite... regardless of the CAPTUREBLT flag. I've tried everything I can think of... nothing works. Any ideas?
Your confusion about BitBlt with CAPTUREBLT behaviour comes from the fact that official BitBlt documentation is unclear and misleading.
It states that
"CAPTUREBLT -- Includes any windows that are layered on top of your window in the resulting image. By default, the image only contains your window."
What actually means (at least for any windows OS without Aero enabled)
"CAPTUREBLT -- Includes any layered(!) windows (see WS_EX_LAYERED extended window style) that overlap your window. Non-layered windows that overlap your window is never included."
Windows without WS_EX_LAYERED extended window style that overlap your window is not included with or without CAPTUREBLT flag (at least for any windows OS without Aero enabled).
QT developers also misunderstood BitBlt/CAPTUREBLT documentation so QT documentation is actually wrong about QPixmap::grabWindow behaviour on WIN32 platform without Aero enabled.
ADD:
If you want to capture your window as it is on the screen you have to capture the entire desktop with CAPTUREBLT flag and then extract the rectangle with your window. (QT developers should do the same thing). It will work correctly in both cases: with and without Aero enabled/available.
I capture all screen and obtains the same results... :(
const uint SRCCOPY = 0x00CC0020; //SRCCOPY
const uint CAPTUREBLT = 0x00CC0020 | 0x40000000; //CAPTUREBLT
bool dv = BitBlt(hCaptureDC, 0, 0, Bounds.Width, Bounds.Height,
hDesktopDC, Bounds.Left, Bounds.Top, _with_tooltips ? CAPTUREBLT : SRCCOPY);