diff options
Diffstat (limited to 'pcl/tools/cmpi/cmpi.cpp')
-rw-r--r-- | pcl/tools/cmpi/cmpi.cpp | 1034 |
1 files changed, 1034 insertions, 0 deletions
diff --git a/pcl/tools/cmpi/cmpi.cpp b/pcl/tools/cmpi/cmpi.cpp new file mode 100644 index 000000000..9527c6e43 --- /dev/null +++ b/pcl/tools/cmpi/cmpi.cpp @@ -0,0 +1,1034 @@ +/////////////////////////////////////////////////////////////////////////////// +// Name: samples/cmpi/cmpi.cpp +// Purpose: Compare images with 'fuzzy' options +// Author: Ray Johnston +// Modified by: +// Created: 2006 +// RCS-ID: $Id:$ +// Copyright: (c) 2006 Artifex Software Inc. +// License: AFPL +/////////////////////////////////////////////////////////////////////////////// + +#include <math.h> + +// 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; i<neighbor_count; i++) { + cp = compare_rows [i_pw + neighbor_row[i]] + p3 + (3 * neighbor_pix[i]); + if (abs(bp[0] - cp[0]) <= color_tolerance && + abs(bp[1] - cp[1]) <= color_tolerance && + abs(bp[2] - cp[2]) <= color_tolerance ) { + if (i == 0) + return false; /* matches at current pixel */ + base_differs = false; + break; /* found a match */ + } + } + if (base_differs) + return true; /* bail out now */ + /* Need to make sure that this isn't a missing feature */ + cp = compare_rows [i_pw] + p3; /* reference is compare */ + for (i=0; i<neighbor_count; i++) { + bp = base_rows [i_pw + neighbor_row[i]] + p3 + (3 * neighbor_pix[i]); + if (abs(bp[0] - cp[0]) <= color_tolerance && + abs(bp[1] - cp[1]) <= color_tolerance && + abs(bp[2] - cp[2]) <= color_tolerance ) { + return false; + } + } + return true; +} + +void +MyCanvas::prune_pending_list( int row ) +{ + diff_rect *diff_pending_scan; + diff_rect *diff_pending_scan_base = diff_list_pending; + + /* First anneal any rects on the pending list that are close enough */ + /* This is done first since this list is active for every row */ + while ( (diff_pending_scan_base != NULL) && (diff_pending_scan_base->next != 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); +} |