///////////////////////////////////////////////////////////////////////////////////////////
// Authors: Jonathan Poole, Jonathan Fazakerley, Marc Cecere, eric Montplaisir
// canvas.h
// JPC36 Wx-View
// August 8 2000
// Technical Advisor : Carlos Moreno
///////////////////////////////////////////////////////////////////////////////////////////

#include "canvas.h"

#include "wx/wxprec.h"

#ifdef __BORLANDC__
#pragma hdrstop
#endif

#ifndef WX_PRECOMP
#include "wx/wx.h"
#endif

#include "wx-view.h"
#include "mainFrame.h"

#include "wx/file.h"
#include <wx/image.h>
#include <wx/bitmap.h>
#include <wx/dc.h>

#include <cmath>
#include <cctype>
#include <iostream>
#include <algorithm>
using namespace std;


class MainFrame;

/*******************************************************************************
    Class: AdjustBrightness
	Description: Class adjustBrightness alters an image's pixels by varying degrees
		to brighten or darken the view. It uses a precomputed look-up-table(LUT),
		which is an array of possible values for a pixel
*******************************************************************************/
class AdjustBrightness      // LUT function object
{
public:
/*******************************************************************************
	Function: AdjustBrightness::AdjustBrightness
	Recieves: double , int
	Returns: n/a (constructor)
	Description: Initializes an AdjustBrightness class object. Fills the LUT
*******************************************************************************/
    AdjustBrightness (double a = 0.2, int n = 1) //constructor
    {

        for (int i = 0; i < 256; i++)
        {
            lut[i] = precomputed (i, a, n);
        }
    }

    unsigned char operator() (unsigned char x) const
    {
        return lut[x];
    }

private:
    unsigned char lut[256];
/****************************************************************************************
	Function: AdjustBrightness::brighter
    Receives: double , double
    Returns: double
    Description: Function computes computes the new RGB value and returns a double
****************************************************************************************/
    double brighter (double x, double a) const
    {
        return x * (1 + a * (1 - x / 255));
    }
/****************************************************************************************
	Function: AdjustBrightness::Precomputed
    Receives: unsigned char , double, int
    Returns: unsigned char
    Description: Function computes new values(unsigned char) that is assigned to a RGB of
    	a pixel. Calls AdjustBrightness::brighter(double, dounle)const
****************************************************************************************/
    unsigned char precomputed (unsigned char val, double a, int n) const
    {
        double x = val;
        for (int i = 0; i < n; i++)
        {
            x = brighter(x, a);
        }

        return static_cast<unsigned char> (x + 0.5);
    }
};

/*******************************************************************************
	Class: AdjustContrast
	Description: Class adjustContrast alters an image's pixels by varying degrees
		to clarify or dull the view. It uses a precomputed look-up-table(LUT),
		which is an array of possible values for a pixel
*******************************************************************************/
class AdjustContrast

{
public:
/*******************************************************************************
	Function: AdjustContrast::AdjustContrast
	Recieves: double , int
	Returns: n/a (constructor)
	Description: Initializes an AdjustContrast class object. Fills the LUT
*******************************************************************************/
    AdjustContrast (double a = 0.2, int n = 1)
    {
        for (int i = 0; i < 256; i++)
        {
            lut[i] = precomputed (i, a, n);
        }
    }

    unsigned char operator() (unsigned char x) const
    {
        return lut[x];
    }

private:
    unsigned char lut[256];
/****************************************************************************************
	Function: AdjustContrast::adjust_contrast
    Receives: double , double
    Returns: double
    Description: Function computes computes the new RGB value and returns a double
****************************************************************************************/
    double adjusted_contrast (double x, double a) const
    {
        if (x <= 127.5)
        {
            return x * (1 - a * (1 - x / 127.5));
        }
        else
        {
            x = 255 - x;
            return 255 - x * (1 - a * (1 - x / 127.5));
        }
    }
/****************************************************************************************
	Function: AdjustContrast::Precomputed
    Receives: unsigned char , double, int
    Returns: unsigned char
    Description: Function computes new values(unsigned char) that is assigned to a RGB of
    	a pixel. Calls AdjustContrast::adjusted_contrast(double, double)const
****************************************************************************************/

    unsigned char precomputed (unsigned char val, double a, int n) const
    {
        double x = val;
        for (int i = 0; i < n; i++)
        {
            x = adjusted_contrast(x, a);
        }

        return static_cast<unsigned char> (x + 0.5);
    }
};

BEGIN_EVENT_TABLE(Canvas, wxPanel)
	EVT_LEFT_UP(Canvas::on_left_up) 	
	EVT_LEFT_DOWN(Canvas::on_left_down) 		
	EVT_RIGHT_UP(Canvas::pop_up_menu) 	
	EVT_MOTION(Canvas::on_motion)
	EVT_LEAVE_WINDOW(Canvas::on_leaving)
	EVT_SIZE (Canvas::on_size)
	EVT_PAINT (Canvas::on_paint)
	EVT_KEY_DOWN(Canvas::on_key_down)
END_EVENT_TABLE()	

Canvas::Canvas(wxFrame* parent, wxWindowID id, wxSize s)
/****************************************************
Fuction: Canvas constructor
Receives: parent frame, parent ID, panel size,
Returns: N/A
Description: Initializes a working
*****************************************************/

        :	wxPanel(parent, id, wxPoint(0,0), s, wxSUNKEN_BORDER), 	
  		    dragging_event(false), interpolating(false), h_flipped(false), point_zoom(false), working_image(NULL),
  		    current_frame(0,0,0,0), drag_rect(0,0,0,0), subimage_frame(0,0,0,0), initial_frame(0,0,0,0),					
			prev_zoom_point(0,0), subimage_center(0,0), click_down(0,0), user_zoom(2), magnification(1),
			scale_original_to_screen(1), rotation_angle(0),
			brightness(0), clarity(0)	  		
{
	bm = new wxBitmap();		
	original_image = new wxImage();
}

Canvas::~Canvas()
/****************************************************
Fuction: Canvas destructor
Receives: N/A
Returns: N/A
Description: releases memory of all dynamically allocated data members
*****************************************************/
{
	delete bm;
	delete original_image;
	delete working_image;	
}

void Canvas::display_image(const wxString & file)
/****************************************************
Fuction: display_image
Receives: file name (const wxString &)
Returns: void
Description: displays an image file centered and sized (if too large) to fit on the panel
*****************************************************/
{
    wxBeginBusyCursor();
    wxYield();

	original_image->LoadFile(file, wxBITMAP_TYPE_ANY);		
	reset_initial_frame();
	
 	if (scale_original_to_screen < 1)
	{ 	
	 	original_image->Rescale(initial_frame.width, initial_frame.height);
	}

	current_frame = initial_frame;	
   	draw_image(*original_image, initial_frame.x, initial_frame.y);   	
}

void Canvas::on_size (wxSizeEvent &)
/****************************************************
Fuction: Canvas::on_size
Receives: N/A
Returns: void
Description: called when panel size is changed. Re-sizes image on screen and redisplays
*****************************************************/
{		
	GetClientSize(&client_width, &client_height);	
	
	if (bm->Ok())
	{	
		reset_initial_frame();		
		recreate_working_image();
	}
}

bool rectangles_overlap (const wxRect & r1, const wxRect & r2)
/****************************************************
Fuction: rectangles_overlap
Receives: 2 rectangles (2 wxRect)
Returns: bool
Description: using the panel reference system, determines if 2 passed wxRect's overlap
*****************************************************/
{
	return ! (r2.GetLeft() > r1.GetRight() || r2.GetRight() < r1.GetLeft() ||
	          r2.GetTop() > r1.GetBottom() || r2.GetBottom() < r1.GetTop());
}

