Friday, December 25, 2015

How to draw OpenCV Mat data into native Windows client area utilizing GDI


under some scenario, probably one wants to combine the OpenCV's ability to manipulate pixel values, and the Windows ability to display the resulting image in a native way (for example, utilizing the GDI system). Such a requirement demands some deep understanding of image representation between these two systems, and probably a non-trivial task than just taking for granted.


I came across a same situation as just mentioned above, and after some try-and-error iteration, finally everything seems have been put onto track. since it hasn't undergone thorough test, I am not sure the tracks are all right tracks.

would like to share here for polishing or beneficial for others facing the same situation.

The header file is as follows:

#pragma once

#include <opencv2/core.hpp>

class COpenCVImage
{
public:
COpenCVImage(LPCTSTR lpszFileName);
~COpenCVImage(void);

BOOL GetSize(LPRECT lpRect);
BOOL DrawBitmap(HDC hdc, LPRECT lpRect);

BOOL Sharpen(HWND hWnd);
BOOL Flip(HWND hWnd);

private:
bool createBitmap(HDC hdc, cv::Mat mat);
private:
cv::Mat m_matImage;
HBITMAP m_hBitmap;

bool m_bInit;
};


the implementation file is as follows:

#include "stdafx.h"
#include "OpenCVImage.h"

#include <Shlwapi.h>
// The following header file will be excluded, since we will draw the picture 
// to the window all by ourselves
// #include <opencv2/highgui.hpp>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/imgproc.hpp>

COpenCVImage::COpenCVImage(LPCTSTR lpszFileName) : m_hBitmap(NULL), m_bInit(false)
{
if(!PathFileExists(lpszFileName))
{
MessageBox(NULL, _T("Cannot open the image file"), _T("Error"), MB_OK);
return ;
}

char* pFileName;
UINT nLen;

nLen = WideCharToMultiByte(CP_UTF8, 0, lpszFileName, sizeof(TCHAR) * (_tcslen(lpszFileName) + 1), NULL, 0, NULL, NULL);
pFileName = new char[nLen];
WideCharToMultiByte(CP_UTF8, 0, lpszFileName, sizeof(TCHAR) * (_tcslen(lpszFileName) + 1), pFileName, nLen, NULL, NULL);

m_matImage = cv::imread(pFileName, cv::IMREAD_COLOR);
delete [] pFileName;

if (m_matImage.empty())
{
MessageBox(NULL, _T("Cannot read in the image file"), _T("Error"), MB_OK);
return;
}

m_bInit = true;
}

COpenCVImage::~COpenCVImage(void)
{
if (m_hBitmap)
DeleteObject(m_hBitmap);
}


BOOL COpenCVImage::DrawBitmap(HDC hdc, LPRECT lpRect)
{
if (!m_bInit)
return FALSE;

HDC hDCMem = CreateCompatibleDC(hdc);

if (!m_hBitmap)
{
createBitmap(hDCMem, m_matImage);
}

SelectObject(hDCMem, m_hBitmap);

BitBlt(hdc, lpRect->left, lpRect->top, lpRect->right - lpRect->left, 
lpRect->bottom - lpRect->top, hDCMem, 0, 0, SRCCOPY);

DeleteDC(hDCMem);

return TRUE;
}


BOOL COpenCVImage::GetSize(LPRECT lpRect)
{
if (!m_bInit)
{
memset(lpRect, 0, sizeof(RECT));
return FALSE;
}

lpRect->left = 0;
lpRect->top = 0;
lpRect->right = m_matImage.cols;
lpRect->bottom = m_matImage.rows;

return TRUE;
}

BOOL COpenCVImage::Sharpen(HWND hWnd)
{
if (!m_bInit)
return FALSE;

cv::Mat matFiltImage;
cv::Matx33f matKernel;

matKernel(1, 1) = 5.0;
matKernel(0, 1) = - 1.0;
matKernel(1, 0) = - 1.0;
matKernel(1, 2) = - 1.0;
matKernel(2, 1) = - 1.0;

cv::filter2D(m_matImage, matFiltImage, m_matImage.depth(), matKernel);

HDC hDCMem = CreateCompatibleDC(GetDC(hWnd));
createBitmap(hDCMem, matFiltImage);
DeleteObject(hDCMem);

InvalidateRect(hWnd, NULL, TRUE);

return TRUE;
}

BOOL COpenCVImage::Flip(HWND hWnd)
{
if (!m_bInit)
return FALSE;

cv::Mat matFlipImage;
cv::flip(m_matImage, matFlipImage, 1);

HDC hDCMem = CreateCompatibleDC(GetDC(hWnd));
createBitmap(hDCMem, matFlipImage);
DeleteObject(hDCMem);

InvalidateRect(hWnd, NULL, TRUE);

return TRUE;
}

bool COpenCVImage::createBitmap(HDC hdc, cv::Mat mat)
{
unsigned char* lpBitmapBits;

if (m_hBitmap)
DeleteObject(m_hBitmap);

BITMAPINFO bi; 
ZeroMemory(&bi, sizeof(BITMAPINFO));
bi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bi.bmiHeader.biWidth = m_matImage.cols;
bi.bmiHeader.biHeight = - m_matImage.rows;
bi.bmiHeader.biPlanes = 1;
bi.bmiHeader.biBitCount = (m_matImage.elemSize() << 3);
bi.bmiHeader.biCompression = BI_RGB;

m_hBitmap = CreateDIBSection(hdc, &bi, DIB_RGB_COLORS, (VOID**)&lpBitmapBits, NULL, 0);

#define ALIGN(x,a)              __ALIGN_MASK(x, a-1)
#define __ALIGN_MASK(x,mask)    (((x)+(mask))&~(mask))
int width = m_matImage.cols * m_matImage.elemSize();
int pitch = ALIGN(width, 4);
#undef __ALIGN_MASK
#undef ALIGN

for(int i = 0; i < m_matImage.rows; i ++)
{
unsigned char* data = mat.ptr<unsigned char>(i);
memcpy(lpBitmapBits, data, width);
lpBitmapBits += pitch;
}

return true;
}

a typical usage probably be:

1. instantiate the instance, for example:
COpenCVImage image(_T("lenna.jpg"));

2. respond to the WM_PAINT message:

case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
// TODO: Add any drawing code here...

RECT rect;
image.GetSize(&rect);
image.DrawBitmap(hdc, &rect);

EndPaint(hWnd, &ps);

break;


the final result is as follows:


appreciate any suggestion to enhance the code

No comments:

Post a Comment