GDI+ 屏幕截图不适用于奇怪的分辨率

问题描述 投票:0回答:1

嗨,我正在尝试用 c++ 创建一个屏幕捕获类(因为 python 屏幕截图速度很慢)。我创建了这个类,但我无法创建具有奇怪分辨率(例如 101x101)的屏幕截图。

我在 VS 中遇到一堆堆损坏和访问冲突错误,但我不知道如何调试它。感谢任何提示/技巧。

这是课程:

#include <windows.h>
#include <gdiplus.h>
#include <iostream>
#include <opencv2/opencv.hpp>

#pragma comment (lib,"Gdiplus.lib")

class ScreenCapturer {
private:
    // GDI+ token and startup input
    ULONG_PTR gdiplusToken;
    Gdiplus::GdiplusStartupInput gdiplusStartupInput;

    // Device contexts
    HDC hScreenDC;
    HDC hMemoryDC;

    // Screen dimensions
    int lastWidth;
    int lastHeight;

    // Bitmap
    HBITMAP hBitmap;
    HBITMAP hOldBitmap; // Store the old bitmap here to use for recovery and cleanup

public:
    ScreenCapturer(){
        Gdiplus::GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);

        hScreenDC = CreateDC(L"DISPLAY", NULL, NULL, NULL);
        if (hScreenDC == NULL) {
            throw std::runtime_error("Failed to create screen device context");
        }
 
        hMemoryDC = CreateCompatibleDC(hScreenDC);
        if (hMemoryDC == NULL) {
            DeleteDC(hScreenDC);
            throw std::runtime_error("Failed to create memory device context.");
        }

        lastWidth = GetDeviceCaps(hScreenDC, HORZRES);
        lastHeight = GetDeviceCaps(hScreenDC, VERTRES);

        hBitmap = CreateCompatibleBitmap(hScreenDC, lastWidth, lastHeight);
        if (!hBitmap) {
            DeleteDC(hMemoryDC);
            DeleteDC(hScreenDC);
            throw std::runtime_error("Failed to create compatible bitmap");
        }

        hOldBitmap = (HBITMAP)SelectObject(hMemoryDC, hBitmap);
    }

    ~ScreenCapturer() {
        SelectObject(hMemoryDC, hOldBitmap);
        DeleteDC(hMemoryDC);
        DeleteDC(hScreenDC);
        DeleteObject(hBitmap);
        Gdiplus::GdiplusShutdown(gdiplusToken);
    }

    cv::Mat captureScreen(int x, int y, int width, int height) {
        if (lastWidth != width || lastHeight != height) {
            SelectObject(hMemoryDC, hOldBitmap);
            DeleteObject(hBitmap);
            hBitmap = CreateCompatibleBitmap(hScreenDC, width, height);
            /*hOldBitmap = (HBITMAP)*/SelectObject(hMemoryDC, hBitmap); // Do we need to re-store the old bitmap?
            lastWidth = width;
            lastHeight = height;
        }

        if (BitBlt(hMemoryDC, 0, 0, width, height, hScreenDC, x, y, SRCCOPY) == NULL) {
            std::cout << "ERROR " << std::endl;
        }

        BITMAPINFOHEADER bi;
        bi.biSize = sizeof(BITMAPINFOHEADER);
        bi.biWidth = width;
        bi.biHeight = -height; // Negative for top-left origin
        bi.biPlanes = 1;
        bi.biBitCount = 24;
        bi.biCompression = BI_RGB;
        bi.biSizeImage = 0;
        bi.biXPelsPerMeter = 0;
        bi.biYPelsPerMeter = 0;
        bi.biClrUsed = 0;
        bi.biClrImportant = 0;

        // Allocate a cv::Mat for the area
        cv::Mat areaImage(height, width, CV_8UC3);

        // Copy the bits to the cv::Mat
        if (GetDIBits(hMemoryDC, hBitmap, 0, (UINT)height, areaImage.data, (BITMAPINFO*)&bi, DIB_RGB_COLORS) == NULL) {
            std::cout << "ERROR " << std::endl;
        }

        return areaImage;
    }
};

我正在这样测试这个类:

#include "ScreenCapturer.cpp"
int main() {
    ScreenCapturer capturer;
    auto screenshot1 = capturer.captureScreen(0, 0, 1920, 1080); // Works
    cv::imwrite("test1.png", screenshot1);
    auto screenshot2 = capturer.captureScreen(0, 0, 100, 100); // Works
    cv::imwrite("test2.png", screenshot2);
    auto screenshot3 = capturer.captureScreen(0, 0, 101, 101); // Doesn't work
    cv::imwrite("test3.png", screenshot3);
    std::cout << "Done\n";
}

我试图从 VS 获取更多信息。当它在调试模式下失败时,它首先会出现一条执行断点指令并显示以下消息:

断点指令(__debugbreak() 语句或类似的调用) 在Everylay.exe中执行。

然后当我继续时,它会显示如下消息:

Everylay.exe 中 0x00007FF9A7A4F349 (ntdll.dll) 处存在未处理的异常: 0xC0000374:堆已损坏(参数: 0x00007FF9A7AB97F0)。

然后是这个:

Everylay.exe 中的 0x00007FF9A797D990 (ntdll.dll) 处抛出异常: 0xC0000005:读取位置 0xFFFFFFFFFFFFFFFF 时发生访问冲突。

我真的不知道如何调试这个。希望有人能帮忙。

c++ screenshot gdi+ gdi
1个回答
0
投票

lpvBits

GetDIBits
 参数指向的缓冲区必须具有足以容纳 
height * scanline_width
 字节的大小。其中 
scanline_width
 是 4 字节对齐的 
img_width * bytes_per_pixel
。对于 101 图像宽度和每像素 3 字节的 
cv::mat
 提供的缓冲区来说,其大小将不够,因为它无法正确对齐扫描线。所以你会遇到缓冲区溢出。

© www.soinside.com 2019 - 2024. All rights reserved.