///////////////////////////////////////////////////////////////////////////// // Name: camwindow.cpp // Purpose: wxPanel derived class to show live camera image // Author: Cesar Mauri Loba (cesar at crea-si dot com) // Modified by: // Created: // Copyright: (C) 2008-2010 Cesar Mauri Loba - CREA Software Systems // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . ///////////////////////////////////////////////////////////////////////////// // For compilers that support precompilation, includes "wx/wx.h". #include #ifdef __BORLANDC__ #pragma hdrstop #endif #ifndef WX_PRECOMP #include #include #endif #include #include #include #include "camwindow.h" #include "visiblenormroi.h" #include "wxnormroi.h" #define MIN_WIDTH 160 #define MIN_HEIGHT 144 #define VP_ALIGNMENT 4 // New event to comunicate worker and GUI DECLARE_EVENT_TYPE(wxEVT_MY_REFRESH, -1) DEFINE_EVENT_TYPE(wxEVT_MY_REFRESH) // implement message map BEGIN_EVENT_TABLE(CCamWindow, wxPanel) EVT_PAINT (CCamWindow::OnPaint) EVT_SIZE (CCamWindow::OnSize) EVT_MOTION (CCamWindow::OnEvtMotion) EVT_LEFT_DCLICK (CCamWindow::OnEvtLeftDClick) EVT_COMMAND (wxID_ANY, wxEVT_MY_REFRESH, CCamWindow::OnRecvRefresh) END_EVENT_TABLE() CCamWindow::CCamWindow() { Init(); } //CCamWindow::CCamWindow( wxWindow *parent, const wxPoint& pos, const wxSize& size ) : // wxPanel(parent, -1, pos, size, wxSIMPLE_BORDER ) CCamWindow::CCamWindow(wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style) //, const wxString& name) { Init(); Create( parent, id, pos, size, style ); //, name ); } bool CCamWindow::Create(wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style) //, const wxString& name) { return wxPanel::Create (parent, id, pos, size, style); //, name); } // asserts if registered controls found CCamWindow::~CCamWindow(void) { assert (m_ControlList.empty()); } void CCamWindow::Init() { // Get canvas width/height m_nImgWidth= 1; m_nImgHeight= 1; m_ImageShowed= true; m_AccessingImage= false; m_SharedImage.Create (1, 1); m_DisplayImage.Create (1, 1); m_prevCursor.x= 0; m_prevCursor.y= 0; } // If parent is a top level window resizes it's client area void CCamWindow::ResizeParentClientArea(int width, int height) { assert ((width % VP_ALIGNMENT)== 0); // TODO: parent should set size automatically changing own size // as a workdaround size is propagated to parent if is top level window wxWindow* pParent= GetParent(); if (pParent && pParent->IsTopLevel()) pParent->SetClientSize (width, height); } // OnSize void CCamWindow::OnSize (wxSizeEvent& event) { bool changed= false; int width = event.GetSize().GetWidth(); int height = event.GetSize().GetHeight(); // printf ("OnSize - Before. W: %d, H: %d\n", width, height); // New requested size should be aligned to 4 or 8 bytes. // So test it and force the alignment when necessary if (width % VP_ALIGNMENT) { width= width + VP_ALIGNMENT - (width % VP_ALIGNMENT); changed= true; } if (width< MIN_WIDTH) { width= MIN_WIDTH; changed= true; } if (height< MIN_HEIGHT) { height= MIN_HEIGHT; changed= true; } if (changed) { SetSize (width, height); //ResizeParentClientArea(width, height); } event.Skip (true); } // DrawCam. Called from the worker thread void CCamWindow::DrawCam (IplImage* pImg) { int convertFlags= 0; // If last image not shown yet don't update assert (pImg); if (m_ImageShowed && pImg) { m_ImageCopyMutex.Enter(); if (m_AccessingImage) { // GUI thread is processing image, don't update m_ImageCopyMutex.Leave(); return; } else { // Exclusive access to shared image m_AccessingImage= true; } m_ImageCopyMutex.Leave(); // Check that image is RGB with channel order RGB or BGR wxASSERT_MSG (pImg->nChannels== 3 && ((pImg->channelSeq[0]== 'R' && pImg->channelSeq[1]== 'G' && pImg->channelSeq[2]== 'B') || (pImg->channelSeq[0]== 'B' && pImg->channelSeq[1]== 'G' && pImg->channelSeq[2]== 'R')), _T("Wrong image format. It should be RGB or BGR") ); // // Adapt image format to show on the screen // // Allocate shared image if size changed if (pImg->width!= m_SharedImage.Width() || pImg->height!= m_SharedImage.Height()) m_SharedImage.Create (pImg->width, pImg->height, pImg->depth, "RGB", pImg->origin, pImg->align); assert (pImg->origin== 0); if (pImg->channelSeq[0]== 'B' && pImg->channelSeq[1]== 'G' && pImg->channelSeq[2]== 'R') { convertFlags|= CV_CVTIMG_SWAP_RB; // pImg->channelSeq[0]= 'R'; // pImg->channelSeq[2]= 'B'; } if (convertFlags) cvConvertImage ( pImg, m_SharedImage.ptr(), convertFlags ); else cvCopy( pImg, m_SharedImage.ptr() ); m_ImageShowed= false; // Release exclusive access to image m_AccessingImage= false; // When calling a GUI function from a thread different than // the main one (this method is usually called from a worker thread) // synchonization is needed (under GTK+ is mandatory, for example). // but it seems that under Windows no GUI Mutex is needed. Futhermore, // wxMutexGuiEnter() blocks when pull down a menu from the menu bar // For more info check WX source thread sample // As wxPostEvent is the recommended synchronization mechanism we'll use it. // Hug! wxPostEvent also blocks if main thread is processing an event :-( // This problem has been reported as being caused on gtk by the call to // wxWakeUpIdle() inside wxPostEvent. See, for instance: // http://osdir.com/ml/lib.wxwindows.general/2003-10/msg00026.html // In theory if the macro __WXGTK20__ is defined this must not happen. // Since version 2.9.0 there is another funtion to post events "wxQueueEvent" // That should be investigated wxCommandEvent event(wxEVT_MY_REFRESH); wxPostEvent(this, event); } } // OnPaint. Called on paint event void CCamWindow::OnPaint (wxPaintEvent& event) { event.Skip(); // Avoid compilation warning. This is the defaul behavior // Create and check DC // Note that In a paint event handler, the application must always create a wxPaintDC object, // even if you do not use it. Otherwise, under MS Windows, refreshing for this and other // windows will go wrong. wxPaintDC dc(this); if(!dc.IsOk()) return; // TODO: does nothing under Linux KDE if (!IsShown()) return; // Not implemented for 2.6? //if (!IsShownOnScreen()) return; // If current image already shown wait for the next frame. // Note this is also necessary to avoid program crash due a void // shared image during the initialization process if (m_ImageShowed) return; // Exclusive access to shared image m_ImageCopyMutex.Enter(); if (m_AccessingImage) { // Worker thread is processing image, don't update m_ImageCopyMutex.Leave(); return; } else { // Exclusive access to shared image m_AccessingImage= true; } m_ImageCopyMutex.Leave(); // Image size changed if (m_nImgWidth!= m_SharedImage.Width() || m_nImgHeight!= m_SharedImage.Height()) { m_nImgWidth= m_SharedImage.Width(); m_nImgHeight= m_SharedImage.Height(); ResizeParentClientArea(m_nImgWidth, m_nImgHeight); //NotifyResizeParent(); } // Allocate DisplayImage when needed int vpWidth, vpHeight; GetSize(&vpWidth, &vpHeight); if (vpWidth % 4) vpWidth= vpWidth + 4 - (vpWidth % 4); if (vpWidth!= m_DisplayImage.Width() || vpHeight!= m_DisplayImage.Height()) { // Allocate shared image if size changed m_DisplayImage.Create (vpWidth, vpHeight, m_SharedImage.Depth(), "RGB", m_SharedImage.Origin(), m_SharedImage.Align()); } // Scale image cvResize( m_SharedImage.ptr(), m_DisplayImage.ptr(), CV_INTER_NN ); // Working with shared image finished m_AccessingImage= false; // Draw controls m_ListMutex.Enter(); TWXNormROIListIterator i; for(i= m_ControlList.begin(); i != m_ControlList.end(); ++i) { (*i).OnPaint (&m_DisplayImage); } m_ListMutex.Leave(); // To wxWidgets unsigned char *rawData; CvSize roiSize; int step = 0; cvGetRawData( m_DisplayImage.ptr(), &rawData, &step, &roiSize ); wxImage wxImg= wxImage(vpWidth, vpHeight, rawData, true ); // convert to bitmap to be used by the window to draw m_Bitmap= wxBitmap( wxImg ); wxCoord x, y, width, height; dc.GetClippingBox( &x, &y, &width, &height ); dc.DrawBitmap( m_Bitmap, x, y ); m_ImageShowed= true; } //CCamWindow::TWXNormROIListIterator CCamWindow::FindControl (CVisibleNormROI* pVisibleNormROI) bool CCamWindow::FindControl (CVisibleNormROI* pVisibleNormROI, CCamWindow::TWXNormROIListIterator& it) { // TWXNormROIListIterator it; for(it= m_ControlList.begin(); it != m_ControlList.end(); ++it) { if ((*it).GetVisibleNormROI() == pVisibleNormROI) return true; } //return NULL; return false; } bool CCamWindow::RegisterControl (CVisibleNormROI* pVisibleNormROI) { assert (pVisibleNormROI); m_ListMutex.Enter(); TWXNormROIListIterator it; if (FindControl (pVisibleNormROI, it)) { // Control already registered m_ListMutex.Leave(); return false; } m_ControlList.push_back (CWXNormROI(pVisibleNormROI, this)); m_ListMutex.Leave(); return true; } bool CCamWindow::UnregisterControl (CVisibleNormROI* pVisibleNormROI) { assert (pVisibleNormROI); m_ListMutex.Enter(); TWXNormROIListIterator it; //= FindControl (pVisibleNormROI); if (!FindControl (pVisibleNormROI, it)) { // Control not found m_ListMutex.Leave(); return false; } m_ControlList.erase (it); m_ListMutex.Leave(); return true; } void CCamWindow::OnEvtMotion ( wxMouseEvent& event ) { TWXNormROIListIterator i; m_ListMutex.Enter(); for(i= m_ControlList.begin(); i != m_ControlList.end(); ++i) { if ((*i).OnEvtMotion (event, m_prevCursor)) break; } m_ListMutex.Leave(); m_prevCursor= event.GetPosition (); event.Skip(); } void CCamWindow::OnEvtLeftDClick ( wxMouseEvent& event ) { TWXNormROIListIterator i; m_ListMutex.Enter(); for(i= m_ControlList.begin(); i != m_ControlList.end(); ++i) { if ((*i).OnEvtLeftDClick (event)) break; } m_ListMutex.Leave(); event.Skip(); } void CCamWindow::OnRecvRefresh( wxCommandEvent& WXUNUSED(event) ) { Refresh(false); }