首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >如果移动窗口,C++ CreateDIBitmap返回null

如果移动窗口,C++ CreateDIBitmap返回null
EN

Stack Overflow用户
提问于 2016-09-15 11:05:28
回答 1查看 532关注 0票数 0

我正在编写一个演示程序,将图像从文件加载到OpenCV cv::Mat格式,然后转换为位图并在Win32窗口中显示。这是完整的源代码:

代码语言:javascript
运行
复制
// Win32
#include <Windows.h>

// OpenCV
#include <opencv2/core.hpp>
#include <opencv2/imgcodecs.hpp>
#pragma comment(lib, "opencv_core310d.lib")
#pragma comment(lib, "opencv_imgcodecs310d.lib")

// Marco
#define WIN_CLASS_NAME  TEXT("DisplayTest")
#define WIN_NAME        TEXT("Display Test")
#define IMAGE_SRC       "./image.jpg"

// Global object
cv::Mat imgMat;
HWND hwndWindow;

// Convert cv::Mat data to bitmap
HBITMAP ConvertCVMatToBMP(cv::Mat frame)
{
    auto convertOpenCVBitDepthToBits = [](const INT32 value) {
        auto regular = 0u;

        switch (value) {
            case CV_8U:
            case CV_8S:
                regular = 8u;
                break;

            case CV_16U:
            case CV_16S:
                regular = 16u;
                break;

            case CV_32S:
            case CV_32F:
                regular = 32u;
                break;

            case CV_64F:
                regular = 64u;
                break;

            default:
                regular = 0u;
                break;
        }

        return regular;
    };

    auto imageSize = frame.size();

    if (imageSize.width && imageSize.height) {
        auto headerInfo = BITMAPINFOHEADER{};
        ZeroMemory(&headerInfo, sizeof(headerInfo));

        headerInfo.biSize = sizeof(headerInfo);
        headerInfo.biWidth = imageSize.width;
        headerInfo.biHeight = -(imageSize.height); // negative otherwise it will be upsidedown
        headerInfo.biPlanes = 1;// must be set to 1 as per documentation frame.channels();

        const auto bits = convertOpenCVBitDepthToBits(frame.depth());
        headerInfo.biBitCount = frame.channels() * bits;

        auto bitmapInfo = BITMAPINFO{};
        ZeroMemory(&bitmapInfo, sizeof(bitmapInfo));

        bitmapInfo.bmiHeader = headerInfo;
        bitmapInfo.bmiColors->rgbBlue = 0;
        bitmapInfo.bmiColors->rgbGreen = 0;
        bitmapInfo.bmiColors->rgbRed = 0;
        bitmapInfo.bmiColors->rgbReserved = 0;

        auto dc = GetDC(nullptr);
        assert(dc != nullptr && "Failure to get DC");

        auto bmp = CreateDIBitmap(dc,
                                  &headerInfo,
                                  CBM_INIT,
                                  frame.data,
                                  &bitmapInfo,
                                  DIB_RGB_COLORS);
        assert(bmp != nullptr && "Failure creating bitmap from captured frame");

        DeleteDC(dc);
        return bmp;
    }

    return nullptr;

}

// Attach image to windows
void attachImage()
{
    HBITMAP bitImage = (HBITMAP)ConvertCVMatToBMP(imgMat);

    // Display image
    PAINTSTRUCT ps;
    HDC hdc = BeginPaint(hwndWindow, &ps);

    HDC imageDC = CreateCompatibleDC(hdc);
    BITMAP bm;
    HBITMAP imageBmpOld = (HBITMAP)SelectObject(imageDC, (HGDIOBJ)bitImage);

    GetObject(bitImage, sizeof(bm), &bm);

    BitBlt(
        hdc,         // tell it we want to draw to the screen
        0, 0,            // as position 0,0 (upper-left corner)
        (int)bm.bmWidth,   // width of the rect to draw
        (int)bm.bmHeight,   // height of the rect
        imageDC,        // the DC to get the rect from (our image DC)
        0, 0,            // take it from position 0,0 in the image DC
        SRCCOPY         // tell it to do a pixel-by-pixel copy
    );
    SelectObject(imageDC, (HGDIOBJ)imageBmpOld);
    DeleteDC(imageDC);
    DeleteObject((HGDIOBJ)imageBmpOld);
    EndPaint(hwndWindow, &ps);
}

