I have a piece of code here which takes a "screenshot" of the display with HDC (with a high resolution aware program):
HDC hdc = GetDC(NULL);
HDC hDest = CreateCompatibleDC(hdc);
int width = GetSystemMetrics(SM_CXSCREEN);
int height = GetSystemMetrics(SM_CYSCREEN);
HBITMAP hbDesktop = CreateCompatibleBitmap( hdc, width, height);
SelectObject(hDest, hbDesktop);
BitBlt(hDest, 0,0, width, height, hdc, 0, 0, SRCCOPY);
Problem is, on higher resolutions displays such as mine (2736x1824), the bitmap image is very large coming in at around 14MB; and I certainly do not need that high of a resolution and would like to downscale it to a more reasonable size of around 1MB if possible as I want to send it over a TCP connection. I am very new to HDC so cut me some slack. Thanks!
You could try to use StretchBlt to resize the bitmap.
Refer to the Doc:Scaling an Image
Related
I have this code
// get the device context of the screen
HDC hScreenDC = GetDC(NULL);
// and a device context to put it in
HDC hMemoryDC = CreateCompatibleDC(hScreenDC);
int width = GetSystemMetrics(SM_CXVIRTUALSCREEN);
int height = GetSystemMetrics(SM_CYVIRTUALSCREEN);
HBITMAP hBitmap = CreateCompatibleBitmap(hScreenDC, width, height);
// get a new bitmap
HBITMAP hOldBitmap = (HBITMAP)SelectObject(hMemoryDC, hBitmap);
BitBlt(hMemoryDC, 0, 0, width, height, hScreenDC, 0, 0, SRCCOPY);
hBitmap = (HBITMAP)SelectObject(hMemoryDC, hOldBitmap);
// clean up
DeleteDC(hMemoryDC);
DeleteDC(hScreenDC);
that generates an HBITMAP of the screen. It works perfectly most of the time, but if I try to run it when I have something on full screen on my browser (Youtube, for example), the image captured is not of the video, rather other program running on the background (Visual Studio for me). I suspect the problem is on getting the device context, but I've tried a few alternatives and had the same problems. How can I solve this?
Use DirectX method from this article: https://www.codeproject.com/Articles/5051/Various-methods-for-capturing-the-screen
After calling GetFrontBufferData you can simply save your bitmap to disk or dig out actual image data and use SetDIBits to assign it to HBITMAP.
1. Problem
I have two buffers. Primary buffer, which is displayed on the screen and a secondary buffer where everything is drawn and then passed to the primary.
The Graphics object is created from the secondary buffer, which is associated with a bitmap of size 800x600. Naturally when you resize the window, the size of the bitmap has to change in order to prevent clipping issues.
The secondary HDC gets updated, and the bitmap is copied to the primary.
The issue is that there is something left in the Graphics object associated with secondary HDC that generates clipping. The drawing region still stays 800x600 despite already being updated to something larger (1000x1000).
My question is what should I update inside the Graphics object (Without having to explicitly recreate it from the existing HDC) in order to make it's drawing region fit the bitmap size.
2. What I tried
The first thing I tried was recreating the Graphics object from the updated HDC. This method works and the drawing region fits the size of the bitmap. It does not meet the design standard however. Graphics should be reusable.
I also tried updating the clipping region of the graphics object using the SetClip method. Although that did not seem to be the problem.
This is how I create the buffers.
HDC CoreWindowFrame::InitPaint(HWND hWnd)
{
windowHdc = GetDC(hWnd);
HDC secondaryBuffer = CreateCompatibleDC(windowHdc);
HBITMAP map = CreateCompatibleBitmap(windowHdc, width, height);
SelectObject(secondaryBuffer, map);
return secondaryBuffer;
}
This function is called on resize
void CoreWindowFrame::UpdateBitmap(int width, int height)
{
HBITMAP map = CreateCompatibleBitmap(windowHdc, width, height);
SelectObject(secondaryBuffer, map);
}
And this is message processing:
case WM_SIZE:
ConsoleWrite("WM_SIZE RECIEVED");
width = *((unsigned short*)&lParam);
height = ((unsigned short*)&lParam)[1];
UpdateBitmap(width, height);
break;
case WM_PAINT:
ConsoleWrite("WM_PAINT RECIEVE");
windowGraphics->Clear(Color(255, 255, 255));
Pen* testPen = new Pen(Color(255, 0, 0), 1.0F);
windowGraphics->DrawLine(testPen, 0, 0, calls*3, 100);
BitBlt(windowHdc, 0, 0, updatedWidth, updatedHeight, secondaryBuffer, 0, 0, MERGECOPY);
3. Expected Result
The graphics object should be reusable and it should not be tossed away and replaced with a new one each time something is refreshed. Therefore it has to be updated accordingly in case anything is resized or changed. I expect the region to fit the size of the currently updated bitmap in the secondary HDC. The bitmap, as seen in the code, is updated properly. It is the Graphics object that does not. It acts like it still remembers some of the value from the previous BitMap which results in this behavior.
windowHdc = GetDC(hWnd);
HDC secondaryBuffer = CreateCompatibleDC(windowHdc);
HBITMAP map = CreateCompatibleBitmap(windowHdc, width, height);
SelectObject(secondaryBuffer, map);
return secondaryBuffer;
HDC obtained from GetDC or BeginPaint cannot be reused, as noted in comments.
You can however reuse memory bitmap (from CreateCompatibleBitmap) and reuse memory dc (obtained from CreateCompatibleDC), although there is usually no point in reusing memory dc.
Moreover, cleanup is required to avoid resource leak. Call ReleaseDC when you are finished with GetDC. See documentation for relevant release/delete functions.
Do this for a simple paint routine:
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
Gdiplus::Graphics gr(hdc);
Gdiplus::Pen testPen(Gdiplus::Color(255, 0, 0), 1.0F);
gr.Clear(Gdiplus::Color::White);
gr.DrawLine(&testPen, 0, 0, 100, 100);
EndPaint(hwnd, &ps);
return 0;
}
Usually you don't need to do anything more. Just call InvalidateRect in response to WM_SIZE to update paint.
For double-buffering, or drawing on a canvas, you can create a memory bitmap and reuse it. If window size changes, then you have to call DeleteObject for the old bitmap, and create a new bitmap based on the new size. Or you can create a bitmap which matches the largest window size, then use this bitmap for all window sizes.
See the example below for double-buffer paint (however reusing hbitmap is not necessary in this case)
HBITMAP hbitmap = NULL;
void InitPaint(HWND hwnd)
{
HDC hdc = GetDC(hwnd);
//create a bitmap with max size:
int w = GetSystemMetrics(SM_CXFULLSCREEN);
int h = GetSystemMetrics(SM_CYFULLSCREEN);
hbitmap = CreateCompatibleBitmap(hdc, w, h);
ReleaseDC(hwnd, hdc);
}
void cleanup()
{
if (hbitmap)
DeleteObject(hbitmap);
}
...
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
RECT rc; GetClientRect(hwnd, &rc);
int w = rc.right;
int h = rc.bottom;
HDC memdc = CreateCompatibleDC(hdc);
HBITMAP oldbmp = (HBITMAP)SelectObject(memdc, hbitmap);
Gdiplus::Graphics gr(memdc);
Gdiplus::Pen testPen(Gdiplus::Color(255, 0, 0), 1.0F);
gr.Clear(Gdiplus::Color(255, 255, 255));
gr.DrawLine(&testPen, 0, 0, w, h);
BitBlt(hdc, 0, 0, w, h, memdc, 0, 0, SRCCOPY);
//cleanup:
SelectObject(memdc, oldbmp);
DeleteDC(memdc);
EndPaint(hwnd, &ps);
return 0;
}
Summary:
GetDC doesn't create anything. It only returns a reference to an existing handle which is used by the system. When you are finished with this handle, release it. There is no way to optimize this any further.
Other GDI objects, like pen or bitmap, can be created for your program and they can be reused. It only takes a few nano-seconds to create/destroy a pen, so it's usually not worth the added complexity to store these objects and keeping track.
The main bottle-neck is when you draw on the screen, for example drawing a line. You have to talk to the graphics card and talk to the monitor which takes a while. You can use double-buffering to draw on a bitmap, then BitBlt on the screen. BitBlt can be slow too, depending on the system.
For animation, use double-buffering if you see flicker.
For better performance you can use newer technologies like Direct2D
If animation is still too slow, consider using a second thread to run any math type calculations (the second thread should not reference any window handle, such as HDC from GetDC or BeginPaint)
I have a little modified QScreen::grabWindow function. And on some computers, unfortunately I didn't find relationship between them, BitBlt freezes for even minutes! Why this can happen and what can I do?
QPixmap DetectionFlow::grabScreen(HWND h)
{
RECT rect;
GetClientRect(h, (LPRECT)&rect);
// get the height and width of the screen
int height = rect.bottom - rect.top;
int width = rect.right - rect.left;
// Create and setup bitmap
HDC display_dc = GetDC(0);
HDC bitmap_dc = CreateCompatibleDC(display_dc);
HBITMAP bitmap = CreateCompatibleBitmap(display_dc, width, height);
HGDIOBJ null_bitmap = SelectObject(bitmap_dc, bitmap);
HDC window_dc = GetDC(h);
BitBlt(bitmap_dc, 0, 0, width, height, window_dc, 0, 0, SRCCOPY);
// clean up all but bitmap
ReleaseDC(h, window_dc);
SelectObject(bitmap_dc, null_bitmap);
DeleteDC(bitmap_dc);
const QPixmap pixmap = qt_pixmapFromWinHBITMAP(bitmap);
DeleteObject(bitmap);
ReleaseDC(0, display_dc);
return pixmap;
}
PS. What is interesting, on computers, where freezes, it freezes randomly. So usually there it works fast (a couple of ms) and then freeze.
Problem was in Aero. Computers that freezes with BitBlt has Win7 and Aero. Without Aero all ok.
I need do copy HDC content but my code is not working - any ideas why? Everything is well until I'm trying to copy between HDC objects. It seems that bits goes nowhere. I'm new to GDI programming.
I'm not sure how SelectObject should work here.
PAINTSTRUCT ps;
HDC paintDC = BeginPaint(hWnd, &ps);
HDC imageDC = ::CreateCompatibleDC(paintDC);
HDC bufferDC = ::CreateCompatibleDC(paintDC);
BITMAPINFO bitmapInfo;
memset ( &bitmapInfo, 0, sizeof(BITMAPINFOHEADER) );
bitmapInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
int scanLines = GetDIBits(imageDC, // handle to DC
m_bitmap, // handle to bitmap
0, // first scan line to set
0, // number of scan lines to copy
NULL, // array for bitmap bits
&bitmapInfo, // bitmap data buffer
DIB_RGB_COLORS ); // RGB or palette index
// Paint the bitmap image.
HBITMAP pOldBitmap = (HBITMAP)SelectObject( imageDC, m_bitmap );
int width = bitmapInfo.bmiHeader.biWidth;
int height = bitmapInfo.bmiHeader.biHeight;
// Copy imageDC to bufferDC
BitBlt(bufferDC, 0, 0,
width, height,
imageDC, 0, 0, SRCCOPY) ;
BitBlt(paintDC, 0, 0,
width, height,
imageDC, 0, 0, SRCCOPY);
SelectObject(imageDC, pOldBitmap);
When you call CreateCompatibleDC function it'll create so called "memory DC" object. Memory DC doesn't have any image data attached to it by default (in fact id does, 1x1 monochrome bitmap, which is not what you want).
You do select a bitmap object for your imageDC, but you don't select any bitmap object for your bufferDC, that's why "bits go nowhere".
Create a HBITMAP object for your bufferDC (you can use CreateCompatibleBitmap for this) and select it for your bufferDC before performing any blits into it.
Remember one thing: DC object is an interface to something you can paint on, it doesn't contain any bitmap data itself.
Is there a way to receive the colour values for each pixel in the client area of a window, with gdi?
As noted in comment by #JerryCoffin. Here's a simple example
hDC = GetDC(hwnd);
hBitmap = CreateCompatibleBitmap(hDC, width, height);
hMemDC = CreateCompatibleDC(hDC);
hOld = SelectObject(hMemDC, hBitmap);
BitBlt(hMemDC, 0, 0, width, height, hDC, x, y, SRCCOPY);
// Clean up
DeleteDC(hMemDC);
ReleaseDC(hwnd, hDC);
You should have a bitmap object selected into memory DC for which you can use GetPixel GDI function and then you can also extract the color values using GetRValue() , GetGValue() , and GetBValue() macros.