void Canvas::on_paint (wxPaintEvent & event)
/****************************************************
Fuction: Canvas::on_paint
Receives: N/A
Returns: void
Description: Re-displays destroyed parts of image when covered portions are removed.
*****************************************************/
{
    wxPaintDC dc (this);
	
	if (bm->Ok()== false)
		return;

    if (current_frame == wxRect(0,0,0,0))
    {
    	return;
    }

    wxRegionIterator region (GetUpdateRegion());

    wxMemoryDC dc_bmp;
    dc_bmp.SelectObject (*bm);

    while (region)
    {
    	// the bitmap is not at location 0,0 of the screen -- we need to adjust it        	
       	wxRect effective_region (region.GetX(), region.GetY(), region.GetW(), region.GetH());
       	
    	if (rectangles_overlap (effective_region, current_frame))
    	{
        	if (effective_region.GetX() < current_frame.x)
        	{
        		effective_region.SetWidth (effective_region.GetWidth() - (current_frame.x - effective_region.GetX()));
        		effective_region.SetX (current_frame.x);
        	}
        	if (effective_region.GetY() < current_frame.y)
        	{
        		effective_region.SetHeight (effective_region.GetHeight() - (current_frame.y - effective_region.GetY()));
        		effective_region.SetY(current_frame.y);
        	}

        	if (effective_region.GetRight() > current_frame.GetRight())
        	{
        		effective_region.SetWidth (effective_region.GetWidth() - (current_frame.GetRight() - effective_region.GetRight()));    	
        	}
        	if (effective_region.GetBottom() > current_frame.GetBottom())
        	{
        		effective_region.SetHeight (effective_region.GetHeight() - (current_frame.GetBottom() - effective_region.GetBottom()));    	
        	}
         	    	
            dc.Blit (effective_region.GetX(), effective_region.GetY(), effective_region.GetWidth(), effective_region.GetHeight(),
                     &dc_bmp, effective_region.GetX() - current_frame.GetX(), effective_region.GetY() - current_frame.GetY());

         }

         region++;
    }
}

void Canvas::reset_image_parameters()
/****************************************************
Fuction: Canvas::reset_image_parameters
Receives: N/A
Returns: N/A
Description: Resets all the variables associated with imaging
*****************************************************/
{
	magnification = 1;	
    rotation_angle = 0;
	prev_zoom_point = wxPoint(0,0);    	
	brightness = 0;
	clarity = 0;	
    h_flipped = false;		
    point_zoom = false;
}	
		
void Canvas::load_file(const wxString & file)
/****************************************************
Fuction: Canvas::load_file
Receives: file name (const wxString)
Returns: void
Description: 	loads an image file, resets the working image, imaging parameters and frames,
				and displays image sized (if necessary) to screen.  .
*****************************************************/
{
    wxBeginBusyCursor();
    wxYield();

	original_image->LoadFile(file, wxBITMAP_TYPE_ANY);	
		
 	if (working_image == NULL)
 	{
		working_image = new wxImage(*original_image);
	}
	else
	{
		*working_image = *original_image;	
	}	
	
	reset_image_parameters();		
	reset_initial_frame();
 	current_frame = initial_frame;		
 	
 	if (scale_original_to_screen < 1)
	{ 	
	 	working_image->Rescale(initial_frame.width, initial_frame.height);
	}
	
   	draw_image(*working_image, initial_frame.x, initial_frame.y);
   	
   	GetParent()->SetTitle (wxString(translation["Wx-View 1.0"]) + " - [" + file.c_str() + "]");
}

void Canvas::change_zoom_type(bool interpolation)
/****************************************************
Fuction: Canvas::change_zoom_type
Receives: bool
Returns: void
Description: toggles method of zooming boolean (interpolation vs. stretching).
*****************************************************/
{		
    interpolating = interpolation;

	if (bm->Ok()== false)
		return;

	if (magnification > 1)				
		recreate_working_image();
}

void Canvas::original()
/****************************************************
Fuction: Canvas::original
Receives: N/A
Returns: void
Description: resets working image, imaging parameters and frames,
			and displays original image sized (if necessary) to fit screen
*****************************************************/
{
	if (bm->Ok()== false)
		return;
		
    wxBeginBusyCursor();
    wxYield();

	reset_image_parameters();
 	current_frame = initial_frame;		
	*working_image = *original_image;
	
	if (scale_original_to_screen < 1)
	{	
		working_image->Rescale(initial_frame.width, initial_frame.height);			    			
	}
	
	draw_image(*working_image, initial_frame.x, initial_frame.y);
}
void Canvas::undraw_rectangle()
/****************************************************
Fuction: Canvas::undraw_rectangle
Receives: N/A
Returns: void
Description: Removes dragging rectangle by reprinting with opposite colours
*****************************************************/
{
	wxClientDC dc(this);

    wxBrush rect_brush ("", wxTRANSPARENT);
	wxPen rect_pen("gray", 1, wxDOT_DASH);
    dc.SetBrush(rect_brush);
    dc.SetPen(rect_pen);				
  	dc.SetLogicalFunction(wxXOR);
						
	dc.DrawRectangle(drag_rect);						
}

void Canvas::on_leaving(wxMouseEvent& event)
/****************************************************
Fuction: Canvas::on_leaving
Receives: N/A
Returns: N/A
Description: If, during a dragging event, the mouse leaves the panel, the dragging event is cancelled .
*****************************************************/
{
	if (bm->Ok() && dragging_event)
	{
		undraw_rectangle();
		drag_rect  = wxRect(0, 0, 0, 0);
	}	
	
	dragging_event = false;
}

void Canvas::on_motion(wxMouseEvent& event)
/****************************************************
Fuction: Canvas::on_motion
Receives: N/A
Returns: void
Description: If dragging is occuring, displays a drag rectangle on the image in opposite colours
*****************************************************/
{
	if (bm->Ok() == false || rotation_angle % 180 != 0)
	{
		return;
	}

	if (event.m_leftDown)
	{	
		//check if dragging event started within the image frame
	
		if (click_down.x < current_frame.GetLeft() || click_down.x > current_frame.GetRight() ||
			click_down.y < current_frame.GetTop() || click_down.y > current_frame.GetBottom() )
		{
            return;
		}

		dragging_event = true;

		wxClientDC dc(this);
        wxBrush rect_brush ("", wxTRANSPARENT);
		wxPen rect_pen("gray", 1, wxDOT_DASH);
        dc.SetBrush(rect_brush);
        dc.SetPen(rect_pen);				
  		dc.SetLogicalFunction(wxXOR);						
		dc.DrawRectangle(drag_rect);						
					
		int new_x = event.m_x, new_y = event.m_y;
			
		// if dragging outside image but stiil on window, limit rectangle to within image frame
			
		if (new_x < current_frame.x)
		{
			new_x = current_frame.x;
		}
		else if (new_x > current_frame.GetRight())
		{
			new_x = current_frame.GetRight();
		}
		
		if (new_y < current_frame.y)
		{
			new_y = current_frame.y;
		}
		else if (new_y > current_frame.GetBottom())
		{
			new_y = current_frame.GetBottom();
		}
		
		int rect_w = abs(click_down.x - new_x), rect_h = abs(click_down.y - new_y);

		//get top left corner point (new x and new y) of dragged rect
				
		if (click_down.x < new_x)
		{
			new_x = click_down.x;
		}
		
		if (click_down.y < new_y)
		{
			new_y = click_down.y;
		}		
		
		drag_rect = wxRect(new_x, new_y, rect_w, rect_h);
		dc.DrawRectangle(drag_rect);		
	}
}

void Canvas::on_left_down(wxMouseEvent& event)
/****************************************************
Fuction: Canvas::on_left_down
Receives: N/A
Returns: void
Description: Records location of left down click on image.
*****************************************************/

{
	if (bm->Ok() && rotation_angle % 180 == 0)
	{
		click_down = wxPoint(event.m_x, event.m_y);		
     	
        drag_rect.x = event.m_x;	
        drag_rect.y = event.m_y;		
 	}
}