// WndProc callback
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam)
{
    switch (message) {
        case WM_DESTROY:
            PostQuitMessage(0);
            break;

        case WM_PAINT:
            attachImage();
            break;

        default:
            return (DefWindowProc(hwnd, message, wparam, lparam));
    }
}

// Register class
ATOM MyRegisterClass(HINSTANCE hInstance)
{
    WNDCLASS wc = { 0 };
    wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
    wc.hCursor = LoadCursor(hInstance, IDC_ARROW);
    wc.hIcon = LoadIcon(hInstance, IDI_APPLICATION);
    wc.hInstance = hInstance;
    wc.lpfnWndProc = WndProc;
    wc.lpszClassName = WIN_CLASS_NAME;
    wc.style = CS_HREDRAW | CS_VREDRAW;

    return RegisterClass(&wc);
}

int main(int argc, char* argv[])
{
    // Register class
    char t[500];
    GetConsoleTitleA(t, 500);
    HWND hwndConsole = FindWindowA(NULL, t);
    HINSTANCE hInstance = (HINSTANCE)GetWindowLong(hwndConsole, GWL_HINSTANCE);
    MyRegisterClass(hInstance);

    // Init data
    imgMat = cv::imread(IMAGE_SRC);

    // Create Win32 windows
    hwndWindow = CreateWindow(WIN_CLASS_NAME, WIN_NAME, WS_OVERLAPPEDWINDOW ^ WS_THICKFRAME,
                              CW_USEDEFAULT, 0, CW_USEDEFAULT, 0,
                              NULL, NULL, hInstance, NULL);
    ShowWindow(hwndWindow, SW_SHOWNORMAL);
    UpdateWindow(hwndWindow);

    MSG msg;
    HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(109));

    // Main message loop:
    while (GetMessage(&msg, NULL, 0, 0)) {
        if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg)) {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }

    return 0;
}

这段代码对我来说很好(Windows 10):

但是如果它在Windows 7上运行,那么在快速拖动和移动窗口之后,它就会“走出”屏幕:

这是将窗口移回屏幕后的结果:

在这种情况下,assert(bmp != nullptr)会崩溃,程序会突然退出。

这种情况不会发生在Windows 10上!

为什么在这种情况下CreateDIBitmap返回null?

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2016-09-15 15:25:32

SelectObject不创建句柄。在下面的函数中,您不创建imageBmpOld,也不负责销毁它。但是您确实创建了bitImage (它是在ConvertCVMatToBMP中创建的),您应该在最后销毁bitImage

代码语言:javascript
运行
复制
// Attach image to windows
void attachImage()
{
    HBITMAP bitImage = (HBITMAP)ConvertCVMatToBMP(imgMat);
    ...
    HBITMAP imageBmpOld = (HBITMAP)SelectObject(imageDC, (HGDIOBJ)bitImage);
    ...
    SelectObject(imageDC, imageBmpOld);
    DeleteDC(imageDC);
    //DeleteObject((HGDIOBJ)imageBmpOld); <<== remove this line
    DeleteObject(bitImage); //add this
    EndPaint(hwndWindow, &ps);
}

GetDC的清理是由ReleaseDC而不是DeleteDC完成的

代码语言:javascript
运行
复制
HBITMAP ConvertCVMatToBMP(cv::Mat frame)
{
    ...    
    auto dc = GetDC(nullptr);
    assert(dc != nullptr && "Failure to get DC");
    auto bmp = CreateDIBitmap(dc, 
                    &headerInfo, CBM_INIT, frame.data, &bitmapInfo, DIB_RGB_COLORS);
    assert(bmp != nullptr && "Failure creating bitmap from captured frame");
    //DeleteDC(dc); <<== remove
    ReleaseDC(nullptr, dc); //<<== add
    ...
}

窗口过程并不总是返回值。在末尾添加return 0;

函数attachImage()不将图像附加到窗口。它只在窗口上绘制图像。设置它的方式只在响应WM_PAINT时起作用,所以您应该将它重命名为OnPaint()

另外,FindWindow(NULL, title)不可靠,因为多个窗口可以具有相同的标题。您应该使用GetConsoleWindow来获取窗口句柄:

代码语言:javascript
运行
复制
int main()
{
    HWND hwndConsole = GetConsoleWindow();
    ...
}

更好的是,您可以使用WinMain入口点跳过控制台窗口。最简单的方法是在IDE中创建一个新项目,它应该允许您选择"win32项目“(而不是"win32控制台项目”)。

代码语言:javascript
运行
复制
//int main()
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, LPTSTR, int)
{
    ...
}
票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/39509511

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档