【问题标题】:Crop an HBITMAP with C++ on Windows在 Windows 上使用 C++ 裁剪 HBITMAP
【发布时间】:2020-06-09 03:23:32
【问题描述】:

我有一个HBITMAP 持有一个窗口的屏幕截图。现在我想从中裁剪出某个区域/矩形并将其作为新的HBITMAP 返回。

然而,以下代码仅将图像压缩到正确的新矩形大小,但不会对其进行裁剪:

HBITMAP crop_image(const RECT rectangle, const HBITMAP source_image)
{
    const auto h_clone = static_cast<HBITMAP>(CopyImage(source_image, IMAGE_BITMAP, rectangle.right - rectangle.left,
                                                        rectangle.bottom - rectangle.top, LR_CREATEDIBSECTION));

    const auto hdc_mem = CreateCompatibleDC(nullptr);
    const auto hdc_mem2 = CreateCompatibleDC(nullptr);

    const auto h_old_bmp = static_cast<HBITMAP>(SelectObject(hdc_mem, source_image));
    const auto h_old_bmp2 = static_cast<HBITMAP>(SelectObject(hdc_mem2, h_clone));

    BitBlt(hdc_mem2, 0, 0, rectangle.right - rectangle.left, rectangle.bottom - rectangle.top,
           hdc_mem, rectangle.left, rectangle.top, SRCCOPY);

    SelectObject(hdc_mem, h_old_bmp);
    SelectObject(hdc_mem2, h_old_bmp2);

    DeleteDC(hdc_mem);
    DeleteDC(hdc_mem2);

    return h_clone;
}

如何修复我的代码以根据需要裁剪图像?

【问题讨论】:

  • CopyImage() 是错误,它会拉伸图像。很难猜到它为什么会出现,复制是 BitBlt 的工作。

标签: c++ windows winapi gdi


【解决方案1】:

由于我很长时间没有在Win32上工作,我不确定这是否可行,所以让我们试试吧。

这是一个想法:

  1. 创建一个新的内存 DC。
  2. 创建一个新的位图,而不是从源中克隆。
  3. BitBlt 将源位图的目标矩形放入刚刚创建的矩形中。

创建新的 DC 和位图

使用CreateCompatibleDC,就像创建内存 DC 一样。然后用CreateCompatibleBitmap 创建一个新的位图。位图的宽度和高度将是裁剪位图的大小。然后用SelectObject将其选入DC。

从源中复制目标矩形

现在您需要使用CreateCompatibleDC 为源位图创建一个新的DC,并使用SelectObject 将源位图选择到其中。然后使用BitBlt 将源位图的矩形复制到刚刚创建的位图中。目的地xy 将为零,因为我们想从左上角开始绘制。源xy 是您要从源位图开始复制的位置。

【讨论】:

    【解决方案2】:

    您的代码可以简化为:

    HBITMAP crop_image(const RECT rectangle, const HBITMAP source_image)
    {
        const auto h_clone = static_cast<HBITMAP>(CopyImage(source_image, IMAGE_BITMAP, rectangle.right - rectangle.left,
                                                            rectangle.bottom - rectangle.top, LR_CREATEDIBSECTION));    
    
        return h_clone;
    }
    

    删除的代码没有任何用处。测试后,我可以得到裁剪区域,而不是缩小的屏幕截图。因为你没有提供完整的代码,所以我无法得出更多结论。

    截屏的过程,我们可以根据官方的例子轻松修改。

    Capturing an Image

    示例中详细描述了所使用的 API。

    只需要修改部分代码,比如更改裁剪区域,示例中还展示了如何将裁剪区域保存为文件。

    int CaptureAnImage(HWND hWnd)
    {
        HDC hdcScreen;
        HDC hdcWindow;
        HDC hdcMemDC = NULL;
        HBITMAP hbmScreen = NULL;
        BITMAP bmpScreen;
        RECT rc;
        rc.left = 0;
        rc.top = 0;
        rc.right = 800;
        rc.bottom = 600;
    
        // Retrieve the handle to a display device context for the client 
        // area of the window. 
        hdcScreen = GetDC(NULL);
        hdcWindow = GetDC(hWnd);
    
        // Create a compatible DC which is used in a BitBlt from the window DC
        hdcMemDC = CreateCompatibleDC(hdcWindow);
    
        if (!hdcMemDC)
        {
            MessageBox(hWnd, L"CreateCompatibleDC has failed", L"Failed", MB_OK);
        }
    
        // Get the client area for size calculation
        RECT rcClient;
        GetClientRect(hWnd, &rcClient);
    
        //This is the best stretch mode
        SetStretchBltMode(hdcWindow, HALFTONE);
    
        //The source DC is the entire screen and the destination DC is the current window (HWND)
        if (!StretchBlt(hdcWindow,
            0, 0,
            rcClient.right, rcClient.bottom,
            hdcScreen,
            0, 0,
            GetSystemMetrics(SM_CXSCREEN),
            GetSystemMetrics(SM_CYSCREEN),
            SRCCOPY))
        {
            MessageBox(hWnd, L"StretchBlt has failed", L"Failed", MB_OK);
        }
    
        // Create a compatible bitmap from the Window DC
        hbmScreen = CreateCompatibleBitmap(hdcWindow, rc.right - rc.left, rc.bottom - rc.top);
    
        if (!hbmScreen)
        {
            MessageBox(hWnd, L"CreateCompatibleBitmap Failed", L"Failed", MB_OK);
        }
    
        // Select the compatible bitmap into the compatible memory DC.
        SelectObject(hdcMemDC, hbmScreen);
    
        // Bit block transfer into our compatible memory DC.
    
    
        if (!BitBlt(hdcMemDC,
            rc.left, rc.top,
            rc.right - rc.left, rc.bottom - rc.top,
            hdcWindow,
            0, 0,
            SRCCOPY))
        {
            MessageBox(hWnd, L"BitBlt has failed", L"Failed", MB_OK);
        }
    
    
    
    //  HBITMAP bmpnew = crop_image(rc, hbmScreen);
    
        // Get the BITMAP from the HBITMAP
        GetObject(hbmScreen, sizeof(BITMAP), &bmpScreen);
    
        BITMAPFILEHEADER   bmfHeader;
        BITMAPINFOHEADER   bi;
    
        bi.biSize = sizeof(BITMAPINFOHEADER);
        bi.biWidth = bmpScreen.bmWidth;
        bi.biHeight = bmpScreen.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 = ((bmpScreen.bmWidth * bi.biBitCount + 31) / 32) * 4 * bmpScreen.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(hdcWindow, hbmScreen, 0,
            (UINT)bmpScreen.bmHeight,
            lpbitmap,
            (BITMAPINFO *)&bi, DIB_RGB_COLORS);
    
        // A file is created, this is where we will save the screen capture.
        HANDLE hFile = CreateFile(L"captureqwsx.bmp",
            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);
    
        //Clean up
        DeleteObject(hbmScreen);
        DeleteObject(hdcMemDC);
        ReleaseDC(NULL, hdcScreen);
        ReleaseDC(hWnd, hdcWindow);
    
        return 0;
    }
    

    它会得到当前屏幕的截屏(800x600)文件。

    【讨论】:

    • @BullyWiiPlaza 你好,这个答案对你有帮助吗?
    猜你喜欢
    • 1970-01-01
    • 2013-10-20
    • 1970-01-01
    • 1970-01-01
    • 2020-10-18
    • 1970-01-01
    • 2012-04-18
    • 1970-01-01
    • 2011-05-27
    相关资源
    最近更新 更多