void Canvas::on_left_up(wxMouseEvent& event)
/****************************************************
Fuction: Canvas::on_left_up
Receives: N/A
Returns: N/A
Description: 	Records location of left mouse click-up:
				zooming into either a completed dragged rectangle or the event point
*****************************************************/
{
	if (bm->Ok() == false)
	{
		return;	
	}

    // released outside image 	
	if (!dragging_event)
	{
		if (event.m_x < current_frame.GetLeft() || event.m_x >(current_frame.GetRight()) ||
			event.m_y < current_frame.GetTop() || event.m_y > (current_frame.GetBottom()) )		
		{
			return;
		}
	}	
	
	if (rotation_angle % 180 != 0)
	{
	    wxMessageBox(translation["Cannot remagnify rotated images."]);             //translator
		return;
	}						
	
    wxBeginBusyCursor();
    wxYield();

	double new_zoom_factor = user_zoom;
	wxPoint zoom_point(event.m_x, event.m_y);	
					
	if (dragging_event)  //zooming into a dragged rectangle
	{
		dragging_event = false;	
		
		
		//scale to panel width or height depending on proportionality of the dragged rectangle to the panel
  		new_zoom_factor = static_cast <double> (client_height) / drag_rect.height;
    	 	    	
    	if ( static_cast <double> (client_width) / drag_rect.width < new_zoom_factor )
    	{
    		new_zoom_factor = static_cast <double> (client_width) / drag_rect.width;
    	}
    	
		if (set_sub_image_with_rect_zoom(new_zoom_factor) == false)
		{
			undraw_rectangle();		
			drag_rect = wxRect(0,0,0,0);	   	    						
			wxEndBusyCursor();			   		   				
			return;
		}
		
   		point_zoom = false;            	

		*working_image = original_image->GetSubImage(subimage_frame);		
   		reset_working_image_with_colour_settings();    		    					
		adjust_working_image();
   		magnify_working_image(magnification * new_zoom_factor * scale_original_to_screen, current_frame);    	   		
		drag_rect = wxRect(0,0,0,0);	   	    	
	}
			
   	else if ( zoom_into_point(zoom_point) == false )
	{
		wxEndBusyCursor();			   		   		
    	return;
	}
  				
	draw_image(*working_image, current_frame.x, current_frame.y);
   	magnification *= new_zoom_factor;    				

	wxEndBusyCursor();			   		   	
}

void Canvas::zoom_center()
/****************************************************
Fuction: Canvas::zoom_center
Receives: N/A
Returns: N/A
Description: zooms into center of image
*****************************************************/
{	
	if (bm->Ok()== false)
		return;

    wxBeginBusyCursor();			
    wxYield();

	wxPoint zoom_point(client_width / 2, client_height / 2);	

	if ( zoom_into_point(zoom_point) )
	{
		draw_image(*working_image, current_frame.x, current_frame.y);								
	 	magnification *= user_zoom;							
	}
	
    wxEndBusyCursor();	
}

bool Canvas::zoom_into_point(wxPoint & zoom_point)
/****************************************************
Fuction: Canvas::zoom_into_point
Receives: point of magnification on panel (wxPoint)
Returns: bool
Description: 	calculates the effective point of magnification on the original image and
				the effective frame that when magnified will fill the largest possible area on the panel,
				and displays the magnified subimage or the downscaled original depending on the
				new magnification
*****************************************************/
{
	double new_magnification = magnification * user_zoom;

   	if (new_magnification > 1)
	{
		wxPoint temp_point = subimage_center;

		if (set_effective_zoom_point(zoom_point) == false)
		{
			return false;		
		}
	    	
		if (set_point_zoom_sub_image_frame(new_magnification) == false)
		{
			//cancel reassignment of subimage_center in set-effective_zoom_point
			subimage_center = temp_point;
			return false;
		}
		
		point_zoom = true;   		
	   	scale_current_frame(new_magnification * scale_original_to_screen, subimage_frame);
		prev_zoom_point = wxPoint(subimage_frame.x, subimage_frame.y);		
		*working_image = original_image->GetSubImage(subimage_frame);	   	
				
	   	reset_working_image_with_colour_settings();   		   	   			
		adjust_working_image();
    	magnify_working_image(new_magnification * scale_original_to_screen, current_frame);								
    	    	
	}
    else
	{
		zoom_out_original(new_magnification);	            	
    }

    return true;
}

bool Canvas::set_effective_zoom_point(wxPoint & zoom_point)
/****************************************************
Fuction: Canvas::set_effective_zoom_point
Receives: point of magnification on panel (wxPoint)
Returns: bool
Description: 	Given the point of zooming on the panel,
				calculates the effective point of magnification on the original image taking into account
				any horizontal flipping and any 180 degree rotations
*****************************************************/
{
    switch (rotation_angle)
    {
    	case 0:
    		break;
    		    	
    	case 180: case -180:    	
    		zoom_point.x = current_frame.GetRight() - zoom_point.x + current_frame.x;
    		zoom_point.y = current_frame.GetBottom() - zoom_point.y + current_frame.y;	
    		break;    	    	
    	
    	default:
 		    wxMessageBox(translation["Cannot remagnify rotated images."]);             //translator
    		return false;
    }

    if (h_flipped)
    {
    	zoom_point.x = current_frame.GetRight() - zoom_point.x + current_frame.x;
    }

    subimage_center = wxPoint(	prev_zoom_point.x + (zoom_point.x - current_frame.x) / (magnification * scale_original_to_screen),
    							prev_zoom_point.y +	(zoom_point.y - current_frame.y) / (magnification * scale_original_to_screen));

	return true;    							    							
}

bool Canvas::set_point_zoom_sub_image_frame(double new_magnification)
/****************************************************
Fuction: Canvas::set_point_zoom_sub_image_frame
Receives: new magnification (double)
Returns: bool
Description: 	calculates the effective frame that when magnified will fill
				the largest possible area on the panel.
*****************************************************/
{
   	int zoom_rect_w = static_cast <int> (client_width / (scale_original_to_screen * new_magnification));
   	int zoom_rect_h = static_cast <int> (client_height / (scale_original_to_screen * new_magnification));	
   	int zoom_x, zoom_y;	   	   	  		

    if (zoom_rect_w > original_image->GetWidth())   	   	
	{
		double scale_down = static_cast <double> (original_image->GetWidth()) / zoom_rect_w;		
		zoom_rect_w = static_cast <int> (zoom_rect_w * scale_down);
    }
	if (zoom_rect_h > original_image->GetHeight())
	{
	   double scale_down = static_cast <double> (original_image->GetHeight()) / zoom_rect_h;	
	   zoom_rect_h = static_cast <int> (zoom_rect_h * scale_down);		
	}

	//requesting 0 pixels as a dimension of a subimage of original	
   	if (zoom_rect_w == 0 || zoom_rect_h == 0)
   	{
   		return false;
   	}	
   				   	   	   	   		
    zoom_x = subimage_center.x - zoom_rect_w / 2;
   	zoom_y = subimage_center.y - zoom_rect_h / 2;
   	   				
   	subimage_frame = wxRect (zoom_x, zoom_y, zoom_rect_w, zoom_rect_h);   	
	shift_rect_into_rect(subimage_frame, wxRect(0, 0, original_image->GetWidth(), original_image->GetHeight()) );
	
	subimage_center.x = subimage_frame.x + subimage_frame.width / 2;
	subimage_center.y = subimage_frame.y + subimage_frame.height / 2;		

	return true;
}

bool Canvas::set_sub_image_with_rect_zoom(double scaling_factor)
/****************************************************
Fuction: Canvas::set_sub_image_with_rect_zoom
Receives: scaling factor (double)
Returns: bool
Description: 	calculates the effective frame on the original image that was marked out by the
				dragging rectangle on the panel taking into account horizontally flipping and
				180 degree rotations
*****************************************************/
{		
	int zoom_rect_w = static_cast <int> ( drag_rect.width / (magnification * scale_original_to_screen) );
	int zoom_rect_h = static_cast <int> ( drag_rect.height / (magnification * scale_original_to_screen) ) ;			

	//requesting 0 pixels as a dimension of a subimage of original		
	if ( zoom_rect_w == 0 || zoom_rect_h == 0)
	{    	
		return false;
	}
				    	
	int effective_x = drag_rect.x;
	int effective_y = drag_rect.y;

    switch (rotation_angle)
    {
    	case 0:
    		break;
    		
    	case 180: case -180:    	
    		effective_x = (current_frame.GetRight() - drag_rect.GetRight()) + current_frame.x;
    		effective_y = (current_frame.GetBottom() - drag_rect.GetBottom()) + current_frame.y;
    		break;    	    	
    	
    	default:
		    wxMessageBox(translation["Cannot remagnify rotated images."]);             //translator    	
    		return false;
    }	
	
	if (h_flipped)
	{
		effective_x = current_frame.GetRight() - effective_x - drag_rect.width + current_frame.x;
	}	
	
	int zoom_rect_x = static_cast <int> (prev_zoom_point.x + (effective_x - current_frame.x) / (magnification * scale_original_to_screen) );   	
  	int zoom_rect_y = static_cast <int> (prev_zoom_point.y + (effective_y - current_frame.y) / (magnification * scale_original_to_screen) );    			
     	  	
	prev_zoom_point = wxPoint(zoom_rect_x, zoom_rect_y);  	        		
	scale_current_frame(scaling_factor, drag_rect);	
	subimage_frame = wxRect(zoom_rect_x, zoom_rect_y, zoom_rect_w, zoom_rect_h);
	
   	return true;
}

