wxWidgets整合OpenGL+OpenCV+MathPlot(一种Windows和Linux通用的方法)
写在前面:其实本不应该发在Java方面的,只是最近在做的项目因为实时应用的关系没有使用Java,因此借这里记录一下心得。
最近在一个在Linux下开放的项目中用到了wxWidgets,当时在GTK+、QT和wx之间选择了很久,最终确定选择wxWidgets。有关这个框架的详细信息,请自行google之。
1. 整合OpenGL
WxWidgets中整合OpenGL是十分简单的,因为wxWidgets本身对OpenGL进行了封装,因此只需要按照example中的例子进行编写即可。一种常见的方法是继承wxGLCanvas类,将EVT_PAINT的回调函数进行重载即可。一段示例代码如下:
static
int
attriblist[]
=
{
WX_GL_RGBA, WX_GL_MIN_RED, 1 , WX_GL_MIN_GREEN, 1 ,
WX_GL_MIN_BLUE, 1 , WX_GL_DEPTH_SIZE, 1 , WX_GL_DOUBLEBUFFER, None
};
BEGIN_EVENT_TABLE(UIOpenGLCanvas, wxGLCanvas)
EVT_SIZE(UIOpenGLCanvas::OnSize)
EVT_PAINT(UIOpenGLCanvas::OnPaint)
EVT_MOUSE_EVENTS(UIOpenGLCanvas::OnMouseEvent)
END_EVENT_TABLE()
UIOpenGLCanvas::UIOpenGLCanvas(wxWindow * parent, const wxString & caption)
// :wxGLCanvas(parent, wxID_ANY, attriblist, wxDefaultPosition, wxDefaultSize, wxFULL_REPAINT_ON_RESIZE, wxT("GLCanvas"), wxNullPalette)
// :wxGLCanvas(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxFULL_REPAINT_ON_RESIZE, wxT("GLCanvas"))
:wxGLCanvas(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxFULL_REPAINT_ON_RESIZE, wxT( " GLCanvas " ), attriblist, wxNullPalette)
,m_caption(caption), count( 0 ) {
int argc = 1 ;
char * argv[ 1 ] = { wxString((wxTheApp -> argv)[ 0 ]).char_str() };
/*
NOTE: this example uses GLUT in order to have a free teapot model
to display, to show 3D capabilities. GLUT, however, seems to cause problems
on some systems. If you meet problems, first try commenting out glutInit(),
then try comeenting out all glut code
*/
glutInit( & argc, argv);
}
UIOpenGLCanvas:: ~ UIOpenGLCanvas() {
}
void UIOpenGLCanvas::OnSize(wxSizeEvent & event ) {
// this is also necessary to update the context on some platforms
wxGLCanvas::OnSize( event );
// set GL viewport (not called by wxGLCanvas::OnSize on all platforms)
int w, h;
GetClientSize( & w, & h);
if (GetContext()) {
SetCurrent();
glViewport( 0 , 0 , (GLint) w, (GLint) h);
}
}
void UIOpenGLCanvas::OnMouseEvent(wxMouseEvent & event ) {
static int dragging = 0 ;
static float last_x, last_y;
// printf("%f %f %d\n", event.GetX(), event.GetY(), (int)event.LeftIsDown());
if ( event .LeftIsDown()) {
if ( ! dragging) {
dragging = 1 ;
} else {
yrot += ( event .GetX() - last_x) * 1.0 ;
xrot += ( event .GetY() - last_y) * 1.0 ;
Refresh( false );
}
last_x = event .GetX();
last_y = event .GetY();
} else
dragging = 0 ;
}
void UIOpenGLCanvas::OnPaint(wxPaintEvent & WXUNUSED( event )) {
Render();
}
void UIOpenGLCanvas::Render() {
/* 此处很关键 */
wxPaintDC( this );
if ( ! GetContext())
return ;
SetCurrent();
glClearColor( 0.0 , 0.0 , 0.0 , 0.0 );
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glViewport( 0 , 0 , (GLint)GetSize().x, (GLint)GetSize().y);
glBegin(GL_POLYGON);
glColor3f( 1.0 , 0.0 , 0.0 );
glVertex2f( 0.1 , 0.1 );
glVertex2f( - 0.1 , 0.1 );
glVertex2f( - 0.1 , - 0.1 );
glVertex2f( 0.1 , - 0.1 );
glEnd();
// using a little of glut
glColor4f( 0 , 0 , 1 , 1 );
glutWireTeapot( 0.4 );
glPopMatrix();
glFlush();
SwapBuffers();
}
WX_GL_RGBA, WX_GL_MIN_RED, 1 , WX_GL_MIN_GREEN, 1 ,
WX_GL_MIN_BLUE, 1 , WX_GL_DEPTH_SIZE, 1 , WX_GL_DOUBLEBUFFER, None
};
BEGIN_EVENT_TABLE(UIOpenGLCanvas, wxGLCanvas)
EVT_SIZE(UIOpenGLCanvas::OnSize)
EVT_PAINT(UIOpenGLCanvas::OnPaint)
EVT_MOUSE_EVENTS(UIOpenGLCanvas::OnMouseEvent)
END_EVENT_TABLE()
UIOpenGLCanvas::UIOpenGLCanvas(wxWindow * parent, const wxString & caption)
// :wxGLCanvas(parent, wxID_ANY, attriblist, wxDefaultPosition, wxDefaultSize, wxFULL_REPAINT_ON_RESIZE, wxT("GLCanvas"), wxNullPalette)
// :wxGLCanvas(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxFULL_REPAINT_ON_RESIZE, wxT("GLCanvas"))
:wxGLCanvas(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxFULL_REPAINT_ON_RESIZE, wxT( " GLCanvas " ), attriblist, wxNullPalette)
,m_caption(caption), count( 0 ) {
int argc = 1 ;
char * argv[ 1 ] = { wxString((wxTheApp -> argv)[ 0 ]).char_str() };
/*
NOTE: this example uses GLUT in order to have a free teapot model
to display, to show 3D capabilities. GLUT, however, seems to cause problems
on some systems. If you meet problems, first try commenting out glutInit(),
then try comeenting out all glut code
*/
glutInit( & argc, argv);
}
UIOpenGLCanvas:: ~ UIOpenGLCanvas() {
}
void UIOpenGLCanvas::OnSize(wxSizeEvent & event ) {
// this is also necessary to update the context on some platforms
wxGLCanvas::OnSize( event );
// set GL viewport (not called by wxGLCanvas::OnSize on all platforms)
int w, h;
GetClientSize( & w, & h);
if (GetContext()) {
SetCurrent();
glViewport( 0 , 0 , (GLint) w, (GLint) h);
}
}
void UIOpenGLCanvas::OnMouseEvent(wxMouseEvent & event ) {
static int dragging = 0 ;
static float last_x, last_y;
// printf("%f %f %d\n", event.GetX(), event.GetY(), (int)event.LeftIsDown());
if ( event .LeftIsDown()) {
if ( ! dragging) {
dragging = 1 ;
} else {
yrot += ( event .GetX() - last_x) * 1.0 ;
xrot += ( event .GetY() - last_y) * 1.0 ;
Refresh( false );
}
last_x = event .GetX();
last_y = event .GetY();
} else
dragging = 0 ;
}
void UIOpenGLCanvas::OnPaint(wxPaintEvent & WXUNUSED( event )) {
Render();
}
void UIOpenGLCanvas::Render() {
/* 此处很关键 */
wxPaintDC( this );
if ( ! GetContext())
return ;
SetCurrent();
glClearColor( 0.0 , 0.0 , 0.0 , 0.0 );
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glViewport( 0 , 0 , (GLint)GetSize().x, (GLint)GetSize().y);
glBegin(GL_POLYGON);
glColor3f( 1.0 , 0.0 , 0.0 );
glVertex2f( 0.1 , 0.1 );
glVertex2f( - 0.1 , 0.1 );
glVertex2f( - 0.1 , - 0.1 );
glVertex2f( 0.1 , - 0.1 );
glEnd();
// using a little of glut
glColor4f( 0 , 0 , 1 , 1 );
glutWireTeapot( 0.4 );
glPopMatrix();
glFlush();
SwapBuffers();
}
2. 整合OpenCV
这个话题在网上有过讨论,在OpenCV中文论坛中提出了一种向HDC绘图的方法。不过这种方法的局限在于:HDC是Windows平台下特有的结构,在Linux下是不可行的。
其实OpenCV的核心是IplImage结构,基本上所有的OpenCV绘图语句以及相应的算法都可以通过这个结构衍生出来。因此,一种直接的想法是如何将IplImage转换为wxWidgets中的wxImage类型,这样就可以直接在wxWidgets绘制IplImage类型的数据了。于是在网上寻找后,在一个国外论坛中找到了现成的代码如下:
void
copy_and_swap_rb(
char
*
s,
char
*
d,
int
size)
{
// Copy image data source s to destination d, swapping R and B channels.
// Assumes 8 bit depth, 3 interleaved channels, and width_step = width*3
const int step = 3 ;
char * end = s + size;
while (s < end) {
d[ 0 ] = s[ 2 ];
d[ 1 ] = s[ 1 ];
d[ 2 ] = s[ 0 ];
d += step;
s += step;
}
}
void wx2cv(wxImage & wx, IplImage * ipl)
{
// Copy image data from wxWidgets image to Ipl (opencv) image
// Assumes ipl image has seq "GBR", depth 8, and 3 channels, and
// has the same size as the wxImage, and width_step = width*3.
copy_and_swap_rb(( char * )wx.GetData(), ipl -> imageData, ipl -> imageSize);
}
void cv2wx(IplImage * ipl, wxImage & wx )
{
// Copy image data from Ipl (opencv) image to wxImage
// Assumes ipl image has seq "GBR", depth 8, and 3 channels, and
// has the same size as the wxImage, and width_step = width*3.
copy_and_swap_rb( ipl -> imageData, ( char * )wx.GetData(),
wx.GetWidth() * wx.GetHeight() * 3 );
}
IplImage * cv_from_wx(wxImage & wx)
{
// Return a new IplImage copied from a wxImage.
// Must be freed by user with cvReleaseImage().
IplImage * ret = cvCreateImage(cvSize(wx.GetWidth(), wx.GetHeight()),
IPL_DEPTH_8U, 3 );
wx2cv(wx, ret);
return ret;
}
wxImage wx_from_cv( IplImage * cx)
{
// Return new wxImage copied from a compatible IplImage.
// Assumes ipl image has seq "GBR", depth 8, and 3 channels
// Fear not. The copy on return is cheap; does not deep-copy the data.
wxImage wx(cx -> width, cx -> height, (unsigned char * ) malloc(cx -> imageSize), false );
cv2wx(cx, wx);
return wx;
}
{
// Copy image data source s to destination d, swapping R and B channels.
// Assumes 8 bit depth, 3 interleaved channels, and width_step = width*3
const int step = 3 ;
char * end = s + size;
while (s < end) {
d[ 0 ] = s[ 2 ];
d[ 1 ] = s[ 1 ];
d[ 2 ] = s[ 0 ];
d += step;
s += step;
}
}
void wx2cv(wxImage & wx, IplImage * ipl)
{
// Copy image data from wxWidgets image to Ipl (opencv) image
// Assumes ipl image has seq "GBR", depth 8, and 3 channels, and
// has the same size as the wxImage, and width_step = width*3.
copy_and_swap_rb(( char * )wx.GetData(), ipl -> imageData, ipl -> imageSize);
}
void cv2wx(IplImage * ipl, wxImage & wx )
{
// Copy image data from Ipl (opencv) image to wxImage
// Assumes ipl image has seq "GBR", depth 8, and 3 channels, and
// has the same size as the wxImage, and width_step = width*3.
copy_and_swap_rb( ipl -> imageData, ( char * )wx.GetData(),
wx.GetWidth() * wx.GetHeight() * 3 );
}
IplImage * cv_from_wx(wxImage & wx)
{
// Return a new IplImage copied from a wxImage.
// Must be freed by user with cvReleaseImage().
IplImage * ret = cvCreateImage(cvSize(wx.GetWidth(), wx.GetHeight()),
IPL_DEPTH_8U, 3 );
wx2cv(wx, ret);
return ret;
}
wxImage wx_from_cv( IplImage * cx)
{
// Return new wxImage copied from a compatible IplImage.
// Assumes ipl image has seq "GBR", depth 8, and 3 channels
// Fear not. The copy on return is cheap; does not deep-copy the data.
wxImage wx(cx -> width, cx -> height, (unsigned char * ) malloc(cx -> imageSize), false );
cv2wx(cx, wx);
return wx;
}
进行这样的转换后,我们就直接可以在wxWidgets中使用OpenCV的接口。
3. 整合MathPlot
MathPlot是sourceforge上的一个开源项目,其功能是使用wxWidgets提供的绘图方法构建操作DC绘图的高级接口。这个项目的源代码十分简单,只有两个文件,但是功能却很实用。我在sourceforge上给了好评。
MathPlot内部实现了坐标轴的拖拽、平移和缩放,将图形划分为Layer,并且引入了动态Layer的概念,即在这个Layer上绘制的图形可以通过重设局部坐标系的原点基准坐标实现移动,并绘制轨迹。而且,MathPlot内部实现了双缓冲,因此,这个框架对于需要实时显示轨迹的简单应用来说具有很好使用价值。
由于MathPlot直接使用了wxWidgets的绘图接口,因此其整合十分简单,只需要在需要绘制的Panel上使用MathPlot提供的接口即可实现整合。
以上简单说明了wxWidgets如何整合OpenGL、OpenCV和MathPlot三种不同的绘图框架,最后给一个将三种绘图方法用在同一个窗口中实现不同功能的实例:
无人分享的快乐不是真快乐,没人分担的痛苦是真痛苦。