user2447581 user2447581 - 2 months ago 33
C++ Question

C++ CreateDIBitmap return null if move windows

I'm writing a demo program to load an image from file to OpenCV cv::Mat format, then convert to bitmap and display in Win32 windows. This is full source code:

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


This code worked fine for me (Windows 10):

enter image description here

But if it running on Windows 7, after quickly drag and move the windows so that it "go out of" the screen:

enter image description here

And this is the result after move the window back into screen:
enter image description here

In this case,
assert(bmp != nullptr)
will fall and the program exit suddenly.

This does not happen on Windows 10!


Why
CreateDIBitmap
return null in this case???

Answer

SelectObject doesn't create a handle. In the function below you don't create imageBmpOld and you are not responsible for destroying it. But you did create bitImage (it is created in ConvertCVMatToBMP) and you should destroy bitImage at the end.

// 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);
}

Cleanup for GetDC is done by ReleaseDC, not 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
    ...
}

Your window procedure doesn't always return a value. Add return 0; at the end.

The function attachImage() does not attach the image to window. It only paints the image on window. The way you have set it up it only works in response to WM_PAINT so you should really rename it OnPaint()

Also FindWindow(NULL, title) is not reliable because more than one window can have the same title. You should use GetConsoleWindow to get that window handle:

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

Better yet, you could use WinMain entry point to skip the console window. The easiest way is to create a new project in your IDE, it should let you select "win32 project" (not "win32 console project")

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