void Canvas::zoom_out()
/****************************************************
Fuction: Canvas::zoom_out(
Receives: N/A
Returns: void
Description: 	Depending on the new magnification either displays a downscaled original image or
				a less magnified image - recalculating the effective point of magnification and the
				effective frame of the sub-image
*****************************************************/
{
	if (bm->Ok()== false)
		return;
		
	if (rotation_angle % 180 != 0)
	{
	    wxMessageBox(translation["Cannot remagnify rotated images."]);             //translator	
		return;
	}						

    wxBeginBusyCursor();
    wxYield();

    double new_magnification = magnification / user_zoom;

	if (new_magnification < 1) //zoom out of original
	{		
    	zoom_out_original(new_magnification);	
    }
    else  //zoom into original
	{			
	    subimage_center = wxPoint( prev_zoom_point.x + (current_frame.width / (magnification * scale_original_to_screen)) / 2 ,
	                               prev_zoom_point.y + (current_frame.height / (magnification * scale_original_to_screen)) / 2 );
	
		if (set_point_zoom_sub_image_frame(new_magnification) == false)
	   	{
	   		return;
	   	}	

		point_zoom = true;	   	
	   	scale_current_frame(new_magnification * scale_original_to_screen, subimage_frame);
	   	prev_zoom_point = wxPoint(subimage_frame.x, subimage_frame.y);	 				    	   			        	    	
		*working_image = original_image->GetSubImage(subimage_frame);	   	
		reset_working_image_with_colour_settings();				
		adjust_working_image();		
		magnify_working_image(new_magnification * scale_original_to_screen, current_frame);				
	}

	draw_image(*working_image, current_frame.x, current_frame.y);								
	magnification /= user_zoom;			
}

void Canvas::magnify_working_image(double new_magnification, wxRect rect)
/****************************************************
Fuction: Canvas::magnify_working_image
Receives: new magnification and the frame the image will be scaled up into (double, wxRect)
Returns: void
Description: 	magnifies the subimage denoted by the frame of the sub-image by
				stretching or interpolating
*****************************************************/
{
	if (interpolating == false || new_magnification < 1) //stretching or downscaling
	{
   		working_image->Rescale(rect.width, rect.height);	
	}       	
   	else    //interpolating
   	{   		
	   	wxImage zoomed_subimage = *working_image;
		wxImage* interp_zoom = interpolate_image(&zoomed_subimage, new_magnification);
		*working_image = *interp_zoom;
		delete interp_zoom;
				
		/*	interpolation is imperfect upsizing so we make the current frame equal to the dimensions
			of the interpolated image*/
		current_frame.SetWidth(working_image->GetWidth());
		current_frame.SetHeight(working_image->GetHeight());		
		current_frame.SetX( (client_width - current_frame.width) / 2);
		current_frame.SetY( (client_height - current_frame.height) / 2);			
   	}
}

void Canvas::zoom_out_original(double new_magnification)
/****************************************************
Fuction: Canvas::zoom_out_original
Receives: new magnification (double)
Returns: N/A
Description: when the new magnification is less then 1, down sizes the working image
*****************************************************/
{
	point_zoom = false;            	
   	scale_current_frame(new_magnification, initial_frame);   	
   	prev_zoom_point = wxPoint(0, 0);	 	

   	*working_image = *original_image;
	working_image->Rescale(current_frame.width, current_frame.height);			    	
	reset_working_image_with_colour_settings();		
		
	adjust_working_image();
	resize_working_image();									
}

wxImage * Canvas::interpolate_image(wxImage * img, double zoom_factor)
/****************************************************
Fuction: Canvas::interpolate_image
Receives: wxImage *, double
Returns: wxImage *
Description: given a magnification, returns a pointer to an interpolated version of a passed image,
*****************************************************/
{   	
   	wxImage * zoomed = new wxImage (static_cast <int> (img->GetWidth() * zoom_factor),
   									static_cast <int> (img->GetHeight() * zoom_factor) );
   	
    const unsigned char * img_data = img->GetData();
    unsigned char * dst = zoomed->GetData();

    for (int row = 0; row < zoomed->GetHeight(); row++)
    {
        double y_src = row / zoom_factor;
        int y1 = static_cast<int>(y_src);
        int y2 = y1 + 1;

        if (y1 < 0)
        {
            y1 = 0;
        }
        if (y2 >= img->GetHeight())
        {
            y2 = img->GetHeight() - 1;
        }

        const unsigned char * row1_start = img_data + 3 * y1 * img->GetWidth();
        const unsigned char * row2_start = img_data + 3 * y2 * img->GetWidth();

        for (int col = 0; col < zoomed->GetWidth(); col++)
        {
            double x_src = col / zoom_factor;
            int x1 = static_cast<int>(x_src);
            int x2 = x1 + 1;

            if (x1 < 0)
            {
                x1 = 0;
            }
            if (x2 >= img->GetWidth())
            {
                x2 = img->GetWidth() - 1;
            }

            const unsigned char * v1 = row1_start + 3 * x1;     //  v1---v2
            const unsigned char * v2 = row1_start + 3 * x2;     //  |     |
            const unsigned char * v3 = row2_start + 3 * x1;     //  |     |
            const unsigned char * v4 = row2_start + 3 * x2;     //  v3---v4

            const double dx = x_src - x1;
            const double dy = y_src - y1;

                // calculate interpolated RED
            double avgx1 = *v1 + dx * (*v2 - *v1);
            double avgx2_minus_avgx1 = *v3 - *v1 + dx * (*v4 - *v3 - (*v2 - *v1));
            v1++; v2++; v3++; v4++;

            *dst++ = static_cast<unsigned char> (avgx1 + dy * avgx2_minus_avgx1);

                // GREEN
            avgx1 = *v1 + dx * (*v2 - *v1);
            avgx2_minus_avgx1 = *v3 - *v1 + dx * (*v4 - *v3 - (*v2 - *v1));
            v1++; v2++; v3++; v4++;

            *dst++ = static_cast<unsigned char> (avgx1 + dy * avgx2_minus_avgx1);

                // BLUE
            avgx1 = *v1 + dx * (*v2 - *v1);
            avgx2_minus_avgx1 = *v3 - *v1 + dx * (*v4 - *v3 - (*v2 - *v1));
            v1++; v2++; v3++; v4++;

            *dst++ = static_cast<unsigned char> (avgx1 + dy * avgx2_minus_avgx1);
        }
    }

    return zoomed;
}

void Canvas::adjust_working_image()
/****************************************************
Fuction: adjust_working_image
Receives: N/A
Returns: void
Description: Given the current angle and flip state rotates and then flips the working image if necessary
*****************************************************/
{
    if (rotation_angle)
    {
    	rotate_working_image();    	
    }    	
    if (h_flipped)
    {
    	flip_working_image();
    }				
}

void Canvas::shift_rect_into_rect(wxRect & small_rect, const wxRect & big_rect)
/****************************************************
Fuction: shift_rect_into_rect
Receives: 2 wxRect
Returns: void
Description: 	adjusts coordinates of the 1st passed rectangle so that it is shifted
				and fully conatined by the larger rectangle
*****************************************************/
{
   	if (small_rect.x < big_rect.x)
   	{
   		small_rect.x = big_rect.x;	
   	}
   	else if ( small_rect.x + small_rect.width > big_rect.width)
   	{	
   		small_rect.x = big_rect.width - small_rect.width;	
   	}	
   	      			
   	if (small_rect.y < big_rect.y)
   	{	
   		small_rect.y = big_rect.y;	
   	}
   	else if ( small_rect.y + small_rect.height > big_rect.height)
   	{	
   		small_rect.y = big_rect.height - small_rect.height;	
   	}		
}

void Canvas::scale_current_frame(double scaling_factor, const wxRect & frame)
/****************************************************
Fuction: scale_current_frame
Receives: scaling factor, scaling frame (double, wxRect)
Returns: void
Description: reassigns the current frame to a scaled version of a passed frame
*****************************************************/
{
	if (frame.width * scaling_factor >= 1)
	    current_frame.width = static_cast <int> (frame.width * scaling_factor);
	else
		current_frame.width	= 1;
	
	if (frame.height * scaling_factor >= 1)				
	    current_frame.height = static_cast <int> (frame.height * scaling_factor);
	else
		current_frame.height = 1;
	
	current_frame.x = (client_width  - current_frame.width) / 2;
	current_frame.y = (client_height  - current_frame.height) / 2;	
}

