context: /*
  *   Copyright 2007-2010 Glenn Pierce, Paul Barber,
  *   Oxford University (Gray Institute for Radiation Oncology and Biology) 
  *
  *   This file is part of MosaicStitcher.
  *
  *   MosaicStitcher is free software: you can redistribute it and/or modify
  *   it under the terms of the GNU General Public License as published by
  *   the Free Software Foundation, either version 3 of the License, or
  *   (at your option) any later version.
  *   
  *   MosaicStitcher is distributed in the hope that it will be useful,
  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  *   GNU General Public License for more details.
  *
  *   You should have received a copy of the GNU General Public License
  *   along with MosaicStitcher.  If not, see <http://www.gnu.org/licenses/>.
  */
  
  using System;
  using System.Timers;
  using System.Drawing;
  using System.Threading;
  using System.Collections;
  using System.Windows.Forms;
  using System.ComponentModel;
  using System.Security.Permissions;
  using System.Drawing.Drawing2D;
  using System.Collections.Generic;
  using System.Runtime.InteropServices;
  
  using ThreadingSystem;
  using FreeImageAPI;
  
  namespace ImageStitching
  {
      public delegate void TimedScrollEventHandler(object sender, TimedScrollEventArgs e);
  
      public delegate void TileViewHandler<S, A>(S sender, A args);
  
      /// <summary>
      /// ImageView - Scrollable Picture Box.
      /// This is very simple it does not paint the image depending
      /// on where you have scroll to. That's for the client code.
      /// </summary>
      public class TileView : UserControl
      {
          [DllImport("user32.dll")]
          static extern IntPtr SetCapture(IntPtr hWnd);
  
          [DllImport("user32.dll", CharSet = CharSet.Auto)]
          public static extern bool PostMessage(int hWnd, uint msg, IntPtr wparam, IntPtr lparam);
  
          [DllImport("user32.dll")]
          public static extern bool GetCursorPos(ref Point pt);
  
          // If there are more than 6 tiles visible use thumbnails instead
          private const int PointAtWhichToUseThumbnails = 10;
  
          private TileViewEditToolBox toolbox = null;
          private MosaicInfo mosaicInfo;
          private List<Tile> tiles;
          private List<Tile> idleTiles;
  
          private Size imageSize;
          private float zoom = 1.0f;
  
          private bool blendingEnabled = false;
          private bool preventIdleProcessing = false;
          private bool highResBitmapReady = false;
          private bool showFileNames = false;
          private bool showJoins = false;
  
          private Rectangle defaultClip;
  
          private bool allowMouseWheelZoom = true;
          private bool panning = true;
          private bool leftMouseButtonDown;
          private Point mouseDownPoint;
          private bool dontDraw = false;
          private int currentTileIdleProcess;
          private bool intermediateBitmapDrawn = false;
          private FreeImageAlgorithmsBitmap intermediateBitmap = null;
  
          private bool highResBitmapIsReset = true;
          private FreeImageAlgorithmsBitmap highResBitmap;
  
          private List<Rectangle> lastIdleTileRectangles = new List<Rectangle>();
          private Size lastIdleTileSize;
  
          private Tile editTile;
          private Point editTileOriginalPosition;
  
          private const int WM_HSCROLL = 0x114;
          private const int WM_VSCROLL = 0x115;
          private const int WM_MOUSEWHEEL = 0x020A;
          private System.Windows.Forms.Timer keyTimer;
          private IContainer components;
          private Keys pressedKey;
  
          private ThreadController threadController = null;
  
          public event EventHandler ZoomChanged;
  
          /// <summary>
          /// Horizontal scroll position has changed event
          /// </summary>
          public event ScrollEventHandler HorzScrollChanged;
  
          /// <summary>
          /// Vertical scroll position has changed event
          /// </summary>
          public event ScrollEventHandler VertScrollChanged;
  
          /// <summary>
          /// Horizontal scroll position has changed event
          /// </summary>
          public event ScrollEventHandler OnScrollChanged;
  
          public event TileViewHandler<TileView, EventArgs> PanningOccurred;
  
          #region Constructor
          private void InitializeComponent()
          {
              this.components = new System.ComponentModel.Container();
              this.keyTimer = new System.Windows.Forms.Timer(this.components);
              this.SuspendLayout();
              // 
              // keyTimer
              // 
              this.keyTimer.Interval = 20;
              this.keyTimer.Tick += new System.EventHandler(this.OnKeyTimerTick);
              // 
              // TileView
              // 
              this.Name = "TileView";
              this.Size = new System.Drawing.Size(729, 519);
              this.MouseDoubleClick += new System.Windows.Forms.MouseEventHandler(this.OnMouseDoubleClick);
              this.KeyUp += new System.Windows.Forms.KeyEventHandler(this.OnKeyUp);
              this.KeyDown += new System.Windows.Forms.KeyEventHandler(this.OnKeyDown);
              this.ResumeLayout(false);
          }
  
          internal TileView()
          {
              this.InitializeComponent();
  
              this.SetStyle(
                          ControlStyles.AllPaintingInWmPaint |
                          ControlStyles.UserPaint |
                          ControlStyles.ResizeRedraw |
                          ControlStyles.Opaque |
                          ControlStyles.DoubleBuffer, true);
  
              this.imageSize = new Size(0, 0);
  
              this.AutoScroll = false;
  
              this.defaultClip = Cursor.Clip;
  
              Application.Idle += new EventHandler(OnIdle);
  
              //this.timer = new System.Timers.Timer();
              //this.timer.Elapsed += new ElapsedEventHandler(OnTimedEvent);
              //timer.Interval = 50;
              //timer.Enabled = false;
          }
  
          #endregion
  
          public TileViewEditToolBox Toolbox
          {
              set
              {
                  this.toolbox = value;
                  this.toolbox.Visible = false;
                  this.toolbox.TickButtonPressed += new TileViewEditToolBoxHandler<TileViewEditToolBox, EventArgs>(OnToolBoxTickButtonPressed);
                  this.toolbox.CrossButtonPressed += new TileViewEditToolBoxHandler<TileViewEditToolBox, EventArgs>(OnToolBoxCrossButtonPressed);
              }
              get
              {
                  return this.toolbox;
              }
          }
  
          public Rectangle DefaultClip
          {
              get
              {
                  return this.defaultClip;
              }
          }
  
          public void SetThreadController(ThreadController threadController)
          {
              this.threadController = threadController;
          }
  
          internal void LoadMosaicInfo(MosaicInfo mosaicInfo)
          {
              this.mosaicInfo = mosaicInfo;
  
              Tile tile = mosaicInfo.Items[0];
  
              this.DestroyIntermediateBitmap();
   
              this.Clear();
              this.CalculateVisibleTiles();
          }
  
          #region ScrollEventCode
  
          /// <summary>
          /// Intercept scroll messages to send notifications
          /// </summary>
          /// <param name="m">Message parameters</param>
          [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode)]
          protected override void WndProc(ref Message m)
          {
              // Let the control process the message
              base.WndProc(ref m);
  
              // Was this a horizontal scroll message?
              if (m.Msg == WM_HSCROLL)
              {
                  //this.Clear();
  
                  if (HorzScrollChanged != null)
                  {
                      uint wParam = (uint)m.WParam.ToInt32();
                      HorzScrollChanged(this,
                          new ScrollEventArgs(
                              GetEventType(wParam & 0xffff), (int)(wParam >> 16)));
                  }
  
                  if (OnScrollChanged != null)
                  {
                      uint wParam = (uint)m.WParam.ToInt32();
                      OnScrollChanged(this,
                          new ScrollEventArgs(
                              GetEventType(wParam & 0xffff), (int)(wParam >> 16)));
                  }
              }
              // or a vertical scroll message?
              else if (m.Msg == WM_VSCROLL /* || m.Msg == WM_MOUSEWHEEL */ )
              {
                  //this.Clear();
  
                  if (VertScrollChanged != null)
                  {
                      uint wParam = (uint)m.WParam.ToInt32();
                      VertScrollChanged(this,
                          new ScrollEventArgs(
                          GetEventType(wParam & 0xffff), (int)(wParam >> 16)));
                  }
  
                  if (OnScrollChanged != null)
                  {
                      uint wParam = (uint)m.WParam.ToInt32();
                      OnScrollChanged(this,
                          new ScrollEventArgs(
                              GetEventType(wParam & 0xffff), (int)(wParam >> 16)));
                  }
              }
  
              if (m.Msg == WM_HSCROLL || m.Msg == WM_VSCROLL /* || m.Msg == WM_MOUSEWHEEL */ )
              {
                  this.Redraw();
                  this.Invalidate();
              }
          }
  
          // Send a scroll event when someone modifies the AutoScrollPosition.
          internal void SetAutoScrollPosition(Point location)
          {
              this.AutoScrollPosition = location;
  
              if (OnScrollChanged != null)
              {
                  OnScrollChanged(this, null);
              }
  
              this.Redraw();
          }
  
          // Based on SB_* constants
          private static ScrollEventType[] _events =
              new ScrollEventType[] {
  									  ScrollEventType.SmallDecrement,
  									  ScrollEventType.SmallIncrement,
  									  ScrollEventType.LargeDecrement,
  									  ScrollEventType.LargeIncrement,
  									  ScrollEventType.ThumbPosition,
  									  ScrollEventType.ThumbTrack,
  									  ScrollEventType.First,
  									  ScrollEventType.Last,
  									  ScrollEventType.EndScroll
  								  };
          /// <summary>
          /// Decode the type of scroll message
          /// </summary>
          /// <param name="wParam">Lower word of scroll notification</param>
          /// <returns></returns>
          private static ScrollEventType GetEventType(uint wParam)
          {
              if (wParam < _events.Length)
                  return _events[wParam];
              else
                  return ScrollEventType.EndScroll;
          }
  
          #endregion ScrollEventCode
  
          protected void OnPanningOccurred(EventArgs args)
          {
              if (PanningOccurred != null)
                  PanningOccurred(this, args);
          }
  
          public void Clear()
          {
              DestroyIntermediateBitmap();
              this.highResBitmap.Clear();
          }
  
          private void ResetHighResolutionBitmap()
          {
              if (highResBitmapIsReset)
                  return;
  
              this.preventIdleProcessing = false;
              this.highResBitmapReady = false;
  
              lastIdleTileRectangles.Clear();
              highResBitmap.Clear();
  
              this.currentTileIdleProcess = 0;
  
              highResBitmapIsReset = true;
          }
  
          /// <summary>
          /// Specifies the Image to be loaded
          /// </summary>		
          private void CalculateVisibleTiles()
          {
              this.tiles = this.GetVisibleTiles();
  
              return;
          }
  
          public List<Tile> GetVisibleTiles()
          {
              Rectangle viewRect = this.ViewedImageSectionRect;
  
              if (viewRect.IsEmpty)
                  return null;
  
              return Tile.GetTilesIntersectingRectangle(MosaicWindow.MosaicInfo.Items, viewRect);
          }
  
          public void Redraw()
          {
              this.CalculateVisibleTiles();
              this.ResetHighResolutionBitmap();
          }
  
          private void CreateHighResBitmap()
          {
              if (this.ClientRectangle.Width == 0 || this.ClientRectangle.Height == 0)
                  return;
  
              if (this.highResBitmap != null)
              {
                  if (this.highResBitmap.Width == this.ClientRectangle.Width &&
                      this.highResBitmap.Height == this.ClientRectangle.Height)
                  {
                      this.highResBitmap.Clear();
                      return;
                  }
              }
  
              this.highResBitmap =
                  new FreeImageAlgorithmsBitmap(this.ClientRectangle.Width, this.ClientRectangle.Height, 24);
          }
  
          private Graphics HighResGraphics(Bitmap bitmap)
          {
              return Graphics.FromImage(bitmap);
          }
  
          // This property returns a rectangle representing the area of the image currently 
          // viewed on the control.
          internal Rectangle ViewedImageSectionRect
          {
              get
              {
                  int left = 0, top = 0;
                  int width = (int)(this.imageSize.Width * this.zoom);
                  int height = (int)(this.imageSize.Height * this.zoom);
  
                  if (width < this.ClientRectangle.Width)
                  {
                      width = this.imageSize.Width;
                  }
                  else
                  {
                      left = (int)(-this.AutoScrollPosition.X / this.zoom);
                      width = (int)(this.ClientRectangle.Width / this.zoom);
                  }
  
                  if (height < this.ClientRectangle.Height)
                  {
                      height = this.imageSize.Height;
                  }
                  else
                  {
                      top = (int)(-this.AutoScrollPosition.Y / this.zoom);
                      height = (int)(this.ClientRectangle.Height / this.zoom);
                  }
  
                  return new Rectangle(left, top, width, height);
              }
          }
  
          public bool BlendingEnabled
          {
              set
              {
                  this.blendingEnabled = value;
  
                  this.Clear();
  
                  CreateIntermediateBitmapIfNeccessary();
  
                  this.SetupScrollbars();
                  this.Invalidate();
              }
              get
              {
                  return this.blendingEnabled;
              }
          }
  
          public bool ShowFileNames
          {
              set
              {
                  this.showFileNames = value;
  
                  if (this.mosaicInfo == null)
                      return;
  
                  this.Clear();
  
                  CreateIntermediateBitmapIfNeccessary();
  
                  this.SetupScrollbars();
                  this.Invalidate();
              }
              get
              {
                  return this.showFileNames;
              }
          }
  
          public bool ShowJoins
          {
              set
              {
                  this.showJoins = value;
  
                  if (this.mosaicInfo == null)
                      return;
  
                  this.Clear();
  
                  CreateIntermediateBitmapIfNeccessary();
  
                  this.SetupScrollbars();
                  this.Invalidate();
              }
              get
              {
                  return this.showJoins;
              }
          }
  
          internal Size ImageSize
          {
              get
              {
                  return this.imageSize;
              }
              set
              {
                  this.imageSize = value;
                  this.SetupScrollbars();
              }
          }
  
          private void DestroyIntermediateBitmap()
          {
              if (this.intermediateBitmap != null)
              {
                  this.intermediateBitmap.Dispose();
                  this.intermediateBitmap = null;
                  this.intermediateBitmapDrawn = false;
              }
          }
  
          private void DrawIntermediateBitmap()
          {
              if (!this.intermediateBitmapDrawn)
              {
                  this.threadController.ReportThreadStarted(this, "Started Zoom");
  
                  float aspectRatio = (float)(this.mosaicInfo.TotalWidth) / this.mosaicInfo.TotalHeight;
  
                  int scaledWidth = Screen.PrimaryScreen.Bounds.Size.Width;
                  int scaledHeight = (int)(scaledWidth / aspectRatio + 0.5);
  
                  if (this.mosaicInfo.IsGreyScale)
                  {
                      this.intermediateBitmap = new FreeImageAlgorithmsBitmap(scaledWidth, scaledHeight, 8);
                  }
                  else
                  {
                      this.intermediateBitmap = new FreeImageAlgorithmsBitmap(scaledWidth, scaledHeight, 24);
                  }
  
                  float xscaleFactor = (float)scaledWidth / this.mosaicInfo.TotalWidth;
                  float yscaleFactor = (float)scaledHeight / this.mosaicInfo.TotalHeight;
  
                  if (this.intermediateBitmap == null)
                  {
                      MessageBox.Show("Failed to create intermediate bitmap");
                  }
  
                  float scale = (float)scaledWidth / this.mosaicInfo.TotalWidth;
        
                  Point scaledPosition = new Point();
  
                  int count = 0;
  
                  foreach (Tile tile in this.mosaicInfo.Items)
                  {
                      if (tile.Thumbnail == null)
                          MessageBox.Show("Error thumnail is null");
  
                      //FreeImageAlgorithmsBitmap thumb = null;
  
                      //lock (tile.ThumbnailLock)
                      //{
                      //    thumb = new FreeImageAlgorithmsBitmap(tile.Thumbnail);
                      //}
  
                      FreeImageAlgorithmsBitmap fib = tile.LoadFreeImageBitmap();
  
                      fib.Rescale(new Size((int)(xscaleFactor * fib.Width), (int)(yscaleFactor * fib.Height)), FREE_IMAGE_FILTER.FILTER_BILINEAR);
  
                      if (fib.IsGreyScale)
                      {
                          fib.LinearScaleToStandardType(mosaicInfo.ScaleMinIntensity, mosaicInfo.ScaleMaxIntensity);
  
                          fib.SetGreyLevelPalette();
                      }
                      else
                      {
                          fib.ConvertToStandardType(true);
                      }
  
                      if (fib.ImageType != FREE_IMAGE_TYPE.FIT_BITMAP)
                      {
                          MessageBox.Show("Failed to convert tile thumbnail to a standard type ?");
                      }
  
                      Size scaledSize = new Size((int)(tile.Width * xscaleFactor + 0.5),
                                                 (int)(tile.Height * yscaleFactor + 0.5));
  
                      scaledPosition.X = (int)(tile.Position.X * xscaleFactor + 0.5);
                      scaledPosition.Y = (int)(tile.Position.Y * yscaleFactor + 0.5);
  
                      Rectangle dstRect = new Rectangle(scaledPosition, scaledSize);
  
                      intermediateBitmap.PasteFromTopLeft(fib, scaledPosition, this.BlendingEnabled);
  
                      fib.Dispose();
  
                      this.threadController.ReportThreadPercentage(this,
                          "Performing Zoom", count++, this.tiles.Count);
                  }
              }
  
              //intermediateBitmap.ConvertTo24Bits();
  
              this.intermediateBitmapDrawn = true;
              this.preventIdleProcessing = false;
              this.dontDraw = false;
  
              this.threadController.ReportThreadCompleted(this, "Zoom Completed", false);
  
              this.Redraw();
              this.Invalidate();
          }
  
          private void ThreadCreateIntermediateBitmap()
          {
              DrawIntermediateBitmap();
          }
  
          public bool NumberOfTilesExceedCacheThreshold
          {
              get
              {
                  // Dont bother with cache images if the total number of tiles is less than fifty.
                  if ((this.tiles.Count > (this.mosaicInfo.NumberOfTiles / 2)) && this.mosaicInfo.NumberOfTiles > 50)
                      return true;
  
                  return false;
              }
          }
  
          public bool CacheImageAvailiable
          {
              get
              {
                  return this.intermediateBitmapDrawn && NumberOfTilesExceedCacheThreshold;
              }
          }
  
          public FreeImageAlgorithmsBitmap CacheImage
          {
              get
              {
                  return this.intermediateBitmap;
              }
          }
  
          private void CreateIntermediateBitmapIfNeccessary()
          {
              if(this.tiles == null)
                  return;
  
              this.CalculateVisibleTiles();
  
              ResetHighResolutionBitmap();
  
              if (this.NumberOfTilesExceedCacheThreshold)
              {
                  this.dontDraw = true;
                  this.preventIdleProcessing = true;
  
                  this.threadController.ThreadStart("ThreadCreateIntermediateBitmap",
                      new ThreadStart(this.ThreadCreateIntermediateBitmap));
  
                  this.ThreadCreateIntermediateBitmap();
              }
          }
  
          public bool AllowPanning
          {
              get
              {
                  return this.panning;
              }
              set
              {
                  this.panning = value;
              }
          }
  
          internal float Zoom
          {
              set
              {
                  if (this.zoom != value)
                  {
                      if (value > 400.0)
                          return;
  
                      Point centre = new Point(this.ClientRectangle.Width / 2, this.ClientRectangle.Height / 2);
  
                      double percentX = (double)(-this.AutoScrollPosition.X + centre.X) / this.zoom;
                      double percentY = (double)(-this.AutoScrollPosition.Y + centre.Y) / this.zoom;
  
                      this.zoom = value;
                      this.SetupScrollbars();
  
                      int x = Convert.ToInt32(percentX * this.zoom);
                      int y = Convert.ToInt32(percentY * this.zoom);
  
                      this.AutoScrollPosition = new Point(x - centre.X, y - centre.Y);
  
                      this.Clear();
                      CreateIntermediateBitmapIfNeccessary();
  
                      this.Redraw();
                      this.Invalidate();
  
                      OnZoomChanged(new EventArgs());
                  }
              }
              get
              {
                  return this.zoom;
              }
          }
  
          protected virtual void OnZoomChanged(EventArgs e)
          {
              if (this.ZoomChanged != null)
                  this.ZoomChanged(this, e);
          }
  
          protected override void OnSizeChanged(EventArgs e)
          {
              base.OnSizeChanged(e);
  
              if (this.mosaicInfo != null)
              {
                  this.Redraw();
              }
          }
  
          protected override void OnClientSizeChanged(EventArgs e)
          {
              CreateHighResBitmap();
  
              base.OnSizeChanged(e);
          }
  
          //protected override void On
  
          private Point CalculateImageCenter()
          {
              // Keep Image Centered
              int top = (int)((this.ClientSize.Height - (this.ImageSize.Height * this.Zoom)) / 2.0);
              int left = (int)((this.ClientSize.Width - (this.ImageSize.Width * this.Zoom)) / 2.0);
  
              if (top < 0)
                  top = 0;
  
              if (left < 0)
                  left = 0;
  
  
gt:             return new Point(left, top);