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

时间:2016-09-15 11:05:29

标签: c++ opencv winapi bitmap

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

// 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):
enter image description here

但是如果它在Windows 7上运行,那么在快速拖动并移动窗口之后,它就会退出&#34;屏幕:
enter image description here

这是将窗口移回屏幕后的结果: enter image description here

在这种情况下,assert(bmp != nullptr)会下降,程序会突然退出 这不会发生在Windows 10上!

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

1 个答案:

答案 0 :(得分:2)

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

// 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

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来获取该窗口句柄:

int main()
{
    HWND hwndConsole = GetConsoleWindow();
    ...
}

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

//int main()
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, LPTSTR, int)
{
    ...
}