Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is GetClientRect including the window border and title bar?

I'm writing some code that will take a screenshot of another application, given its' window handle, in C++. The method I am using is to use BitBlt. My application is successfully taking the screenshot and I have a function that will save that image data out to a bmp file.

The screenshot contains the window's chrome though. That is, the border and the title bar. Based on my understanding, GetClientRect is supposed to exclude the window's border and title bar. I understand that GetWindowRect returns the coordinates within the user's desktop, and GetClientRect returns the coordinates relative to the application itself.

I notice in my screenshots that the title bar and left border are visible, but the right border and bottom of the application are cut off. So, I'm thinking that if I want to exclude the title and borders then I need to be doing some sort of combination of GetWindowRect and GetClientRect, and using information about the window itself to offset the GetClientRect dimensions by whatever the height of the window's title bar is, for example.

Does this sound accurate, or is my code below doing something wrong?

#include <Windows.h>
#include "ScreenshotManager.h"

namespace Managers {

    ScreenshotManager::ScreenshotManager(HWND gameHandle) {

        // get a device context for the window
        m_gameContext = GetWindowDC(gameHandle);

        // create a compatible device context for bitblt
        m_bitmapContext = CreateCompatibleDC(m_gameContext);

        // get window client area dimensions
        GetClientRect(gameHandle, &m_gameClientArea);

    }

    bool ScreenshotManager::TakeScreenshot() {

        // create a compatible bitmap for the game screenshots
        m_bitmap = CreateCompatibleBitmap(m_gameContext, m_gameClientArea.right, m_gameClientArea.bottom);

        // select the bitmap into the compatible device context
        SelectObject(m_bitmapContext, m_bitmap);

        // perform bit block transfer
        if (BitBlt(m_bitmapContext, 0, 0, m_gameClientArea.right, m_gameClientArea.bottom, m_gameContext, 0, 0, SRCCOPY) == false)
            return false;

        // get information about the taken screenshot
        GetObject(m_bitmap, sizeof(BITMAP), &m_bitmapInformation);

        return true;

    }

    void ScreenshotManager::SaveScreenshot(LPCWSTR outputPath) {

        BITMAPFILEHEADER   bmfHeader;    
        BITMAPINFOHEADER   bi;

        bi.biSize = sizeof(BITMAPINFOHEADER);
        bi.biWidth = m_bitmapInformation.bmWidth;
        bi.biHeight = m_bitmapInformation.bmHeight;
        bi.biPlanes = 1;    
        bi.biBitCount = 32;    
        bi.biCompression = BI_RGB;    
        bi.biSizeImage = 0;  
        bi.biXPelsPerMeter = 0;    
        bi.biYPelsPerMeter = 0;    
        bi.biClrUsed = 0;    
        bi.biClrImportant = 0;

        DWORD dwBmpSize = ((m_bitmapInformation.bmWidth * bi.biBitCount + 31) / 32) * 4 * m_bitmapInformation.bmHeight;

        // Starting with 32-bit Windows, GlobalAlloc and LocalAlloc are implemented as wrapper functions that 
        // call HeapAlloc using a handle to the process's default heap. Therefore, GlobalAlloc and LocalAlloc 
        // have greater overhead than HeapAlloc.
        HANDLE hDIB = GlobalAlloc(GHND,dwBmpSize);
        char *lpbitmap = (char *)GlobalLock(hDIB);

        // Gets the "bits" from the bitmap and copies them into a buffer which is pointed to by lpbitmap.
        GetDIBits(m_gameContext, m_bitmap, 0, (UINT)m_bitmapInformation.bmHeight, lpbitmap, (BITMAPINFO *)&bi, DIB_RGB_COLORS);

        // A file is created, this is where we will save the screen capture.
        HANDLE hFile = CreateFile(outputPath, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);   

        // Add the size of the headers to the size of the bitmap to get the total file size
        DWORD dwSizeofDIB = dwBmpSize + sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);

        //Offset to where the actual bitmap bits start.
        bmfHeader.bfOffBits = (DWORD)sizeof(BITMAPFILEHEADER) + (DWORD)sizeof(BITMAPINFOHEADER); 

        //Size of the file
        bmfHeader.bfSize = dwSizeofDIB; 

        //bfType must always be BM for Bitmaps
        bmfHeader.bfType = 0x4D42; //BM   

        DWORD dwBytesWritten = 0;
        WriteFile(hFile, (LPSTR)&bmfHeader, sizeof(BITMAPFILEHEADER), &dwBytesWritten, NULL);
        WriteFile(hFile, (LPSTR)&bi, sizeof(BITMAPINFOHEADER), &dwBytesWritten, NULL);
        WriteFile(hFile, (LPSTR)lpbitmap, dwBmpSize, &dwBytesWritten, NULL);

        //Unlock and Free the DIB from the heap
        GlobalUnlock(hDIB);    
        GlobalFree(hDIB);

        //Close the handle for the file that was created
        CloseHandle(hFile);

    }

}
like image 803
Ryan Avatar asked Jan 17 '23 09:01

Ryan


1 Answers

GetClientRect() does not include the border and title bar. All it does is tell you the dimensions of the client area.

BitBlt() copies a rectangular region of pixels from one device context to another. In this example the source DC is a window DC, so the origin coordinates are relative to that window.

What your code is doing is copying a client-sized rectangle from the window’s origin. (That’s why the right and bottom edges go missing.)

You might be interested in AdjustWindowRectEx() to help identify the coordinates of the region you want to copy.

like image 101
Brian Nixon Avatar answered Jan 25 '23 23:01

Brian Nixon