/////////////////////////////////////////////////////////////////////////////// // Name: samples/cmpi/cmpi.cpp // Purpose: Compare images with 'fuzzy' options // Author: Ray Johnston // Modified by: // Created: 2006 // RCS-ID: $Id:$ // Copyright: (c) 2006-2021 Artifex Software Inc. // License: AFPL /////////////////////////////////////////////////////////////////////////////// #include // For compilers that support precompilation, includes "wx/wx.h". #include "wx/wxprec.h" #ifdef __BORLANDC__ #pragma hdrstop #endif #ifndef WX_PRECOMP #include "wx/wx.h" #endif #include "wx/defs.h" #include "wx/image.h" #include "wx/file.h" #include "wx/filename.h" #include "wx/filefn.h" #include "wx/mstream.h" #include "wx/wfstream.h" #include "wx/pen.h" #include "wx/progdlg.h" #include "wx/msgdlg.h" // derived classes class MyFrame; class MyApp; // MyCanvas class MyCanvas: public wxScrolledWindow { public: MyCanvas() {} MyCanvas( wxWindow *parent, wxWindowID, const wxPoint &pos, const wxSize &size ); ~MyCanvas(); void OnPaint( wxPaintEvent &event ); bool ProcessArgs(int argc, wxChar **argv, wxStatusBar *S); void update_bitmap(); void goto_diff_rect_focus(); bool pixel_differs( unsigned char **base_rows, unsigned char **compare_rows, int pixel, int i_pix_window, int color_tolerance); void prune_pending_list( int row ); bool load_image_files( wxChar *basefilename, wxChar *comparefilename ); wxImage base_image, compare_image, diff_image, *current_image, *prev_image; wxStatusBar *frame_status_bar; wxChar *basefn, *comparefn; wxBitmap *current_bitmap; float current_zoom, prev_zoom; int i_pix_window; int i_rect_separation; int *neighbor_pix, *neighbor_row, neighbor_count; int color_tolerance; int diff_count; bool batch_mode; unsigned char *alpha; bool mask_on, prev_mask; bool highlight_on; typedef struct diff_rect_s { int left, top, right, bottom; struct diff_rect_s *next, *prev; } diff_rect; diff_rect *diff_list_head, *diff_list_tail, *diff_list_pending; diff_rect *diff_rect_focus; int diff_list_count, i_diff_rect_focus; protected: private: DECLARE_DYNAMIC_CLASS(MyCanvas) DECLARE_EVENT_TABLE() }; // MyFrame class MyFrame: public wxFrame { public: MyFrame(); bool ProcessArgs(int argc, wxChar **argv); void OnAbout( wxCommandEvent &event ); void OnHelp( wxCommandEvent &event ); void OnNewImages( wxCommandEvent &event ); void OnQuit( wxCommandEvent &event ); void OnHiLiteToggle( wxCommandEvent &event ); void OnMaskToggle( wxCommandEvent &event ); void OnView_Image( wxCommandEvent &event ); void OnGoTo( wxCommandEvent &event ); void OnZoom( wxCommandEvent &event ); void OnSettings( wxCommandEvent &event ); MyCanvas *m_canvas; wxStatusBar *status_bar; private: DECLARE_DYNAMIC_CLASS(MyFrame) DECLARE_EVENT_TABLE() }; // MyApp class MyApp: public wxApp { public: virtual bool OnInit(); }; // main program IMPLEMENT_APP(MyApp) // MyCanvas IMPLEMENT_DYNAMIC_CLASS(MyCanvas, wxScrolledWindow) BEGIN_EVENT_TABLE(MyCanvas, wxScrolledWindow) EVT_PAINT(MyCanvas::OnPaint) END_EVENT_TABLE() MyCanvas::MyCanvas( wxWindow *parent, wxWindowID id, const wxPoint &pos, const wxSize &size ) : wxScrolledWindow( parent, id, pos, size, wxSUNKEN_BORDER ) { SetBackgroundColour(* wxWHITE); current_bitmap = (wxBitmap *)NULL; current_image = (wxImage *)NULL; prev_image = current_image; frame_status_bar = (wxStatusBar *)NULL; neighbor_count = 0; neighbor_pix = neighbor_row = NULL; } // Called when zoom factor changes to create bitmap of correct size void MyCanvas::update_bitmap() { int current_scroll_x, current_scroll_y, sx, sy, client_width_pixels, client_height_rows; if ((current_image == prev_image) && (current_zoom == prev_zoom) && (mask_on == prev_mask)) return; if (current_bitmap) { delete current_bitmap; } GetClientSize( &client_width_pixels, &client_height_rows ); GetViewStart( ¤t_scroll_x, ¤t_scroll_y ); /* Keep center of focus constant as we zoom in and out */ /* These calculations could be chained, but this is easier for debugging */ sx = current_scroll_x + ( client_width_pixels / 2 ); sy = current_scroll_y + ( client_height_rows / 2 ); sx = sx * ( current_zoom / prev_zoom ); sy = sy * ( current_zoom / prev_zoom ); sx = sx - ( client_width_pixels / 2 ); sy = sy - ( client_height_rows / 2 ); sx = ( sx < 0 ) ? 0 : sx; sy = ( sy < 0 ) ? 0 : sy; prev_image = current_image; prev_zoom = current_zoom; prev_mask = mask_on; current_bitmap = new wxBitmap( current_image->Scale( current_image->GetWidth() * current_zoom, current_image->GetHeight() * current_zoom) ); SetScrollbars( 1, 1, current_bitmap->GetWidth(), current_bitmap->GetHeight(), sx, sy ); wxChar *fn; char *hdr; if (current_image == &base_image) { hdr = "Base Image: "; fn = basefn; } else if (current_image == &compare_image) { hdr = "Compare Image: "; fn = comparefn; } else { hdr = "Diff Image"; fn = _T(""); } #if wxUSE_STATUSBAR frame_status_bar->SetStatusText(wxString(hdr, wxConvUTF8), 0); frame_status_bar->SetStatusText(wxString(fn, wxConvUTF8), 1); #endif // wxUSE_STATUSBAR Refresh(); } void MyCanvas::goto_diff_rect_focus( ) { int scroll_units_pixels, scroll_units_rows, client_width_pixels, client_height_rows; /* make the center of the current diff rect be at the center of the window */ int i_rect_center_pixel = ( diff_rect_focus->left + diff_rect_focus->right ) / 2; int i_rect_center_row = ( diff_rect_focus->top + diff_rect_focus->bottom ) / 2; GetScrollPixelsPerUnit( &scroll_units_pixels, &scroll_units_rows ); GetClientSize( &client_width_pixels, &client_height_rows ); int scroll_to_pixel = ( (current_zoom * i_rect_center_pixel) - ( client_width_pixels / 2 ) ); int scroll_to_row = ( current_zoom * i_rect_center_row - ( client_height_rows / 2 ) ); Scroll( scroll_to_pixel / scroll_units_pixels, scroll_to_row / scroll_units_rows ); Refresh(); } #define MAX_PIXWINDOW 8 bool MyCanvas::pixel_differs( unsigned char **base_rows, unsigned char **compare_rows, int pixel, int i_pw, int color_tolerance) { /* rows are pointers to the data to be compared */ /* the current pixel is in ***_rows[i_pw][3*i_pw] */ /* color_tolerance is an absolute diff in each channel R, G, B */ int p3 = (pixel * 3); /* center pixel offset in row */ int i; bool base_differs = true; /* default to differing */ /* This row in _rows is i_pw */ /* Check pixels from the center out */ unsigned char *cp, *bp = base_rows [i_pw] + p3; /* reference is base */ for (i=0; inext != NULL) ) { diff_pending_scan = diff_pending_scan_base->next; while (diff_pending_scan != NULL) { if ( ( diff_pending_scan_base->right >= (diff_pending_scan->left - i_rect_separation) ) && ( diff_pending_scan_base->left <= (diff_pending_scan->right + i_rect_separation) ) ) { /* merge the _scan rect into the _base rect and delete the _scan element */ diff_pending_scan_base->left = diff_pending_scan_base->left < diff_pending_scan->left ? diff_pending_scan_base->left : diff_pending_scan->left; diff_pending_scan_base->right = diff_pending_scan_base->right > diff_pending_scan->right ? diff_pending_scan_base->right : diff_pending_scan->right; diff_pending_scan_base->top = diff_pending_scan_base->top < diff_pending_scan->top ? diff_pending_scan_base->top : diff_pending_scan->top; diff_pending_scan_base->bottom = diff_pending_scan_base->bottom > diff_pending_scan->bottom ? diff_pending_scan_base->bottom : diff_pending_scan->bottom; /* delete the diff_pending_scan rectangle from the list */ diff_pending_scan->prev->next = diff_pending_scan->next; if (diff_pending_scan->next != NULL) diff_pending_scan->next->prev = diff_pending_scan->prev; diff_rect *prev = diff_pending_scan->prev; free( diff_pending_scan ); diff_pending_scan = prev; } diff_pending_scan = diff_pending_scan->next; } diff_pending_scan_base = diff_pending_scan_base->next; } /* Remove any pending differences that will be too far above the current row */ /* Before adding it, we need to make sure we can't simply anneal to an existing diff */ diff_pending_scan = diff_list_pending; while (diff_pending_scan != NULL) { if (diff_pending_scan->bottom <= (row - i_rect_separation)) { bool diff_pending_merged = false; diff_rect *diff_list_scan = diff_list_head; while (diff_list_scan != NULL) { if ( ( diff_list_scan->bottom >= diff_pending_scan->top - i_rect_separation ) && ( ( diff_list_scan->right >= (diff_pending_scan->left - i_rect_separation) ) && ( diff_list_scan->left <= (diff_pending_scan->right + i_rect_separation) ) ) ) { /* merge the diff_pending_scan rect into the diff_list_scan rect */ diff_list_scan->left = diff_list_scan->left < diff_pending_scan->left ? diff_list_scan->left : diff_pending_scan->left; diff_list_scan->right = diff_list_scan->right > diff_pending_scan->right ? diff_list_scan->right : diff_pending_scan->right; diff_list_scan->top = diff_list_scan->top < diff_pending_scan->top ? diff_list_scan->top : diff_pending_scan->top; diff_list_scan->bottom = diff_list_scan->bottom > diff_pending_scan->bottom ? diff_list_scan->bottom : diff_pending_scan->bottom; /* delete the diff_pending_scan rectangle from the list */ if (diff_pending_scan->prev != NULL) diff_pending_scan->prev->next = diff_pending_scan->next; else diff_list_pending = diff_pending_scan->next; if (diff_pending_scan->next != NULL) diff_pending_scan->next->prev = diff_pending_scan->prev; diff_rect *next = diff_pending_scan->next; free( diff_pending_scan ); diff_pending_scan = next; diff_pending_merged = true; break; } diff_list_scan = diff_list_scan->next; } if ( ! diff_pending_merged ) { /* move this rect from the pending list to the end of the diff_list */ if (diff_pending_scan->prev != NULL) /* not the first on the pending list */ diff_pending_scan->prev->next = diff_pending_scan->next; else diff_list_pending = diff_pending_scan->next; if (diff_pending_scan->next != NULL) /* wasn't the last on the pending list */ diff_pending_scan->next->prev = diff_pending_scan->prev; /* now put this on the diff_list at the tail */ if (diff_list_tail == NULL) { diff_list_tail = diff_list_head = diff_pending_scan; diff_pending_scan->prev = NULL; } else { diff_list_tail->next = diff_pending_scan; diff_pending_scan->prev = diff_list_tail; diff_list_tail = diff_pending_scan; } diff_pending_scan = diff_pending_scan->next; diff_list_tail->next = NULL; diff_list_count++; /* finished moving a rect from 'pending' to the diff_list */ } /* end if ! diff_pending_merged */ } else { diff_pending_scan = diff_pending_scan->next; } /* end if diff_pending rect ends too far above current row */ } /* end while diff_pending_scan != NULL */ } bool MyCanvas::load_image_files( wxChar *basefilename, wxChar *comparefilename ) { char str[256]; int i, percent_done = 0; wxProgressDialog *wxPD = new wxProgressDialog(wxT("Loading and Analyzing Images"), wxT("________________________________________________________________"), 100, 0, wxPD_AUTO_HIDE ); sprintf(str, "Loading baseline image file: %-64s", basefilename); wxPD->Update( 0, wxString(str, wxConvUTF8)); if ( ! base_image.LoadFile( basefilename, wxBITMAP_TYPE_ANY ) ) { wxLogError(wxT("Can't load baseline image")); return false; } else { sprintf(str, "Loading compare image file: %-64s", basefilename); wxPD->Update( 25, wxString(str, wxConvUTF8)); if ( ! compare_image.LoadFile( comparefilename, wxBITMAP_TYPE_ANY )){ wxLogError(wxT("Can't load compare image")); return false; } } if (compare_image.GetWidth() != base_image.GetWidth() || compare_image.GetHeight() != base_image.GetHeight()) { wxLogError(wxT("compare image geometry doesn't match base image")); return false; } basefn = basefilename; comparefn = comparefilename; current_image = &base_image; base_image.SetAlpha (); compare_image.SetAlpha (); memset (base_image.GetAlpha(), 255, base_image.GetWidth() * base_image.GetHeight()); memset (compare_image.GetAlpha(), 255, base_image.GetWidth() * base_image.GetHeight()); // Compute 'fit' zoom factor int frameW, frameH; DoGetClientSize(&frameW, &frameH); float WRatio = (float)frameW / (float)(base_image.GetWidth()); float HRatio = (float)frameH / (float)(base_image.GetHeight()); current_zoom = WRatio < HRatio ? WRatio : HRatio; prev_zoom = current_zoom; /* save the value for scroll positioning when zooming */ // Gather differences unsigned char *base_data = base_image.GetData(); unsigned char *compare_data = compare_image.GetData(); bool differs; int pixel, row; int image_width = base_image.GetWidth(); int image_height = base_image.GetHeight(); unsigned char *diff_data = (unsigned char *)malloc(image_width * image_height *3); unsigned char *p_base_rows[MAX_PIXWINDOW]; unsigned char *p_compare_rows[MAX_PIXWINDOW]; diff_list_head = diff_list_tail = diff_list_pending = (diff_rect *)NULL; diff_rect_focus = NULL; i_diff_rect_focus = 0; diff_list_count = 0; alpha = (unsigned char *)calloc(image_height * image_width, 1); mask_on = false; highlight_on = false; prev_mask = mask_on; memset(diff_data, 255, image_width * image_height * 3); memset(alpha, 30, image_width * image_height); /* generate the list of neighbors dynamically */ neighbor_count = 1; for (i=1; i <= i_pix_window; i++) neighbor_count += 8 * i; neighbor_pix = (int *)calloc(neighbor_count, sizeof(int)); neighbor_row = (int *)calloc(neighbor_count, sizeof(int)); neighbor_count = 1; for (i=1; i <= i_pix_window; i++) { for (row = -i; row < i; row++, neighbor_count++) { /* lower right, up */ neighbor_pix[neighbor_count] = i; neighbor_row[neighbor_count] = row; } for (pixel = i; pixel > -i; pixel--, neighbor_count++) { /* upper right, left */ neighbor_pix[neighbor_count] = pixel; neighbor_row[neighbor_count] = i; } for (row = i; row > -i; row--, neighbor_count++) { /* lower right, up */ neighbor_pix[neighbor_count] = -i; neighbor_row[neighbor_count] = row; } for (pixel = -i; pixel < i; pixel++, neighbor_count++) { /* upper right, left */ neighbor_pix[neighbor_count] = pixel; neighbor_row[neighbor_count] = -i; } } for (row=0; row < image_height; row++) { if ( 25 + ((row * 75)/ image_height) > percent_done ) { /* update progress dialog */ percent_done = 25 + ( ( row * 75 )/ image_height ); sprintf(str, " Analyzing Differences: %d %%", percent_done); wxPD->Update( percent_done, wxString(str, wxConvUTF8)); } int i; for (i = -i_pix_window; i <= i_pix_window; i++) { int row_offset = (row + i) * image_width * 3; p_base_rows[i+i_pix_window] = base_data + row_offset; p_compare_rows[i+i_pix_window] = compare_data + row_offset; } for (pixel=0; pixel < image_width; pixel++) { if ( (pixel < i_pix_window) || (pixel >= (image_width - i_pix_window)) || (row < i_pix_window) || (row >= (image_height - i_pix_window)) ) { /* Need to copy a range of data to our "window" filling in rows and */ /* columns outside of the image */ // ------------------------------------------------------------- // // // THIS SECTION T.B.I. BUT IT ONLY AFFECTS DIFFERENCES AT THE // EDGE OF THE IMAGES, SO WE LEAVE IT FOR LATER. // // The approach is to copy the data to a 'window' of rows that // is 1+2*i_pix_window square and pass those row pointers to // pixel_differs() /* Now call the comparator on the window we just filled in */ differs = false; } else { differs = pixel_differs( p_base_rows, p_compare_rows, pixel, i_pix_window, color_tolerance); } if (differs) { unsigned char *d = diff_data + 3 * (pixel + (row * image_width)); d[0] = d[1] = d[2] = 0; alpha[pixel + (row * image_width)] = 255; diff_count++; /* Now maintain the list of diff_rects */ bool diff_processed = false; diff_rect *diff_rect_scan = diff_list_pending; while (diff_rect_scan != NULL) { /* we already know that any rects still on the pending list are */ /* within the row i_rect_separation distance -- just check the pixel distance */ if ( (pixel >= (diff_rect_scan->left - i_rect_separation)) && (pixel <= (diff_rect_scan->right + i_rect_separation)) ) { /* update thebottom to the current row. Set right and left if past current pixel */ diff_rect_scan->right = (pixel > diff_rect_scan->right) ? pixel : diff_rect_scan->right ; diff_rect_scan->left = (pixel < diff_rect_scan->left) ? pixel : diff_rect_scan->left ; diff_rect_scan->bottom = row; diff_processed = true; break; } /* if not at end of list, advance to next */ if (diff_rect_scan->next == NULL) { break; /* exit loop with _scan at tail of list */ /* the above exit at end of list will have diff_processed == false */ } diff_rect_scan = diff_rect_scan->next; } if ( ! diff_processed ) { /* This difference wasn't on the 'pending' list -- add a new rect at the head */ diff_rect *diff_rect_new = (diff_rect *)calloc(1, sizeof(diff_rect)); diff_rect_new->right = diff_rect_new->left = pixel; diff_rect_new->bottom = diff_rect_new->top = row; diff_rect_new->next = diff_list_pending; if (diff_list_pending != NULL) { diff_list_pending->prev = diff_rect_new; /* link back to new head of list */ } diff_list_pending = diff_rect_new; /* new head of list */ } } /* finished handling a pixel that differs */ } /* end of pixels in a row */ prune_pending_list( row ); } /* end of rows in image */ /* Move any rects still pending to the diff_list */ prune_pending_list( row + i_rect_separation + 1 ); /* far enoungh beyond all pending */ wxPD->Close(); wxPD->Destroy(); diff_image.Create(image_width, image_height, diff_data, NULL, false); free(neighbor_pix); free(neighbor_row); return true; } bool MyCanvas::ProcessArgs(int argc=0, wxChar **argv=NULL, wxStatusBar *status_bar=NULL) { int atarg = 1; char *logfilename = "cmpi.log"; frame_status_bar = status_bar; i_pix_window = 3; /* Default is sort of loose */ color_tolerance = 5; /* also for color variations */ diff_count = 0; i_rect_separation = 3 * i_pix_window; batch_mode = false; while ((atarg < argc) && (argv[atarg][0] == '-')) { /* process options */ switch(argv[atarg][1]) { case 'b': batch_mode = true; break; case 'c': sscanf((char *)argv[atarg]+2, "%d", &color_tolerance); if (color_tolerance < 0) color_tolerance = 0; break; case 'l': if (argv[atarg]+2 != 0) logfilename = (char *)argv[atarg]+2; else { atarg++; logfilename = (char *)argv[atarg]; } break; case 'r': sscanf((char *)argv[atarg]+2, "%d", &i_rect_separation); if (i_rect_separation < 0) i_rect_separation = 0; break; case 'w': sscanf((char *)argv[atarg]+2, "%d", &i_pix_window); if (i_pix_window < 0) i_pix_window = 0; break; default: wxLogError(wxT("Unknown option")); } atarg++; } if (argc - atarg < 2) { wxLogError(wxT("Two filename arguments are required")); return false; } if ( ! load_image_files( argv[atarg], argv[atarg+1] ) ) return false; if (batch_mode) { char str[256], basename[256]; wxFile logFile; if (wxFileExists(logfilename)) logFile.Open(logfilename, wxFile::write_append); else logFile.Create(logfilename); logFile.SeekEnd(); /* find the "basename .p?m" from the base image name */ int end = strlen(argv[atarg]) - 1; while ( argv[atarg][end--] != '.' && end > 0 ) ; if ( end == 0 ) end = strlen(argv[atarg]) - 1; int start = end++; while ( argv[atarg][start] != '/' && argv[atarg][start] != '\\' && argv[atarg][start] != ':' && start > 0 ) start--; strncpy(basename, argv[atarg]+start, end-start); basename[end-start] = 0; sprintf(str, "%s\tDifference count: %d\n", basename, diff_count); logFile.Write(wxString(str, wxConvUTF8)); logFile.Close(); return false; } #if wxUSE_STATUSBAR char str[32]; sprintf( str, "Rect Count: %-10d", diff_list_count); frame_status_bar->SetStatusText(wxString(str, wxConvUTF8), 2); sprintf( str, "Diff Count: %-10d", diff_count); frame_status_bar->SetStatusText(wxString(str, wxConvUTF8), 3); #endif // wxUSE_STATUSBAR update_bitmap(); return true; } MyCanvas::~MyCanvas() { base_image.Destroy(); compare_image.Destroy(); delete current_bitmap; } void MyCanvas::OnPaint( wxPaintEvent &WXUNUSED(event) ) { wxPaintDC dc( this ); PrepareDC( dc ); if (current_bitmap && current_bitmap->Ok()) { diff_rect *diff_rect_scan; dc.DrawBitmap( *current_bitmap, 0, 0 ); /* now outline the difference rects */ if ( highlight_on ) { dc.SetBrush( *wxTRANSPARENT_BRUSH ); for (diff_rect_scan = diff_list_head; diff_rect_scan != NULL; diff_rect_scan = diff_rect_scan->next) { int w = current_zoom * (diff_rect_scan->right - diff_rect_scan->left + 1); int h = current_zoom * (diff_rect_scan->bottom - diff_rect_scan->top + 1); bool focus = diff_rect_scan == diff_rect_focus; dc.SetPen( wxPen( focus ? *wxBLUE : *wxRED, focus ? 4 : 2, wxSOLID ) ); dc.DrawRectangle( current_zoom * diff_rect_scan->left, current_zoom * diff_rect_scan->top, (w <= 0) ? 1 : w, (h <= 0) ? 1 : h ); } /* end for diff_rect_scan */ } /* end if highlight_on */ } } // MyFrame enum { ID_QUIT = wxID_EXIT, ID_ABOUT = wxID_ABOUT, ID_HELP = wxID_HELP, ID_NEW = 100, ID_BASE = 101, ID_COMPARE = 102, ID_DIFF = 103, ID_HILITE = 104, ID_MASK = 105, ID_GOTO_NEXT = 106, ID_GOTO_PREV = 107, ID_ZOOM_FIT = 108, ID_ZOOM_1 = 109, ID_ZOOM_IN = 110, ID_ZOOM_OUT = 111, ID_PIXEL_WINDOW = 112, ID_COLOR_TOLERANCE = 113, }; IMPLEMENT_DYNAMIC_CLASS( MyFrame, wxFrame ) BEGIN_EVENT_TABLE(MyFrame,wxFrame) EVT_MENU (ID_ABOUT, MyFrame::OnAbout) EVT_MENU (ID_HELP, MyFrame::OnHelp) EVT_MENU (ID_QUIT, MyFrame::OnQuit) EVT_MENU (ID_NEW, MyFrame::OnNewImages) EVT_MENU (ID_BASE, MyFrame::OnView_Image) EVT_MENU (ID_COMPARE, MyFrame::OnView_Image) EVT_MENU (ID_DIFF, MyFrame::OnView_Image) EVT_MENU (ID_HILITE, MyFrame::OnHiLiteToggle) EVT_MENU (ID_MASK, MyFrame::OnMaskToggle) EVT_MENU (ID_GOTO_NEXT, MyFrame::OnGoTo) EVT_MENU (ID_GOTO_PREV, MyFrame::OnGoTo) EVT_MENU (ID_ZOOM_FIT, MyFrame::OnZoom) EVT_MENU (ID_ZOOM_1, MyFrame::OnZoom) EVT_MENU (ID_ZOOM_IN, MyFrame::OnZoom) EVT_MENU (ID_ZOOM_OUT, MyFrame::OnZoom) EVT_MENU (ID_PIXEL_WINDOW, MyFrame::OnSettings) EVT_MENU (ID_COLOR_TOLERANCE, MyFrame::OnSettings) END_EVENT_TABLE() MyFrame::MyFrame() : wxFrame( (wxFrame *)NULL, wxID_ANY, _T("cmpi - fuzzy image comparator"), wxPoint(20,20), wxSize(1200,900) ) { status_bar = (wxStatusBar *)NULL; wxMenuBar *menu_bar = new wxMenuBar(); wxMenu *menuFile = new wxMenu; menuFile->Append( ID_NEW, _T("&Open Image Files...\tCtrl-O")); menuFile->AppendSeparator(); menuFile->Append( ID_QUIT, _T("E&xit\tCtrl-Q")); menu_bar->Append(menuFile, _T("&File")); wxMenu *menuView = new wxMenu; menuView->Append( ID_BASE, _T("&Base Image\tb")); menuView->Append( ID_COMPARE, _T("&Compare Image\tc")); menuView->Append( ID_DIFF, _T("&Diff Image\td")); menuView->AppendSeparator(); menuView->Append( ID_HILITE, _T("&Highlight Toggle\th")); menuView->Append( ID_MASK, _T("&Mask Toggle Image\tm")); menuView->AppendSeparator(); menuView->Append( ID_GOTO_NEXT, _T("&Next Difference\tn")); menuView->Append( ID_GOTO_PREV, _T("&Previous Difference\tp")); menuView->AppendSeparator(); menuView->Append( ID_ZOOM_FIT, _T("Zoom &Fit in Window\tf")); menuView->Append( ID_ZOOM_1, _T("Zoom &1:1\t1")); menuView->Append( ID_ZOOM_IN, _T("Zoom &In\t+")); menuView->Append( ID_ZOOM_OUT, _T("Zoom &Out\t-")); menu_bar->Append(menuView, _T("&View")); wxMenu *menuSettings = new wxMenu; menuSettings->Append( ID_PIXEL_WINDOW, _T("Pixel &Window ...\tCtrl-W")); menuSettings->Append( ID_COLOR_TOLERANCE, _T("Color &Tolerance...\tCtrl-T")); menu_bar->Append(menuSettings, _T("&Settings")); wxMenu *menuHelp = new wxMenu; menuHelp->Append( ID_ABOUT, _T("&About...")); menuHelp->Append( ID_HELP, _T("&Help\tF1")); menu_bar->Append(menuHelp, _T("&Help")); SetMenuBar( menu_bar ); #if wxUSE_STATUSBAR status_bar = CreateStatusBar(4); int widths[] = { 150, -1, 200, 200 }; SetStatusWidths( 4, widths ); #endif // wxUSE_STATUSBAR m_canvas = new MyCanvas( this, wxID_ANY, wxPoint(0,0), wxSize(10,10) ); } bool MyFrame::ProcessArgs(int argc, wxChar **argv) { if (!m_canvas->ProcessArgs(argc, argv, status_bar)) Close(); return true; } void MyFrame::OnQuit( wxCommandEvent &WXUNUSED(event) ) { Close( true ); } void MyFrame::OnAbout( wxCommandEvent &WXUNUSED(event) ) { (void)wxMessageBox( _T("cmpi - Image Comparison tool with fuzzy compare -- Version 0.9\n") _T("Artifex Software (c) 2006"), _T("Written by Ray Johnston"), wxICON_INFORMATION | wxOK ); } void MyFrame::OnHelp( wxCommandEvent &WXUNUSED(event) ) { (void)wxMessageBox( _T( "usage: cmpi [-w#] [-c#] [-r#] [-l logfilename] baseline.ppm compare.ppm\n" "\n" " w# is pixel distance from the reference location 0 to 3\n" " c# is the color tolerance (same for all components)\n" " r# is the max rect separation used to collect adjacent\n" " or nearby pixels into rectangular regions\n" "\n" "Hotkeys are:\n" "\n" " b display baseline image\n" " c display compare image\n" " d display diff image\n" " f fit page to window\n" " h highlight on/off toggle\n" " m mask on/off toggle (only for base or compare images)\n" " n next difference rectangle - center on screen, highlight blue\n" " p previous difference rectangle - center on screen, highlight blue\n" " 1 set zoom to 1:1\n" " - zoom out\n" " + zoom in\n" "\n" " ctrl-q quit\n" ), _T("Usage:"), wxICON_INFORMATION | wxOK ); } void MyFrame::OnNewImages( wxCommandEvent &WXUNUSED(event) ) { #if wxUSE_FILEDLG unsigned int i; wxString filename = wxFileSelector(_T("Select base image file")); if ( !filename ) return; wxChar *basefilename = (wxChar *)calloc(filename.Len(), sizeof(wxChar)); for ( i=0; i < filename.Len(); i++) basefilename[i] = filename.GetChar(i); basefilename[i] = 0; filename = wxFileSelector(_T("Select compare image file")); if ( !filename ) return; wxChar *comparefilename = (wxChar *)calloc(filename.Len(), sizeof(wxChar)); for ( i=0; i < filename.Len(); i++) comparefilename[i] = filename.GetChar(i); comparefilename[i] = 0; m_canvas->load_image_files(basefilename, comparefilename); #endif // wxUSE_FILEDLG } void MyFrame::OnView_Image( wxCommandEvent &event ) { MyCanvas *m = m_canvas; switch (event.GetId()) { case ID_COMPARE: m->current_image = &m->compare_image; break; case ID_DIFF: m->current_image = &m->diff_image; break; case ID_BASE: default: m->current_image = &m->base_image; break; } m->update_bitmap(); } void MyFrame::OnMaskToggle( wxCommandEvent &WXUNUSED(event) ) { MyCanvas *m = m_canvas; if ((m->current_image == &m->base_image) || (m->current_image == &m->compare_image)) { if ( ! m->mask_on ) { memcpy(m->base_image.GetAlpha(), m->alpha, m->base_image.GetWidth() * m->base_image.GetHeight()); memcpy(m->compare_image.GetAlpha(), m->alpha, m->base_image.GetWidth() * m->base_image.GetHeight()); m->mask_on = true; } else { memset (m->base_image.GetAlpha(), 255, m->base_image.GetWidth() * m->base_image.GetHeight()); memset (m->compare_image.GetAlpha(), 255, m->base_image.GetWidth() * m->base_image.GetHeight()); m->mask_on = false; } m->update_bitmap(); } } void MyFrame::OnHiLiteToggle( wxCommandEvent &WXUNUSED(event) ) { m_canvas->highlight_on = ! m_canvas->highlight_on; m_canvas->Refresh(); } void MyFrame::OnZoom( wxCommandEvent &event ) { MyCanvas *m = m_canvas; double log_2, log2_zoom; float WRatio, HRatio; int frameW, frameH; switch (event.GetId()) { case ID_ZOOM_IN: log_2 = log(2.0); log2_zoom = log(m->current_zoom) / log_2; log2_zoom = ceil(log2_zoom + 0.00001); m->current_zoom = exp(log2_zoom * log_2); break; case ID_ZOOM_OUT: log_2 = log(2.0); log2_zoom = log(m->current_zoom) / log_2; log2_zoom = floor(log2_zoom - 0.00001); m->current_zoom = exp(log2_zoom * log_2); break; case ID_ZOOM_FIT: // Compute 'fit' zoom factor DoGetClientSize(&frameW, &frameH); WRatio = (float)frameW / (float)(m->base_image.GetWidth()); HRatio = (float)frameH / (float)(m->base_image.GetHeight()); m->current_zoom = WRatio < HRatio ? WRatio : HRatio; break; case ID_ZOOM_1: default: m->current_zoom = 1.0; } // Now scale the bitmap from the image m->update_bitmap(); } void MyFrame::OnGoTo( wxCommandEvent &event ) { MyCanvas *m = m_canvas; if (m->diff_rect_focus == NULL) { m->diff_rect_focus = m->diff_list_head; m->i_diff_rect_focus = 1; /* first rect */ } else { switch (event.GetId()) { case ID_GOTO_NEXT: if (m->diff_rect_focus->next != NULL) { m->diff_rect_focus = m->diff_rect_focus->next; m->i_diff_rect_focus++; } break; case ID_GOTO_PREV: if (m->diff_rect_focus->prev != NULL) { m->diff_rect_focus = m->diff_rect_focus->prev; m->i_diff_rect_focus--; } break; default: /* 't' Center on current focus rect */ break; } } #if wxUSE_STATUSBAR char str[32]; sprintf( str, "At: %d / %-10d", m->i_diff_rect_focus, m->diff_list_count); status_bar->SetStatusText(wxString(str, wxConvUTF8), 2); #endif // wxUSE_STATUSBAR m->goto_diff_rect_focus(); /* Make this one the center */ } void MyFrame::OnSettings( wxCommandEvent &event ) { MyCanvas *m = m_canvas; #if 0000 /* TBI */ wxMessageDialog *settings_dlg; settings_dlg = new WxMessageDialog(this, _T("Change Difference Settings\n"), _T("Pixel Window Color Tolerance\n"), wxCANCEL | wxOK ); m_spin_pix_win = new wxSpinButton( settings_dlg, ID_SPIN_PIX_WIN, wxPoint( 20, 40 ) ); m_spin_pix_win->SetRange( 0, MAX_PIXWINDOW ); m_spin_pix_win->SetValue( m->i_pix_window ); m_spin_col_tol = new wxSpinButton( settings_dlg, ID_SPIN_COL_TOL, wxPoint( 20, 80 ) ); m_spin_col_tol->SetRange( 0, 255 ); m_spin_col_tol->SetValue( m->color_tolerance ); switch (event.GetId()) { case ID_PIXEL_WINDOW: settings_dlg.SetFocus( m_spin_pix_win ); break; case ID_COLOR_TOLERANCE: settings_dlg.SetFocus( m_spin_col_tol ); break; default: break; } /* on "Find Diffs" BUtton */ if ( m_spin_window.GetValue != m->i_pix_window || m_spin_tolerance.GetValue != m->color_tolerance ) #endif // 0000 } //----------------------------------------------------------------------------- // MyApp //----------------------------------------------------------------------------- bool MyApp::OnInit() { #if wxUSE_PNM wxImage::AddHandler( new wxPNMHandler ); #endif MyFrame *frame = new MyFrame(); frame->Show( true ); return frame->ProcessArgs(MyApp::argc, MyApp::argv); }