void Canvas::draw_image(const wxImage & image, int x, int y)
/****************************************************
Fuction: Canvas::draw_image
Receives: const wxImage &, printing point (x and y int)
Returns: void
Description: Initializes bit map data member to a passed image and displays on panel
*****************************************************/
{
    if (! image.Ok())
    {
    	return;
    }

	*bm = image.ConvertToBitmap();	
	draw_current_bitmap(x, y);
	wxEndBusyCursor();	
}
	
void Canvas::draw_current_bitmap(int x, int y)
/****************************************************
Fuction: Canvas::draw_image
Receives: printing point (x and y int)
Returns: void
Description: 	draws bitmap on screen
				the 4 rectangles that describe the panel regions around the image are also
				drawn to achieve the background default colour on the panel
*****************************************************/
{
	wxClientDC dc(this);	
	dc.SetBrush (wxBrush (GetBackgroundColour(), wxSOLID));
	dc.SetPen (*wxTRANSPARENT_PEN);

	if (current_frame.GetTop() > 0)		
		dc.DrawRectangle (0, 0,	client_width, current_frame.GetTop());	
		
	if (current_frame.GetBottom() + 1  < client_height)	
		dc.DrawRectangle (0, current_frame.GetBottom() + 1, client_width, client_height - (current_frame.GetBottom() + 1));			
	
	if (current_frame.x > 0)			
		dc.DrawRectangle (0, current_frame.y, current_frame.x, current_frame.height);		
		
	if (current_frame.GetRight() + 1 < client_width)	
		dc.DrawRectangle (current_frame.GetRight() + 1, current_frame.y, client_width - (current_frame.GetRight() + 1), current_frame.height);				
	
	dc.DrawBitmap (*bm, current_frame.x, current_frame.y);
}	
	
void Canvas::rotate_working_image()
/****************************************************
Fuction: rotate_working_image
Receives: N/A
Returns: void
Description: 	assigns the working image to a rotated version of itself	
				the mask is set to background because rotations produce a new rectangular image
*****************************************************/
{
	//function declarations
	wxImage * rotated90 (const wxImage *);
	wxImage * rotated_minus90 (const wxImage *);
	wxImage * rotated180 (const wxImage *);	
	wxImage * rotated;
   		
   	wxImage* rotated_image;
   	
    working_image->SetMask();
    working_image->SetMaskColour(GetBackgroundColour().Red(),
		        	            GetBackgroundColour().Green(),
   	    					    GetBackgroundColour().Blue());    					   	   	   	
	switch (rotation_angle)
	{
		case 0:
			break;
		case 90:
			rotated_image = rotated90 (working_image);
			*working_image = *rotated_image;
			delete rotated_image;		
	    	break;
	 	case -90:
			rotated_image = rotated_minus90 (working_image);
			*working_image = *rotated_image;
			delete rotated_image;			 	
	 		break;
	 	case 180: case -180:	
			rotated_image = rotated180 (working_image);
			*working_image = *rotated_image;
			delete rotated_image;			 		 	
	    	break;
		default:
		    double m_angle = (rotation_angle * pi) / 180.0;
		
			*working_image = working_image->Rotate(m_angle, wxPoint(working_image->GetWidth() / 2, working_image->GetHeight() / 2));
            break;
	}
	
    working_image->SetMask();
    working_image->SetMaskColour(GetBackgroundColour().Red(),
		        	            GetBackgroundColour().Green(),
   	    					    GetBackgroundColour().Blue());    							
}

void Canvas::flip_working_image()
/****************************************************
Fuction: flip_working_image
Receives: N/A
Returns: void
Description: assigns the working image to a horizontally flipped version of itself	
*****************************************************/
{
   	wxImage * flipHorizontal (const wxImage *);	
	wxImage* flip_image = flipHorizontal(working_image);
	*working_image = *flip_image;
	delete flip_image;
}

void Canvas::on_rotate()
/****************************************************
Fuction: Canvas::on_rotate
Receives: N/A
Returns: N/A
Description: Prompts user for rotation angle and re-displays rotated image.
*****************************************************/
{
	if (bm->Ok()== false)
		return;
		
    wxString input = wxGetTextFromUser(translation["Enter the image rotation angle"], translation["Angle in degrees:"], "", this);

	long degrees = atoi(input);
                                      		
	if (degrees == 0)
		return;
                                  		                                 		                              		
	if (h_flipped)
	{
		rotation_angle += -degrees;
	}
	else
	{
		rotation_angle += degrees;
	}
		
	adjust_rotation_angle();	
	recreate_working_image();
}

void Canvas::adjust_rotation_angle()
/****************************************************
Fuction: adjust_rotation_angle
Receives: N/A
Returns: void
Description: Adjusts an angle -360 to 360 to a -180 to 180 angle
*****************************************************/
{
	if (rotation_angle > 180)
	{
		rotation_angle = -180 + (rotation_angle - 180);
	}
	else if (rotation_angle < -180)
	{
		rotation_angle = 180 + (rotation_angle + 180);
	}
}

void Canvas::on_v_flip()
/****************************************************
Fuction: on_v_flip
Receives: N/A
Returns: void
Description: 	Vertically flips working image by increasing the rotation angle by 180 degrees,
				toggling the horizontal flip boolean, and re-displaying the image				
				(note: rotation must be done before flipping to achieve proper vertical flip)
*****************************************************/
{
	if (bm->Ok()== false)
		return;
		
    switch_h_flip();
	rotation_angle += 180;
	adjust_rotation_angle();	
	recreate_working_image();
}

void Canvas::on_h_flip()
/****************************************************
Fuction: on_h_flip
Receives: N/A
Returns: void
Description: toggles horizontal flip boolean and redisplays working image
*****************************************************/
{
	if (bm->Ok()== false)
		return;
		
    switch_h_flip();
	recreate_working_image();
}

void Canvas::switch_h_flip()
/****************************************************
Fuction: switch_h_flip
Receives: N/A
Returns: void
Description: toggles horizontal flip boolean
*****************************************************/
{
	if (h_flipped)
	{
		h_flipped = false;
	}
	else
	{
		h_flipped = true;
	}
}

wxImage * flipHorizontal(const wxImage * img)
/****************************************************
Fuction: flip_horizontal
Receives: const wxImage *
Returns: wxImage *
Description: Returns a pointer to a horizantally flipped version of a passed image.
*****************************************************/
{
	wxImage * rotated = new wxImage (img->GetWidth(), img->GetHeight());

    unsigned char * dst_start = rotated->GetData();

    const unsigned char * src = img->GetData();
    const int offset = 3 * (rotated->GetWidth());
	const int src_offset = 3 * (img->GetWidth());

    for (int i = 0; i < img->GetHeight(); i++)
    {
		reverse_copy(src, src + src_offset, dst_start);
		dst_start += offset;
		src += src_offset;
    }

    	// reverse flipped te RGB to GRB;  fix the pixels
    const int end = 3 * rotated->GetWidth()*rotated->GetHeight();
    unsigned char * const dst = rotated->GetData();
    for (int i = 0; i < end; i += 3)
    {
    	swap (dst[i], dst[i+2]);
    }

    return rotated;
}

wxImage * rotated90 (const wxImage * img)
/****************************************************
Fuction: rotated90
Receives: const wxImage *
Returns: wxImage *
Description: returns a pointer to a 90 degree rotated version of a passed image.
*****************************************************/
{
	wxImage * rotated = new wxImage (img->GetHeight(), img->GetWidth());

    unsigned char * dst_start =
        rotated->GetData() + 3 * (rotated->GetWidth() * (rotated->GetHeight() - 1));

    const unsigned char * src = img->GetData();
    const int offset = 3 * (rotated->GetWidth() + 1);

    for (int i = 0; i < img->GetHeight(); i++)
    {
        unsigned char * dst = dst_start;
        for (int j = 0; j < img->GetWidth(); j++)
        {
            *dst++ = *src++;
            *dst++ = *src++;
            *dst++ = *src++;

            dst -= offset;  // precomputed above
        }

        dst_start += 3;
    }
    return rotated;
}

wxImage * rotated_minus90(const wxImage * img)
/****************************************************
Fuction: rotated_minus90
Receives: const wxImage *
Returns: wxImage *
Description: returns a pointer to a -90 degree rotated version of a passed image.
*****************************************************/
{
    wxImage * rotated = new wxImage (img->GetHeight(), img->GetWidth());

    unsigned char * dst_start =
        rotated->GetData() + 3 * (rotated->GetWidth() - 1);

    const unsigned char * src = img->GetData();
    const int offset = 3 * (rotated->GetWidth() -1);

    for (int i = 0; i < img->GetHeight(); i++)
    {
        unsigned char * dst = dst_start;
        for (int j = 0; j < img->GetWidth(); j++)
        {
            *dst++ = *src++;
            *dst++ = *src++;
            *dst++ = *src++;

            dst += offset;  // precomputed above
        }

        dst_start -= 3;
    }
    return rotated;
}

wxImage * rotated180(const wxImage * img)
/****************************************************
Fuction: rotated180
Receives: const wxImage *
Returns: wxImage *
Description: returns a pointer to a 180 degree rotated version of a passed image.
*****************************************************/
{
    wxImage * rotated = new wxImage (img->GetWidth(), img->GetHeight());

    unsigned char * dst_start =
        rotated->GetData() + 3 * (rotated->GetWidth() * (rotated->GetHeight() -1));

    const unsigned char * src = img->GetData();
    const int offset = 3 * (rotated->GetWidth() );
	const int src_offset = 3 * (img->GetWidth() );

    for (int i = 0; i < img->GetHeight(); i++)
    {
		reverse_copy(src, src + src_offset, dst_start);
		dst_start -= offset;
		src += src_offset;
    }

    	// reverse flipped te RGB to GRB;  fix the pixels
    const int end = 3 * rotated->GetWidth() * rotated->GetHeight();
    unsigned char * const dst = rotated->GetData();
    for (int i = 0; i < end; i += 3)
    {
    	swap (dst[i], dst[i+2]);
    }

    return rotated;
}

void Canvas::reset_initial_frame()	
/****************************************************
Fuction: reset_initial_frame
Receives: N/A
Returns: N/A
Description: 	Given the original image, initializes the "scale to screen" factor to 1 or
				,if the image is too large, to a number that when multiplied by the dimensions of
				the image gives the initial frame in which the image wiil be displayed on the panel
*****************************************************/
{
	scale_original_to_screen = 1;			
	
	if ( original_image->GetHeight() > client_height || original_image->GetWidth() > client_width)
	{
    	scale_original_to_screen = get_scale_to_screen_factor(*original_image);
	}	
	
	initial_frame = get_image_frame(*original_image, scale_original_to_screen);									
}

void Canvas::resize_working_image()
/****************************************************
Fuction: Canvas::resize_working_image
Receives: N/A
Returns: void
Description: 	If the image is too large for the panel, initializes the current frame to
				the down scaled dimensions of the working image and down scales the working image
				into that frame
*****************************************************/
{
	double scale_to_screen = 1;

	if ( working_image->GetHeight() > client_height || working_image->GetWidth() > client_width)
	{
    	scale_to_screen = get_scale_to_screen_factor(*working_image);	
 	}
 	 	
	current_frame = get_image_frame(*working_image, scale_to_screen);
	
	if (scale_to_screen < 1)
	{
	 	working_image->Rescale(current_frame.width, current_frame.height);		 	 		 	 	
	}
}

double Canvas::get_scale_to_screen_factor(const wxImage & img)
/****************************************************
Fuction: get_scale_to_screen_factor
Receives: const wxImage &
Returns: double
Description: Calculates the factor.required to down scale an image into the panel (>0 <1)
*****************************************************/
{
	double scale_to_screen = static_cast <double> (client_height) / (img.GetHeight());
	
	if ( static_cast <double>  (client_width) / (img.GetWidth()) < scale_to_screen)
	{
		scale_to_screen = static_cast <double> (client_width) / (img.GetWidth());
	}		
	return scale_to_screen;		
}
	
wxRect Canvas::get_image_frame(const wxImage & img, double scale_to_screen)
/****************************************************
Fuction: get_image_frame
Receives: image, scale to screen factor (const wxImage &, double)
Returns: frame (wxRect)
Description: given an image and a scaling factor, returns a down scaled frame of the image dimensions			
*****************************************************/
{
	wxRect img_dimension;

	img_dimension.width = static_cast <int> (img.GetWidth() * scale_to_screen);						
	img_dimension.height = static_cast <int> (img.GetHeight() * scale_to_screen);
	img_dimension.x = (client_width - img_dimension.width) / 2;
	img_dimension.y = (client_height - img_dimension.height) / 2;
	
	return img_dimension;
}

void Canvas::on_bright()
/****************************************************
Fuction: Canvas::on_bright
Receives: N/A
Returns: void
Description: increments the current brightness of working image and redisplays
*****************************************************/
{
	if (bm->Ok()== false)
		return;
		
	brightness++;	
	recreate_working_image();	
}

wxImage * Brighten(const wxImage * img, int brightness)
/****************************************************
Fuction: brighten
Receives: const wxImage *, int
Returns: wxImage *
Description: returns a pointer to a brightened vesrion of a passed image
*****************************************************/
{
    unsigned char brighter(const unsigned char);//declare function
    wxImage * brightened = new wxImage (img->GetWidth(), img->GetHeight());
    unsigned char * dst_start = brightened->GetData();

    const unsigned char * src = img->GetData();
	transform(src, src + 3 * img->GetWidth() * img->GetHeight(), dst_start, AdjustBrightness(0.2, brightness) );		

    return brightened;
}

void Canvas::adjust_brightness_on_working_image()
/****************************************************
Fuction: Canvas::adjust_brightness_on_working_image
Receives: N/A
Returns: N/A
Description: 	Depending on current brightness setting, dims or brightens the working image by
				the current amount.
*****************************************************/
{
    wxImage * Brighten (const wxImage *, int brightness);
    wxImage * Dim (const wxImage *, int brightness);

	if (brightness > 0)
	{
	    wxImage* brighter_image = Brighten(working_image, brightness);
	    *working_image = *brighter_image;
	    delete brighter_image;
	}
	else if (brightness < 0)
	{
        wxImage* dimmer_image = Dim(working_image, abs(brightness));
        *working_image = *dimmer_image;
        delete dimmer_image;
	}    		
}

void Canvas::on_dim ()
/****************************************************
Fuction: Canvas::on_dim
Receives: N/A
Returns: N/A
Description: Decrements the current brightness of working image and redisplays
*****************************************************/
{
	if (bm->Ok()== false)
		return;
		
	brightness--;	
	recreate_working_image();	
}

wxImage * Dim(const wxImage * img, int brightness)
/****************************************************
Fuction: dim
Receives: const wxImage *, int
Returns: wxImage *
Description: returns a pointer to a dimmed verson of a passed image
*****************************************************/
{
    unsigned char dimmer(const unsigned char);//declare function
    wxImage * dimmed = new wxImage (img->GetWidth(), img->GetHeight());
    unsigned char * dst_start = dimmed->GetData();

    const unsigned char * src = img->GetData();
	transform(src, src + 3 * img->GetWidth() * img->GetHeight(), dst_start, AdjustBrightness(-0.2 , brightness));		

    return dimmed;
}

void Canvas::on_plus_contrast()
/****************************************************
Fuction: Canvas::on_plus_contrast
Receives: N/A
Returns: N/A
Description: Increments the current contrast of the working image and redisplays
*****************************************************/
{
	if (bm->Ok()== false)
		return;
		
	clarity++;
	recreate_working_image();
}

wxImage * Clarify(const wxImage * img, int clarity)
/****************************************************
Fuction: clarify
Receives: const wxImage *, int
Returns: wxImage *
Description: returns a pointer to a contrasted version of a passed image
*****************************************************/
{
    unsigned char clearer(const unsigned char);//declare function
    wxImage * cleared = new wxImage (img->GetWidth(), img->GetHeight());
    unsigned char * dst_start = cleared->GetData();

    const unsigned char * src = img->GetData();
	transform(src, src + 3 * img->GetWidth() * img->GetHeight(), dst_start, AdjustContrast(0.2 , clarity));		

    return cleared;
}

void Canvas::adjust_contrast_on_working_image()
/****************************************************
Fuction: Canvas::adjust_contrast_on_working_image
Receives: N/A
Returns: N/A
Description: 	Depending on current contrast setting, clarifies or dulls the working image by
				the current amount.
*****************************************************/
{
    wxImage * Clarify (const wxImage *, int clarity);
    wxImage * Dull (const wxImage *, int clarity);

	if (clarity > 0)
	{    	
        wxImage* clearer_image = Clarify(working_image, clarity);
        *working_image = *clearer_image;
        delete clearer_image;    	
 	}
	else if (clarity < 0)
	{
        wxImage* duller_image = Dull(working_image, abs(clarity));
        *working_image = *duller_image;
        delete duller_image;
 	}
}

void Canvas::on_minus_contrast()
/****************************************************
Fuction: Canvas::on_minus_contrast
Receives: N/A
Returns: N/A
Description: Decrements the current contrast of the working image and redisplays
*****************************************************/
{
	if (bm->Ok()== false)
		return;

	clarity--;
	recreate_working_image();
}

wxImage * Dull(const wxImage * img, int clarity)
/****************************************************
Fuction: dull
Receives: const wxImage *, int
Returns: wxImage *
Description: returns a pointer to a dulled version of a passed image
*****************************************************/
{
    unsigned char duller(const unsigned char);  //declare function
    wxImage * dulled = new wxImage (img->GetWidth(), img->GetHeight());
    unsigned char * dst_start = dulled->GetData();

    const unsigned char * src = img->GetData();
	transform(src, src + 3 * img->GetWidth() * img->GetHeight(), dst_start, AdjustContrast(-0.2 , clarity));		

    return dulled;
}

void Canvas::recreate_working_image()
/****************************************************
Fuction: Canvas::recreate_working_image
Receives: N/A
Returns: N/A
Description: Recreates working image with all current imaging variables
*****************************************************/
{
    wxBeginBusyCursor();
    wxYield();
	
	if (magnification != 1)
	{	
		if (magnification  < 1)
		{
	   		zoom_out_original(magnification);
	 	}
        else
        {
    		if (point_zoom)  //change subimage_frame to maximize subimage dimensions to new window size
    		{
    			set_point_zoom_sub_image_frame(magnification);
    			prev_zoom_point = wxPoint(subimage_frame.x, subimage_frame.y);
    		}		
    		
   		   	scale_current_frame(magnification * scale_original_to_screen, subimage_frame);    		

			*working_image = original_image->GetSubImage(subimage_frame);			
			reset_working_image_with_colour_settings();			

			if (rotation_angle % 180 == 0)
			{
				adjust_working_image();			
			}
			
			magnify_working_image(magnification * scale_original_to_screen, current_frame);
			
    		if (rotation_angle % 180 != 0)
    		{
				adjust_working_image();			
    			resize_working_image();								    			
    		}						
		}
	}
	else
	{		
	    *working_image = *original_image;
		reset_working_image_with_colour_settings();	
		adjust_working_image();
		resize_working_image();	    					
	}

	draw_image(*working_image, current_frame.x, current_frame.y);												
    wxEndBusyCursor();	
}

void Canvas::reset_working_image_with_colour_settings()
/****************************************************
Fuction: reset_working_image_with_colour_settings
Receives: N/A
Returns: void
Description: reinitializes contrast and brightness of the working image
*****************************************************/
{   	   	
	if (clarity)
		adjust_contrast_on_working_image();
		
	if (brightness)
		adjust_brightness_on_working_image();		
}

void Canvas::change_zoom_factor(double new_user_zoom)
/****************************************************
Fuction: change_zoom_factor
Receives: default zoom factor
Returns: void
Description: changes the zooming factor performed in point zooms and zoom outs
*****************************************************/
{
   user_zoom = new_user_zoom;
}

void Canvas::shift_left(double shift_percentage)
/****************************************************
Fuction: shift_left
Receives: percentage of the current subimage frame by which to shift (double)
Returns: void
Description: shifts the view of a zoomed image to the left.
			-shifts the bitmap, draws the new sliver of magnified area on the old bitmap, draws new bitmap
			-shifts at least 1 pixel by default			
*****************************************************/
{
	if (bm->Ok()== false || magnification <= 1)
		return;

	if (rotation_angle % 180 != 0)
	{
		wxMessageBox(translation["Cannot shift rotated images."]);		
		return;
	}

	int pixel_shift = static_cast <int> (shift_percentage * subimage_frame.width);
	
	if (pixel_shift < 1)
	{
		pixel_shift = 1;
	}
	
	//vertical flip or not horizontally flipped and not rotated 180 degrees
	if ( (h_flipped && rotation_angle == 180) || !(h_flipped || rotation_angle == 180) )
	{
		if (subimage_frame.x == 0)
			return;
	
		if (subimage_frame.x - pixel_shift < 0)
		{
			pixel_shift = subimage_frame.x;
		}															
			
		subimage_frame.x -= pixel_shift;		
	}
	else
	{
		if (subimage_frame.GetRight() == original_image->GetWidth() - 1)
			return; 	
			
		if (subimage_frame.GetRight() + pixel_shift > original_image->GetWidth() - 1)
		{
			pixel_shift = original_image->GetWidth() - 1 - subimage_frame.GetRight();
		}										
	
		subimage_frame.x += pixel_shift;					
	}	
	
	prev_zoom_point.x = subimage_frame.x;		
	subimage_center.x = subimage_frame.x + subimage_frame.width / 2;			
	
	const int screen_pixel_shift = static_cast<int>(pixel_shift * magnification * scale_original_to_screen );	
			
	wxMemoryDC dc_src;
	wxMemoryDC dc_dst;
	
	dc_src.SelectObject (*bm);
	dc_dst.SelectObject (*bm);
	
	dc_dst.Blit (screen_pixel_shift, 0, bm->GetWidth() - screen_pixel_shift, bm->GetHeight(), &dc_src, 0, 0, wxCOPY);

	if ( (h_flipped && rotation_angle == 180) || !(h_flipped || rotation_angle == 180) )
	{
		draw_rest_of_shifted_image(	wxRect(	subimage_frame.x, subimage_frame.y,
											pixel_shift, subimage_frame.height), wxPoint(0, 0));
	}
	else
	{
		draw_rest_of_shifted_image(	wxRect(	subimage_frame.GetRight() - pixel_shift, subimage_frame.y,
											pixel_shift, subimage_frame.height), wxPoint(0, 0));
	}	
	
	draw_current_bitmap(current_frame.x, current_frame.y);		
}

void Canvas::shift_right(double shift_percentage)
/****************************************************
Fuction: shift_right
Receives: percentage of the current subimage frame by which to shift (double)
Returns: void
Description: shifts the view of a zoomed image to the right.
			-shifts the bitmap, draws the new sliver of magnified area on the old bitmap, draws new bitmap
			-shifts at least 1 pixel by default
*****************************************************/
{
	if (bm->Ok()== false || magnification <= 1)
		return;

	if (rotation_angle % 180 != 0)
	{
		wxMessageBox(translation["Cannot shift rotated images."]);	
		return;
	}

	int pixel_shift = static_cast <int> (shift_percentage * subimage_frame.width);
	
	if (pixel_shift < 1)
	{
		pixel_shift = 1;
	}

	//vertical flip or not horizontally flipped and not rotated 180 degrees		
	if ( (h_flipped && rotation_angle == 180) || !(h_flipped || rotation_angle == 180) )
	{
		if (subimage_frame.GetRight() == original_image->GetWidth() - 1)
			return; 	
			
		if (subimage_frame.GetRight() + pixel_shift > original_image->GetWidth() - 1)
		{
			pixel_shift = original_image->GetWidth() - 1 - subimage_frame.GetRight();
		}									
			
		subimage_frame.x += pixel_shift;		
	}
	else
	{
		if (subimage_frame.x == 0)
			return; 		

		if (subimage_frame.x - pixel_shift < 0)
		{
			pixel_shift = subimage_frame.x;
		}															
		
		subimage_frame.x -= pixel_shift;		
	}
		
	prev_zoom_point.x = subimage_frame.x;		
	subimage_center.x = subimage_frame.x + subimage_frame.width / 2;				
	
	const int screen_pixel_shift = static_cast<int>(pixel_shift * magnification * scale_original_to_screen );
			
	wxMemoryDC dc_src;
	wxMemoryDC dc_dst;
	
	dc_src.SelectObject (*bm);
	dc_dst.SelectObject (*bm);
	
	dc_dst.Blit (0, 0, bm->GetWidth() - screen_pixel_shift, bm->GetHeight(), &dc_src, screen_pixel_shift, 0, wxCOPY);

	if ( (h_flipped && rotation_angle == 180) || !(h_flipped || rotation_angle == 180) )
	{
		draw_rest_of_shifted_image(	wxRect(	subimage_frame.GetRight() - pixel_shift, subimage_frame.y,
											pixel_shift, subimage_frame.height),
									wxPoint(bm->GetWidth() - 1 - screen_pixel_shift, 0));
	}
	else
	{
		draw_rest_of_shifted_image(	wxRect(	subimage_frame.x, subimage_frame.y,
											pixel_shift, subimage_frame.height),
									wxPoint(bm->GetWidth() - 1 - screen_pixel_shift,0));
	}	
	
	draw_current_bitmap(current_frame.x, current_frame.y);		
}

void Canvas::shift_up(double shift_percentage)
/****************************************************
Fuction: shift_up
Receives: percentage of the current subimage frame by which to shift (double)
Returns: void
Description: shifts the view of a zoomed image up.
			-shifts the bitmap, draws the new sliver of magnified area on the old bitmap, draws new bitmap
			-shifts at least 1 pixel by default
*****************************************************/
{
	if (bm->Ok()== false || magnification <= 1)
		return;

	if (rotation_angle % 180 != 0)
	{
		wxMessageBox(translation["Cannot shift rotated images."]);	
		return;
	}

	int pixel_shift = static_cast <int> (shift_percentage * subimage_frame.height);
	
	if (pixel_shift < 1)
	{
		pixel_shift = 1;
	}

	//normal image or horizontally flipped only
	if ( (!h_flipped && rotation_angle == 0) || (h_flipped && rotation_angle == 0 ) )
	{
		if (subimage_frame.GetTop() == 0)
			return;
	
		if (subimage_frame.GetTop() - pixel_shift < 0)
		{
			pixel_shift = subimage_frame.y;
		}
	
		subimage_frame.y -= pixel_shift;						
	}
	else
	{
		if (subimage_frame.GetBottom() == original_image->GetHeight() - 1)
			return;	
	
		if (subimage_frame.GetBottom() + pixel_shift > original_image->GetHeight() - 1)
		{
			pixel_shift = original_image->GetHeight() - 1 - subimage_frame.GetBottom();
		}	
		
		subimage_frame.y += pixel_shift;								
	}	
		
	prev_zoom_point.y = subimage_frame.y;		
	subimage_center.y = subimage_frame.y + subimage_frame.height / 2;

	const int screen_pixel_shift = static_cast<int>(pixel_shift * magnification * scale_original_to_screen);	
			
	wxMemoryDC dc_src;
	wxMemoryDC dc_dst;
	
	dc_src.SelectObject (*bm);
	dc_dst.SelectObject (*bm);
	
	dc_dst.Blit (0, screen_pixel_shift, bm->GetWidth(), bm->GetHeight() - screen_pixel_shift, &dc_src, 0, 0, wxCOPY);

	if ( (!h_flipped && rotation_angle == 0) || (h_flipped && rotation_angle == 0 ) )
	{
		draw_rest_of_shifted_image(	wxRect(	subimage_frame.x, subimage_frame.y,
											subimage_frame.width, pixel_shift), wxPoint(0,0));
	}
	else
	{
		draw_rest_of_shifted_image(	wxRect(	subimage_frame.x, subimage_frame.GetBottom() - pixel_shift,
											subimage_frame.width, pixel_shift), wxPoint(0,0));
	}	
	
	draw_current_bitmap(current_frame.x, current_frame.y);	
}

void Canvas::shift_down(double shift_percentage )
/****************************************************
Fuction: shift_down
Receives: percentage of the current subimage frame by which to shift (double)
Returns: void
Description: shifts the view of a zoomed image down.
			-shifts the bitmap, draws the new sliver of magnified area on the old bitmap, draws new bitmap
			-shifts at least 1 pixel by default
*****************************************************/
{
	if (bm->Ok()== false || magnification <= 1)
		return;
	
	if (rotation_angle % 180 != 0)
	{
		wxMessageBox(translation["Cannot shift rotated images."]);
		return;
	}

	int pixel_shift = static_cast <int> (shift_percentage * subimage_frame.height);
	
	if (pixel_shift < 1)
	{
		pixel_shift = 1;
	}
	
	//normal image or horizontally flipped only
	if ( (!h_flipped && rotation_angle == 0) || (h_flipped && rotation_angle == 0 ) )
	{
		if (subimage_frame.GetBottom() == original_image->GetHeight() - 1)
			return;	
			
		if (subimage_frame.GetBottom() + pixel_shift > original_image->GetHeight() - 1)
		{
			pixel_shift = original_image->GetHeight() - 1 - subimage_frame.GetBottom();
		}						
	
		subimage_frame.y += pixel_shift;			
	}
	else
	{
		if (subimage_frame.GetTop() == 0)
			return;		
			
		if (subimage_frame.GetTop() - pixel_shift < 0)
		{
			pixel_shift = subimage_frame.y;
		}									
			
		subimage_frame.y -= pixel_shift;			
	}		
		
	prev_zoom_point.y = subimage_frame.y;		
	subimage_center.y = subimage_frame.y + subimage_frame.height / 2;

	const int screen_pixel_shift = static_cast<int>(pixel_shift * magnification * scale_original_to_screen);	
			
	wxMemoryDC dc_src;
	wxMemoryDC dc_dst;
	
	dc_src.SelectObject (*bm);
	dc_dst.SelectObject (*bm);
	
	dc_dst.Blit (0, 0, bm->GetWidth(), bm->GetHeight() - screen_pixel_shift, &dc_src, 0, screen_pixel_shift, wxCOPY);

	if ( (!h_flipped && rotation_angle == 0) || (h_flipped && rotation_angle == 0 ) )
	{
		draw_rest_of_shifted_image(	wxRect(	subimage_frame.x, subimage_frame.GetBottom() - pixel_shift,
											subimage_frame.width, pixel_shift),
									wxPoint(0, bm->GetHeight() - 1 - screen_pixel_shift));
	}
	else
	{
		draw_rest_of_shifted_image(	wxRect(	subimage_frame.x, subimage_frame.y, subimage_frame.width, pixel_shift),
									wxPoint(0, bm->GetHeight() - 1 - screen_pixel_shift));
	}	
	
	draw_current_bitmap(current_frame.x, current_frame.y);		
}

void Canvas::draw_rest_of_shifted_image (const wxRect & missing_area, const wxPoint & bm_pos)
/****************************************************
Fuction: draw_rest_of_shifted_image
Receives: 	the frame of the newly viewable are on the original image, position to draw on the bitmap
			(wxRect, wxPoint)

Returns: void
Description: magnifies the frame and draws the image at a given position on the old bitmap
*****************************************************/
{	
	double new_magnification = magnification * scale_original_to_screen;

   	*working_image = original_image->GetSubImage(missing_area);			
   	
	if (clarity)
		adjust_contrast_on_working_image();
		
	if (brightness)
		adjust_brightness_on_working_image();		   	
   	
	adjust_working_image();						
	
	wxRect temp_frame = current_frame; //if interpolating in magnify function, current frame is changed
	
   	magnify_working_image(new_magnification	, wxRect(0, 0, 	missing_area.GetWidth() * new_magnification,
   															missing_area.GetHeight() * new_magnification));
	
   	current_frame = temp_frame;														   															
   																
	wxMemoryDC dc;
	dc.SelectObject (*bm);	
	
	wxBitmap rest_of_image = working_image->ConvertToBitmap();			   		   			
	dc.DrawBitmap(rest_of_image, bm_pos.x, bm_pos.y);	
}


void Canvas::pop_up_menu(wxMouseEvent &event)
/****************************************************
Fuction: pop_up_menu
Receives: N/A	
Returns: void
Description: displays a pop up menu on the panel
*****************************************************/

{
	dynamic_cast <MainFrame *> (GetParent())->pop_up_menu(event);
}

void Canvas::on_key_down(wxKeyEvent & event)
/****************************************************
Fuction: on_key_down
Receives: N/A	
Returns: void
Description: captures key events passing to parent window
*****************************************************/

{
	dynamic_cast <MainFrame *> (GetParent())->on_key_down(event);
}
