Download Cyotek.Windows.Forms.ImageBox.zip version 1.1.4.2, last updated 13/02/2014 (1.62 MB)

Download
  • md5: eafe88cd279eec36bc79f6409f0fc49d
using System;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;

namespace Cyotek.Windows.Forms
{
  // Cyotek ImageBox
  // Copyright (c) 2010-2014 Cyotek.
  // http://cyotek.com
  // http://cyotek.com/blog/tag/imagebox

  // Licensed under the MIT License. See imagebox-license.txt for the full text.

  // If you use this control in your applications, attribution, donations or contributions are welcome.

  /// <summary>
  ///   Component for displaying images with support for scrolling and zooming.
  /// </summary>
  [DefaultProperty("Image")]
  [ToolboxBitmap(typeof(ImageBox), "ImageBox.bmp")]
  [ToolboxItem(true)]
  /* [Designer("Cyotek.Windows.Forms.Design.ImageBoxDesigner", Cyotek.Windows.Forms.ImageBox.Design.dll, PublicKeyToken=58daa28b0b2de221")] */ public class ImageBox : VirtualScrollableControl
  {
    #region Constants

    private const int MaxZoom = 3500;

    private const int MinZoom = 1;

    private const int SelectionDeadZone = 5;

    #endregion

    #region Instance Fields

    private bool _allowClickZoom;

    private bool _allowDoubleClick;

    private bool _allowZoom;

    private bool _autoCenter;

    private bool _autoPan;

    private int _dropShadowSize;

    private int _gridCellSize;

    private Color _gridColor;

    private Color _gridColorAlternate;

    private ImageBoxGridDisplayMode _gridDisplayMode;

    private ImageBoxGridScale _gridScale;

    private Bitmap _gridTile;

    private Image _image;

    private Color _imageBorderColor;

    private ImageBoxBorderStyle _imageBorderStyle;

    private InterpolationMode _interpolationMode;

    private bool _invertMouse;

    private bool _isPanning;

    private bool _limitSelectionToImage;

    private Color _pixelGridColor;

    private int _pixelGridThreshold;

    private bool _scaleText;

    private Color _selectionColor;

    private ImageBoxSelectionMode _selectionMode;

    private RectangleF _selectionRegion;

    private bool _shortcutsEnabled;

    private bool _showPixelGrid;

    private ImageBoxSizeMode _sizeMode;

    private Point _startMousePosition;

    private Point _startScrollPosition;

    private ContentAlignment _textAlign;

    private Color _textBackColor;

    private ImageBoxGridDisplayMode _textDisplayMode;

    private Brush _texture;

    private int _updateCount;

    private bool _virtualMode;

    private Size _virtualSize;

    private int _zoom;

    private ZoomLevelCollection _zoomLevels;

    #endregion

    #region Public Constructors

    /// <summary>
    ///   Initializes a new instance of the <see cref="ImageBox" /> class.
    /// </summary>
    public ImageBox()
    {
      this.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint | ControlStyles.OptimizedDoubleBuffer | ControlStyles.ResizeRedraw, true);
      this.SetStyle(ControlStyles.StandardDoubleClick, false);

      // ReSharper disable DoNotCallOverridableMethodsInConstructor
      this.BeginUpdate();
      this.WheelScrollsControl = false;
      this.AllowZoom = true;
      this.LimitSelectionToImage = true;
      this.DropShadowSize = 3;
      this.ImageBorderStyle = ImageBoxBorderStyle.None;
      this.BackColor = Color.White;
      this.AutoSize = false;
      this.AutoScroll = true;
      this.GridScale = ImageBoxGridScale.Small;
      this.GridDisplayMode = ImageBoxGridDisplayMode.Client;
      this.GridColor = Color.Gainsboro;
      this.GridColorAlternate = Color.White;
      this.GridCellSize = 8;
      this.AutoPan = true;
      this.InterpolationMode = InterpolationMode.NearestNeighbor;
      this.AutoCenter = true;
      this.SelectionColor = SystemColors.Highlight;
      this.ActualSize();
      this.ShortcutsEnabled = true;
      this.ZoomLevels = ZoomLevelCollection.Default;
      this.ImageBorderColor = SystemColors.ControlDark;
      this.PixelGridColor = Color.DimGray;
      this.PixelGridThreshold = 5;
      this.TextAlign = ContentAlignment.MiddleCenter;
      this.TextBackColor = Color.Transparent;
      this.TextDisplayMode = ImageBoxGridDisplayMode.Client;
      this.EndUpdate();
      // ReSharper restore DoNotCallOverridableMethodsInConstructor
    }

    #endregion

    #region Events

    /// <summary>
    ///   Occurs when the AllowClickZoom property is changed.
    /// </summary>
    [Category("Property Changed")]
    public event EventHandler AllowClickZoomChanged;

    /// <summary>
    ///   Occurs when the AllowDoubleClick property value changes
    /// </summary>
    [Category("Property Changed")]
    public event EventHandler AllowDoubleClickChanged;

    /// <summary>
    ///   Occurs when the AllowZoom property is changed.
    /// </summary>
    [Category("Property Changed")]
    public event EventHandler AllowZoomChanged;

    /// <summary>
    ///   Occurs when the AutoCenter property is changed.
    /// </summary>
    [Category("Property Changed")]
    public event EventHandler AutoCenterChanged;

    /// <summary>
    ///   Occurs when the AutoPan property is changed.
    /// </summary>
    [Category("Property Changed")]
    public event EventHandler AutoPanChanged;

    /// <summary>
    ///   Occurs when the DropShadowSize property is changed.
    /// </summary>
    [Category("Property Changed")]
    public event EventHandler DropShadowSizeChanged;

    /// <summary>
    ///   Occurs when the GridSizeCell property is changed.
    /// </summary>
    [Category("Property Changed")]
    public event EventHandler GridCellSizeChanged;

    /// <summary>
    ///   Occurs when the GridColorAlternate property is changed.
    /// </summary>
    [Category("Property Changed")]
    public event EventHandler GridColorAlternateChanged;

    /// <summary>
    ///   Occurs when the GridColor property is changed.
    /// </summary>
    [Category("Property Changed")]
    public event EventHandler GridColorChanged;

    /// <summary>
    ///   Occurs when the GridDisplayMode property is changed.
    /// </summary>
    [Category("Property Changed")]
    public event EventHandler GridDisplayModeChanged;

    /// <summary>
    ///   Occurs when the GridScale property is changed.
    /// </summary>
    [Category("Property Changed")]
    public event EventHandler GridScaleChanged;

    /// <summary>
    ///   Occurs when the ImageBorderColor property value changes
    /// </summary>
    [Category("Property Changed")]
    public event EventHandler ImageBorderColorChanged;

    /// <summary>
    ///   Occurs when the ImageBorderStyle property is changed.
    /// </summary>
    [Category("Property Changed")]
    public event EventHandler ImageBorderStyleChanged;

    /// <summary>
    ///   Occurs when the Image property is changed.
    /// </summary>
    [Category("Property Changed")]
    public event EventHandler ImageChanged;

    /// <summary>
    ///   Occurs when the InterpolationMode property is changed.
    /// </summary>
    [Category("Property Changed")]
    public event EventHandler InterpolationModeChanged;

    /// <summary>
    ///   Occurs when the InvertMouse property is changed.
    /// </summary>
    [Category("Property Changed")]
    public event EventHandler InvertMouseChanged;

    /// <summary>
    ///   Occurs when the LimitSelectionToImage property is changed.
    /// </summary>
    [Category("Property Changed")]
    public event EventHandler LimitSelectionToImageChanged;

    /// <summary>
    ///   Occurs when panning the control completes.
    /// </summary>
    [Category("Property Changed")]
    public event EventHandler PanEnd;

    /// <summary>
    ///   Occurs when panning the control starts.
    /// </summary>
    [Category("Property Changed")]
    public event EventHandler PanStart;

    /// <summary>
    ///   Occurs when the PixelGridColor property value changes
    /// </summary>
    [Category("Property Changed")]
    public event EventHandler PixelGridColorChanged;

    /// <summary>
    /// Occurs when the PixelGridThreshold property value changes
    /// </summary>
    [Category("Property Changed")]
    public event EventHandler PixelGridThresholdChanged;

    /// <summary>
    /// Occurs when the ScaleText property value changes
    /// </summary>
    [Category("Property Changed")]
    public event EventHandler ScaleTextChanged;

    /// <summary>
    ///   Occurs when a selection region has been defined
    /// </summary>
    [Category("Action")]
    public event EventHandler<EventArgs> Selected;

    /// <summary>
    ///   Occurs when the user starts to define a selection region.
    /// </summary>
    [Category("Action")]
    public event EventHandler<ImageBoxCancelEventArgs> Selecting;

    /// <summary>
    ///   Occurs when the SelectionColor property is changed.
    /// </summary>
    [Category("Property Changed")]
    public event EventHandler SelectionColorChanged;

    /// <summary>
    ///   Occurs when the SelectionMode property is changed.
    /// </summary>
    [Category("Property Changed")]
    public event EventHandler SelectionModeChanged;

    /// <summary>
    ///   Occurs when the SelectionRegion property is changed.
    /// </summary>
    [Category("Property Changed")]
    public event EventHandler SelectionRegionChanged;

    /// <summary>
    ///   Occurs when the ShortcutsEnabled property value changes
    /// </summary>
    [Category("Property Changed")]
    public event EventHandler ShortcutsEnabledChanged;

    /// <summary>
    ///   Occurs when the ShowPixelGrid property value changes
    /// </summary>
    [Category("Property Changed")]
    public event EventHandler ShowPixelGridChanged;

    /// <summary>
    /// Occurs when the SizeMode property value changes
    /// </summary>
    [Category("Property Changed")]
    public event EventHandler SizeModeChanged;

    /// <summary>
    ///   Occurs when the SizeToFit property is changed.
    /// </summary>
    [Category("Property Changed")]
    public event EventHandler SizeToFitChanged;

    /// <summary>
    /// Occurs when the TextAlign property value changes
    /// </summary>
    [Category("Property Changed")]
    public event EventHandler TextAlignChanged;

    /// <summary>
    /// Occurs when the TextBackColor property value changes
    /// </summary>
    [Category("Property Changed")]
    public event EventHandler TextBackColorChanged;

    /// <summary>
    /// Occurs when the TextDisplayMode property value changes
    /// </summary>
    [Category("Property Changed")]
    public event EventHandler TextDisplayModeChanged;

    /// <summary>
    ///   Occurs when virtual painting should occur
    /// </summary>
    [Category("Appearance")]
    public event PaintEventHandler VirtualDraw;

    /// <summary>
    ///   Occurs when the VirtualMode property value changes
    /// </summary>
    [Category("Property Changed")]
    public event EventHandler VirtualModeChanged;

    /// <summary>
    ///   Occurs when the VirtualSize property value changes
    /// </summary>
    [Category("Property Changed")]
    public event EventHandler VirtualSizeChanged;

    /// <summary>
    ///   Occurs when the Zoom property is changed.
    /// </summary>
    [Category("Property Changed")]
    public event EventHandler ZoomChanged;

    /// <summary>
    ///   Occurs when the ZoomLevels property is changed
    /// </summary>
    [Category("Property Changed")]
    public event EventHandler ZoomLevelsChanged;

    /// <summary>
    /// Occurs when then a zoom action is performed.
    /// </summary>
    [Category("Action")]
    public event EventHandler<ImageBoxZoomEventArgs> Zoomed;

    #endregion

    #region Class Members

    /// <summary>
    ///   Creates a bitmap image containing a 2x2 grid using the specified cell size and colors.
    /// </summary>
    /// <param name="cellSize">Size of the cell.</param>
    /// <param name="cellColor">Cell color.</param>
    /// <param name="alternateCellColor">Alternate cell color.</param>
    /// <returns></returns>
    public static Bitmap CreateCheckerBoxTile(int cellSize, Color cellColor, Color alternateCellColor)
    {
      Bitmap result;
      int width;
      int height;

      // draw the tile
      width = cellSize * 2;
      height = cellSize * 2;
      result = new Bitmap(width, height);

      using (Graphics g = Graphics.FromImage(result))
      {
        using (Brush brush = new SolidBrush(cellColor))
        {
          g.FillRectangle(brush, new Rectangle(cellSize, 0, cellSize, cellSize));
          g.FillRectangle(brush, new Rectangle(0, cellSize, cellSize, cellSize));
        }

        using (Brush brush = new SolidBrush(alternateCellColor))
        {
          g.FillRectangle(brush, new Rectangle(0, 0, cellSize, cellSize));
          g.FillRectangle(brush, new Rectangle(cellSize, cellSize, cellSize, cellSize));
        }
      }

      return result;
    }

    /// <summary>
    ///   Creates a checked tile texture using default values.
    /// </summary>
    /// <returns></returns>
    public static Bitmap CreateCheckerBoxTile()
    {
      return CreateCheckerBoxTile(8, Color.Gainsboro, Color.WhiteSmoke);
    }

    #endregion

    #region Overridden Properties

    /// <summary>
    ///   Gets or sets a value indicating whether the container enables the user to scroll to any content placed outside of its visible boundaries.
    /// </summary>
    /// <value>
    ///   <c>true</c> if the container enables auto-scrolling; otherwise, <c>false</c>.
    /// </value>
    [DefaultValue(true)]
    public override bool AutoScroll
    {
      get { return base.AutoScroll; }
      set { base.AutoScroll = value; }
    }

    /// <summary>
    ///   Specifies if the control should auto size to fit the image contents.
    /// </summary>
    /// <value></value>
    /// <returns>
    ///   <c>true</c> if enabled; otherwise, <c>false</c>
    /// </returns>
    [Browsable(true)]
    [EditorBrowsable(EditorBrowsableState.Always)]
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
    [DefaultValue(false)]
    public override bool AutoSize
    {
      get { return base.AutoSize; }
      set
      {
        if (base.AutoSize != value)
        {
          base.AutoSize = value;
          this.AdjustLayout();
        }
      }
    }

    /// <summary>
    ///   Gets or sets the background color for the control.
    /// </summary>
    /// <value></value>
    /// <returns>
    ///   A <see cref="T:System.Drawing.Color" /> that represents the background color of the control. The default is the value of the
    ///   <see
    ///     cref="P:System.Windows.Forms.Control.DefaultBackColor" />
    ///   property.
    /// </returns>
    [DefaultValue(typeof(Color), "White")]
    public override Color BackColor
    {
      get { return base.BackColor; }
      set { base.BackColor = value; }
    }

    /// <summary>
    ///   Gets or sets the background image displayed in the control.
    /// </summary>
    /// <value></value>
    /// <returns>
    ///   An <see cref="T:System.Drawing.Image" /> that represents the image to display in the background of the control.
    /// </returns>
    [Browsable(false)]
    [EditorBrowsable(EditorBrowsableState.Never)]
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
    public override Image BackgroundImage
    {
      get { return base.BackgroundImage; }
      set { base.BackgroundImage = value; }
    }

    /// <summary>
    ///   Gets or sets the background image layout as defined in the <see cref="T:System.Windows.Forms.ImageLayout" /> enumeration.
    /// </summary>
    /// <value>The background image layout.</value>
    /// <returns>
    ///   One of the values of <see cref="T:System.Windows.Forms.ImageLayout" /> (
    ///   <see
    ///     cref="F:System.Windows.Forms.ImageLayout.Center" />
    ///   , <see cref="F:System.Windows.Forms.ImageLayout.None" />,
    ///   <see
    ///     cref="F:System.Windows.Forms.ImageLayout.Stretch" />
    ///   , <see cref="F:System.Windows.Forms.ImageLayout.Tile" />, or
    ///   <see
    ///     cref="F:System.Windows.Forms.ImageLayout.Zoom" />
    ///   ). <see cref="F:System.Windows.Forms.ImageLayout.Tile" /> is the default value.
    /// </returns>
    [Browsable(false)]
    [EditorBrowsable(EditorBrowsableState.Never)]
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
    public override ImageLayout BackgroundImageLayout
    {
      get { return base.BackgroundImageLayout; }
      set { base.BackgroundImageLayout = value; }
    }

    #endregion

    #region Overridden Methods

    /// <summary>
    ///   Retrieves the size of a rectangular area into which a control can be fitted.
    /// </summary>
    /// <param name="proposedSize">The custom-sized area for a control.</param>
    /// <returns>
    ///   An ordered pair of type <see cref="T:System.Drawing.Size" /> representing the width and height of a rectangle.
    /// </returns>
    public override Size GetPreferredSize(Size proposedSize)
    {
      Size size;

      if (!this.ViewSize.IsEmpty)
      {
        int width;
        int height;

        // get the size of the image
        width = this.ScaledImageWidth;
        height = this.ScaledImageHeight;

        // add an offset based on padding
        width += this.Padding.Horizontal;
        height += this.Padding.Vertical;

        // add an offset based on the border style
        width += this.GetImageBorderOffset();
        height += this.GetImageBorderOffset();

        size = new Size(width, height);
      }
      else
      {
        size = base.GetPreferredSize(proposedSize);
      }

      return size;
    }

    /// <summary>
    ///   Clean up any resources being used.
    /// </summary>
    /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
    protected override void Dispose(bool disposing)
    {
      if (disposing)
      {
        if (this.IsAnimating)
        {
          ImageAnimator.StopAnimate(this.Image, this.OnFrameChangedHandler);
        }

        if (_texture != null)
        {
          _texture.Dispose();
          _texture = null;
        }

        if (_gridTile != null)
        {
          _gridTile.Dispose();
          _gridTile = null;
        }
      }

      base.Dispose(disposing);
    }

    /// <summary>
    ///   Determines whether the specified key is a regular input key or a special key that requires preprocessing.
    /// </summary>
    /// <param name="keyData">
    ///   One of the <see cref="T:System.Windows.Forms.Keys" /> values.
    /// </param>
    /// <returns>
    ///   true if the specified key is a regular input key; otherwise, false.
    /// </returns>
    protected override bool IsInputKey(Keys keyData)
    {
      bool result;

      if ((keyData & Keys.Right) == Keys.Right | (keyData & Keys.Left) == Keys.Left | (keyData & Keys.Up) == Keys.Up | (keyData & Keys.Down) == Keys.Down)
      {
        result = true;
      }
      else
      {
        result = base.IsInputKey(keyData);
      }

      return result;
    }

    /// <summary>
    ///   Raises the <see cref="System.Windows.Forms.Control.BackColorChanged" /> event.
    /// </summary>
    /// <param name="e">
    ///   An <see cref="T:System.EventArgs" /> that contains the event data.
    /// </param>
    protected override void OnBackColorChanged(EventArgs e)
    {
      base.OnBackColorChanged(e);

      this.Invalidate();
    }

    /// <summary>
    ///   Raises the <see cref="ScrollControl.BorderStyleChanged" /> event.
    /// </summary>
    /// <param name="e">
    ///   The <see cref="EventArgs" /> instance containing the event data.
    /// </param>
    protected override void OnBorderStyleChanged(EventArgs e)
    {
      base.OnBorderStyleChanged(e);

      this.AdjustLayout();
    }

    /// <summary>
    ///   Raises the <see cref="System.Windows.Forms.Control.DockChanged" /> event.
    /// </summary>
    /// <param name="e">
    ///   An <see cref="T:System.EventArgs" /> that contains the event data.
    /// </param>
    protected override void OnDockChanged(EventArgs e)
    {
      base.OnDockChanged(e);

      if (this.Dock != DockStyle.None)
      {
        this.AutoSize = false;
      }
    }

    /// <summary>
    /// Raises the <see cref="E:System.Windows.Forms.Control.FontChanged" /> event.
    /// </summary>
    /// <param name="e">An <see cref="T:System.EventArgs" /> that contains the event data.</param>
    protected override void OnFontChanged(EventArgs e)
    {
      base.OnFontChanged(e);

      this.Invalidate();
    }

    /// <summary>
    /// Raises the <see cref="E:System.Windows.Forms.Control.ForeColorChanged" /> event.
    /// </summary>
    /// <param name="e">An <see cref="T:System.EventArgs" /> that contains the event data.</param>
    protected override void OnForeColorChanged(EventArgs e)
    {
      base.OnForeColorChanged(e);

      this.Invalidate();
    }

    /// <summary>
    ///   Raises the <see cref="System.Windows.Forms.Control.KeyDown" /> event.
    /// </summary>
    /// <param name="e">
    ///   A <see cref="T:System.Windows.Forms.KeyEventArgs" /> that contains the event data.
    /// </param>
    protected override void OnKeyDown(KeyEventArgs e)
    {
      base.OnKeyDown(e);

      this.ProcessScrollingShortcuts(e);

      if (this.ShortcutsEnabled && this.AllowZoom && this.SizeMode == ImageBoxSizeMode.Normal)
      {
        this.ProcessImageShortcuts(e);
      }
    }

    /// <summary>
    ///   Raises the <see cref="System.Windows.Forms.Control.MouseDown" /> event.
    /// </summary>
    /// <param name="e">
    ///   A <see cref="T:System.Windows.Forms.MouseEventArgs" /> that contains the event data.
    /// </param>
    protected override void OnMouseDown(MouseEventArgs e)
    {
      base.OnMouseDown(e);

      if (!this.Focused)
      {
        this.Focus();
      }
    }

    /// <summary>
    ///   Raises the <see cref="System.Windows.Forms.Control.MouseMove" /> event.
    /// </summary>
    /// <param name="e">
    ///   A <see cref="T:System.Windows.Forms.MouseEventArgs" /> that contains the event data.
    /// </param>
    protected override void OnMouseMove(MouseEventArgs e)
    {
      base.OnMouseMove(e);

      if (e.Button == MouseButtons.Left)
      {
        this.ProcessPanning(e);
        this.ProcessSelection(e);
      }
    }

    /// <summary>
    ///   Raises the <see cref="System.Windows.Forms.Control.MouseUp" /> event.
    /// </summary>
    /// <param name="e">
    ///   A <see cref="T:System.Windows.Forms.MouseEventArgs" /> that contains the event data.
    /// </param>
    protected override void OnMouseUp(MouseEventArgs e)
    {
      bool doNotProcessClick;

      base.OnMouseUp(e);

      doNotProcessClick = this.IsPanning || this.IsSelecting;

      if (this.IsPanning)
      {
        this.IsPanning = false;
      }

      if (this.IsSelecting)
      {
        this.EndDrag();
      }
      this.WasDragCancelled = false;

      if (!doNotProcessClick && this.AllowZoom && this.AllowClickZoom && !this.IsPanning && this.SizeMode == ImageBoxSizeMode.Normal)
      {
        if (e.Button == MouseButtons.Left && ModifierKeys == Keys.None)
        {
          this.ProcessMouseZoom(true, e.Location);
        }
        else if (e.Button == MouseButtons.Right || (e.Button == MouseButtons.Left && ModifierKeys != Keys.None))
        {
          this.ProcessMouseZoom(false, e.Location);
        }
      }
    }

    /// <summary>
    ///   Raises the <see cref="System.Windows.Forms.Control.MouseWheel" /> event.
    /// </summary>
    /// <param name="e">
    ///   A <see cref="T:System.Windows.Forms.MouseEventArgs" /> that contains the event data.
    /// </param>
    protected override void OnMouseWheel(MouseEventArgs e)
    {
      base.OnMouseWheel(e);

      if (this.AllowZoom && this.SizeMode == ImageBoxSizeMode.Normal)
      {
        int spins;

        // The MouseWheel event can contain multiple "spins" of the wheel so we need to adjust accordingly
        spins = Math.Abs(e.Delta / SystemInformation.MouseWheelScrollDelta);

        // TODO: Really should update the source method to handle multiple increments rather than calling it multiple times
        for (int i = 0; i < spins; i++)
        {
          this.ProcessMouseZoom(e.Delta > 0, e.Location);
        }
      }
    }

    /// <summary>
    ///   Raises the <see cref="System.Windows.Forms.Control.PaddingChanged" /> event.
    /// </summary>
    /// <param name="e">
    ///   An <see cref="T:System.EventArgs" /> that contains the event data.
    /// </param>
    protected override void OnPaddingChanged(EventArgs e)
    {
      base.OnPaddingChanged(e);
      this.AdjustLayout();
    }

    /// <summary>
    ///   Raises the <see cref="System.Windows.Forms.Control.Paint" /> event.
    /// </summary>
    /// <param name="e">
    ///   A <see cref="T:System.Windows.Forms.PaintEventArgs" /> that contains the event data.
    /// </param>
    protected override void OnPaint(PaintEventArgs e)
    {
      if (this.AllowPainting)
      {
        Rectangle innerRectangle;

        innerRectangle = this.GetInsideViewPort();

        // draw the background
        using (SolidBrush brush = new SolidBrush(this.BackColor))
        {
          e.Graphics.FillRectangle(brush, innerRectangle);
        }

        if (_texture != null && this.GridDisplayMode != ImageBoxGridDisplayMode.None)
        {
          switch (this.GridDisplayMode)
          {
            case ImageBoxGridDisplayMode.Image:
              Rectangle fillRectangle;

              fillRectangle = this.GetImageViewPort();
              e.Graphics.FillRectangle(_texture, fillRectangle);
              break;

            case ImageBoxGridDisplayMode.Client:
              e.Graphics.FillRectangle(_texture, innerRectangle);
              break;
          }
        }

        // draw the image
        if (!this.ViewSize.IsEmpty)
        {
          this.DrawImageBorder(e.Graphics);
        }
        if (this.VirtualMode)
        {
          this.OnVirtualDraw(e);
        }
        else if (this.Image != null)
        {
          this.DrawImage(e.Graphics);
        }

        // draw the grid
        if (this.ShowPixelGrid && !this.VirtualMode)
        {
          this.DrawPixelGrid(e.Graphics);
        }

        // draw the selection
        if (this.SelectionRegion != Rectangle.Empty)
        {
          this.DrawSelection(e);
        }

        // text
        if (!string.IsNullOrEmpty(this.Text) && this.TextDisplayMode != ImageBoxGridDisplayMode.None)
        {
          this.DrawText(e);
        }

        base.OnPaint(e);
      }
    }

    /// <summary>
    ///   Raises the <see cref="System.Windows.Forms.Control.ParentChanged" /> event.
    /// </summary>
    /// <param name="e">
    ///   An <see cref="T:System.EventArgs" /> that contains the event data.
    /// </param>
    protected override void OnParentChanged(EventArgs e)
    {
      base.OnParentChanged(e);
      this.AdjustLayout();
    }

    /// <summary>
    ///   Raises the <see cref="System.Windows.Forms.Control.Resize" /> event.
    /// </summary>
    /// <param name="e">
    ///   An <see cref="T:System.EventArgs" /> that contains the event data.
    /// </param>
    protected override void OnResize(EventArgs e)
    {
      this.AdjustLayout();

      base.OnResize(e);
    }

    /// <summary>
    ///   Raises the <see cref="System.Windows.Forms.ScrollableControl.Scroll" /> event.
    /// </summary>
    /// <param name="se">
    ///   A <see cref="T:System.Windows.Forms.ScrollEventArgs" /> that contains the event data.
    /// </param>
    protected override void OnScroll(ScrollEventArgs se)
    {
      this.Invalidate();

      base.OnScroll(se);
    }

    /// <summary>
    /// Raises the <see cref="E:System.Windows.Forms.Control.TextChanged" /> event.
    /// </summary>
    /// <param name="e">An <see cref="T:System.EventArgs" /> that contains the event data.</param>
    protected override void OnTextChanged(EventArgs e)
    {
      base.OnTextChanged(e);

      this.Invalidate();
    }

    #endregion

    #region Public Properties

    /// <summary>
    ///   Gets or sets a value indicating whether clicking the control with the mouse will automatically zoom in or out.
    /// </summary>
    /// <value>
    ///   <c>true</c> if clicking the control allows zooming; otherwise, <c>false</c>.
    /// </value>
    [DefaultValue(false)]
    [Category("Behavior")]
    public virtual bool AllowClickZoom
    {
      get { return _allowClickZoom; }
      set
      {
        if (_allowClickZoom != value)
        {
          _allowClickZoom = value;
          this.OnAllowClickZoomChanged(EventArgs.Empty);
        }
      }
    }

    /// <summary>
    /// Gets or sets a value indicating whether the DoubleClick event can be raised.
    /// </summary>
    /// <value><c>true</c> if the DoubleClick event can be raised; otherwise, <c>false</c>.</value>
    [Category("Behavior")]
    [DefaultValue(false)]
    public virtual bool AllowDoubleClick
    {
      get { return _allowDoubleClick; }
      set
      {
        if (this.AllowDoubleClick != value)
        {
          _allowDoubleClick = value;

          this.OnAllowDoubleClickChanged(EventArgs.Empty);
        }
      }
    }

    /// <summary>
    ///   Gets or sets a value indicating whether the user can change the zoom level.
    /// </summary>
    /// <value>
    ///   <c>true</c> if the zoom level can be changed; otherwise, <c>false</c>.
    /// </value>
    [Category("Behavior")]
    [DefaultValue(true)]
    public virtual bool AllowZoom
    {
      get { return _allowZoom; }
      set
      {
        if (this.AllowZoom != value)
        {
          _allowZoom = value;

          this.OnAllowZoomChanged(EventArgs.Empty);
        }
      }
    }

    /// <summary>
    ///   Gets or sets a value indicating whether the image is centered where possible.
    /// </summary>
    /// <value>
    ///   <c>true</c> if the image should be centered where possible; otherwise, <c>false</c>.
    /// </value>
    [DefaultValue(true)]
    [Category("Appearance")]
    public virtual bool AutoCenter
    {
      get { return _autoCenter; }
      set
      {
        if (_autoCenter != value)
        {
          _autoCenter = value;
          this.OnAutoCenterChanged(EventArgs.Empty);
        }
      }
    }

    /// <summary>
    ///   Gets or sets if the mouse can be used to pan the control.
    /// </summary>
    /// <value>
    ///   <c>true</c> if the control can be auto panned; otherwise, <c>false</c>.
    /// </value>
    /// <remarks>If this property is set, the SizeToFit property cannot be used.</remarks>
    [DefaultValue(true)]
    [Category("Behavior")]
    public virtual bool AutoPan
    {
      get { return _autoPan; }
      set
      {
        if (_autoPan != value)
        {
          _autoPan = value;
          this.OnAutoPanChanged(EventArgs.Empty);
        }
      }
    }

    /// <summary>
    ///   Gets or sets the minimum size of the auto-scroll.
    /// </summary>
    /// <value></value>
    /// <returns>
    ///   A <see cref="T:System.Drawing.Size" /> that determines the minimum size of the virtual area through which the user can scroll.
    /// </returns>
    [Browsable(false)]
    [EditorBrowsable(EditorBrowsableState.Never)]
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
    public new Size AutoScrollMinSize
    {
      get { return base.AutoScrollMinSize; }
      set { base.AutoScrollMinSize = value; }
    }

    /// <summary>
    /// Gets the point at the center of the currently displayed image viewport.
    /// </summary>
    /// <value>The point at the center of the current image viewport.</value>
    [Browsable(false)]
    public Point CenterPoint
    {
      get
      {
        Rectangle viewport;

        viewport = this.GetImageViewPort();

        return new Point(viewport.Width / 2, viewport.Height / 2);
      }
    }

    /// <summary>
    ///   Gets or sets the size of the drop shadow.
    /// </summary>
    /// <value>The size of the drop shadow.</value>
    [Category("Appearance")]
    [DefaultValue(3)]
    public virtual int DropShadowSize
    {
      get { return _dropShadowSize; }
      set
      {
        if (this.DropShadowSize != value)
        {
          _dropShadowSize = value;

          this.OnDropShadowSizeChanged(EventArgs.Empty);
        }
      }
    }

    /// <summary>
    ///   Gets or sets the size of the grid cells.
    /// </summary>
    /// <value>The size of the grid cells.</value>
    [Category("Appearance")]
    [DefaultValue(8)]
    public virtual int GridCellSize
    {
      get { return _gridCellSize; }
      set
      {
        if (_gridCellSize != value)
        {
          _gridCellSize = value;
          this.OnGridCellSizeChanged(EventArgs.Empty);
        }
      }
    }

    /// <summary>
    ///   Gets or sets the color of primary cells in the grid.
    /// </summary>
    /// <value>The color of primary cells in the grid.</value>
    [Category("Appearance")]
    [DefaultValue(typeof(Color), "Gainsboro")]
    public virtual Color GridColor
    {
      get { return _gridColor; }
      set
      {
        if (_gridColor != value)
        {
          _gridColor = value;
          this.OnGridColorChanged(EventArgs.Empty);
        }
      }
    }

    /// <summary>
    ///   Gets or sets the color of alternate cells in the grid.
    /// </summary>
    /// <value>The color of alternate cells in the grid.</value>
    [Category("Appearance")]
    [DefaultValue(typeof(Color), "White")]
    public virtual Color GridColorAlternate
    {
      get { return _gridColorAlternate; }
      set
      {
        if (_gridColorAlternate != value)
        {
          _gridColorAlternate = value;
          this.OnGridColorAlternateChanged(EventArgs.Empty);
        }
      }
    }

    /// <summary>
    ///   Gets or sets the grid display mode.
    /// </summary>
    /// <value>The grid display mode.</value>
    [DefaultValue(ImageBoxGridDisplayMode.Client)]
    [Category("Appearance")]
    public virtual ImageBoxGridDisplayMode GridDisplayMode
    {
      get { return _gridDisplayMode; }
      set
      {
        if (_gridDisplayMode != value)
        {
          _gridDisplayMode = value;
          this.OnGridDisplayModeChanged(EventArgs.Empty);
        }
      }
    }

    /// <summary>
    ///   Gets or sets the grid scale.
    /// </summary>
    /// <value>The grid scale.</value>
    [DefaultValue(typeof(ImageBoxGridScale), "Small")]
    [Category("Appearance")]
    public virtual ImageBoxGridScale GridScale
    {
      get { return _gridScale; }
      set
      {
        if (_gridScale != value)
        {
          _gridScale = value;
          this.OnGridScaleChanged(EventArgs.Empty);
        }
      }
    }

    /// <summary>
    ///   Gets or sets the image.
    /// </summary>
    /// <value>The image.</value>
    [Category("Appearance")]
    [DefaultValue(null)]
    public virtual Image Image
    {
      get { return _image; }
      set
      {
        if (_image != value)
        {
          // disable animations
          if (this.IsAnimating)
          {
            ImageAnimator.StopAnimate(this.Image, this.OnFrameChangedHandler);
          }

          _image = value;
          this.OnImageChanged(EventArgs.Empty);
        }
      }
    }

    /// <summary>
    ///   Gets or sets the color of the image border.
    /// </summary>
    /// <value>The color of the image border.</value>
    [Category("Appearance")]
    [DefaultValue(typeof(Color), "ControlDark")]
    public virtual Color ImageBorderColor
    {
      get { return _imageBorderColor; }
      set
      {
        if (this.ImageBorderColor != value)
        {
          _imageBorderColor = value;

          this.OnImageBorderColorChanged(EventArgs.Empty);
        }
      }
    }

    /// <summary>
    ///   Gets or sets the image border style.
    /// </summary>
    /// <value>The image border style.</value>
    [Category("Appearance")]
    [DefaultValue(typeof(ImageBoxBorderStyle), "None")]
    public virtual ImageBoxBorderStyle ImageBorderStyle
    {
      get { return _imageBorderStyle; }
      set
      {
        if (this.ImageBorderStyle != value)
        {
          _imageBorderStyle = value;

          this.OnImageBorderStyleChanged(EventArgs.Empty);
        }
      }
    }

    /// <summary>
    ///   Gets or sets the interpolation mode.
    /// </summary>
    /// <value>The interpolation mode.</value>
    [Category("Appearance")]
    [DefaultValue(InterpolationMode.NearestNeighbor)]
    public virtual InterpolationMode InterpolationMode
    {
      get { return _interpolationMode; }
      set
      {
        if (value == InterpolationMode.Invalid)
        {
          value = InterpolationMode.Default;
        }

        if (_interpolationMode != value)
        {
          _interpolationMode = value;
          this.OnInterpolationModeChanged(EventArgs.Empty);
        }
      }
    }

    /// <summary>
    ///   Gets or sets a value indicating whether the mouse should be inverted when panning the control.
    /// </summary>
    /// <value>
    ///   <c>true</c> if the mouse should be inverted when panning the control; otherwise, <c>false</c>.
    /// </value>
    [DefaultValue(false)]
    [Category("Behavior")]
    public virtual bool InvertMouse
    {
      get { return _invertMouse; }
      set
      {
        if (_invertMouse != value)
        {
          _invertMouse = value;
          this.OnInvertMouseChanged(EventArgs.Empty);
        }
      }
    }

    /// <summary>
    /// Gets a value indicating whether the image is currently being displayed at 100% zoom
    /// </summary>
    /// <value><c>true</c> if the image is currently being displayed at 100% zoom; otherwise, <c>false</c>.</value>
    [Browsable(false)]
    public virtual bool IsActualSize
    {
      get { return this.Zoom == 100; }
    }

    /// <summary>
    ///   Gets a value indicating whether this control is panning.
    /// </summary>
    /// <value>
    ///   <c>true</c> if this control is panning; otherwise, <c>false</c>.
    /// </value>
    [DefaultValue(false)]
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
    [Browsable(false)]
    public virtual bool IsPanning
    {
      get { return _isPanning; }
      protected set
      {
        if (_isPanning != value)
        {
          CancelEventArgs args;

          args = new CancelEventArgs();

          if (value)
          {
            this.OnPanStart(args);
          }
          else
          {
            this.OnPanEnd(EventArgs.Empty);
          }

          if (!args.Cancel)
          {
            _isPanning = value;

            if (value)
            {
              _startScrollPosition = this.AutoScrollPosition;
              this.Cursor = Cursors.SizeAll;
            }
            else
            {
              this.Cursor = Cursors.Default;
            }
          }
        }
      }
    }

    /// <summary>
    ///   Gets or sets a value indicating whether this a selection region is currently being drawn.
    /// </summary>
    /// <value>
    ///   <c>true</c> if a selection region is currently being drawn; otherwise, <c>false</c>.
    /// </value>
    [Browsable(false)]
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
    public virtual bool IsSelecting { get; protected set; }

    /// <summary>
    ///   Gets or sets a value indicating whether selection regions should be limited to the image boundaries.
    /// </summary>
    /// <value>
    ///   <c>true</c> if selection regions should be limited to image boundaries; otherwise, <c>false</c>.
    /// </value>
    [Category("Behavior")]
    [DefaultValue(true)]
    public virtual bool LimitSelectionToImage
    {
      get { return _limitSelectionToImage; }
      set
      {
        if (this.LimitSelectionToImage != value)
        {
          _limitSelectionToImage = value;

          this.OnLimitSelectionToImageChanged(EventArgs.Empty);
        }
      }
    }

    /// <summary>
    /// Gets or sets the color of the pixel grid.
    /// </summary>
    /// <value>The color of the pixel grid.</value>
    [Category("Appearance")]
    [DefaultValue(typeof(Color), "DimGray")]
    public virtual Color PixelGridColor
    {
      get { return _pixelGridColor; }
      set
      {
        if (this.PixelGridColor != value)
        {
          _pixelGridColor = value;

          this.OnPixelGridColorChanged(EventArgs.Empty);
        }
      }
    }

    /// <summary>
    /// Gets or sets the minimum size of zoomed pixel's before the pixel grid will be drawn
    /// </summary>
    /// <value>The pixel grid threshold.</value>
    [Category("Behavior")]
    [DefaultValue(5)]
    public virtual int PixelGridThreshold
    {
      get { return _pixelGridThreshold; }
      set
      {
        if (this.PixelGridThreshold != value)
        {
          _pixelGridThreshold = value;

          this.OnPixelGridThresholdChanged(EventArgs.Empty);
        }
      }
    }

    /// <summary>
    /// Gets or sets a value indicating whether the font size of text is scaled according to the current zoom level.
    /// </summary>
    /// <value><c>true</c> if the size of text is scaled according to the current zoom level; otherwise, <c>false</c>.</value>
    [Category("Appearance")]
    [DefaultValue(false)]
    public virtual bool ScaleText
    {
      get { return _scaleText; }
      set
      {
        if (this.ScaleText != value)
        {
          _scaleText = value;

          this.OnScaleTextChanged(EventArgs.Empty);
        }
      }
    }

    /// <summary>
    ///   Gets or sets the color of selection regions.
    /// </summary>
    /// <value>
    ///   The color of selection regions.
    /// </value>
    [Category("Appearance")]
    [DefaultValue(typeof(Color), "Highlight")]
    public virtual Color SelectionColor
    {
      get { return _selectionColor; }
      set
      {
        if (this.SelectionColor != value)
        {
          _selectionColor = value;

          this.OnSelectionColorChanged(EventArgs.Empty);
        }
      }
    }

    /// <summary>
    ///   Gets or sets the selection mode.
    /// </summary>
    /// <value>
    ///   The selection mode.
    /// </value>
    [Category("Behavior")]
    [DefaultValue(typeof(ImageBoxSelectionMode), "None")]
    public virtual ImageBoxSelectionMode SelectionMode
    {
      get { return _selectionMode; }
      set
      {
        if (this.SelectionMode != value)
        {
          _selectionMode = value;

          this.OnSelectionModeChanged(EventArgs.Empty);
        }
      }
    }

    /// <summary>
    ///   Gets or sets the selection region.
    /// </summary>
    /// <value>
    ///   The selection region.
    /// </value>
    [Browsable(false)]
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
    public virtual RectangleF SelectionRegion
    {
      get { return _selectionRegion; }
      set
      {
        if (this.SelectionRegion != value)
        {
          _selectionRegion = value;

          this.OnSelectionRegionChanged(EventArgs.Empty);
        }
      }
    }

    /// <summary>
    ///   Gets or sets a value indicating whether the defined shortcuts are enabled.
    /// </summary>
    /// <value>
    ///   <c>true</c> to enable the shortcuts; otherwise, <c>false</c>.
    /// </value>
    [Category("Behavior")]
    [DefaultValue(true)]
    public virtual bool ShortcutsEnabled
    {
      get { return _shortcutsEnabled; }
      set
      {
        if (this.ShortcutsEnabled != value)
        {
          _shortcutsEnabled = value;

          this.OnShortcutsEnabledChanged(EventArgs.Empty);
        }
      }
    }

    /// <summary>
    /// Gets or sets a value indicating whether a pixel grid is displayed when the control is zoomed.
    /// </summary>
    /// <value><c>true</c> if a pixel grid is displayed when the control is zoomed; otherwise, <c>false</c>.</value>
    [Category("Appearance")]
    [DefaultValue(false)]
    public virtual bool ShowPixelGrid
    {
      get { return _showPixelGrid; }
      set
      {
        if (this.ShowPixelGrid != value)
        {
          _showPixelGrid = value;

          this.OnShowPixelGridChanged(EventArgs.Empty);
        }
      }
    }

    /// <summary>
    /// Gets or sets the size mode of images hosted in the control.
    /// </summary>
    /// <value>The size mode.</value>
    [Category("Behavior")]
    [DefaultValue(typeof(ImageBoxSizeMode), "Normal")]
    public virtual ImageBoxSizeMode SizeMode
    {
      get { return _sizeMode; }
      set
      {
        if (this.SizeMode != value)
        {
          _sizeMode = value;

          this.OnSizeModeChanged(EventArgs.Empty);
        }
      }
    }

    /// <summary>
    ///   Gets or sets a value indicating whether the control should automatically size to fit the image contents.
    /// </summary>
    /// <value>
    ///   <c>true</c> if the control should size to fit the image contents; otherwise, <c>false</c>.
    /// </value>
    [DefaultValue(false)]
    [Category("Appearance")]
    [Browsable(false)]
    [EditorBrowsable(EditorBrowsableState.Never)]
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
    [Obsolete("This property is deprecated and will be removed in a future version of the component. Implementors should use the SizeMode property instead.")]
    public virtual bool SizeToFit
    {
      get { return this.SizeMode == ImageBoxSizeMode.Fit; }
      set
      {
        if ((this.SizeMode == ImageBoxSizeMode.Fit) != value)
        {
          this.SizeMode = value ? ImageBoxSizeMode.Fit : ImageBoxSizeMode.Normal;
          this.OnSizeToFitChanged(EventArgs.Empty);

          if (value)
          {
            this.AutoPan = false;
          }
          else
          {
            this.ActualSize();
          }
        }
      }
    }

    /// <summary>
    /// Gets or sets the alignment of the text on the control.
    /// </summary>
    /// <value>One of the <see cref="ContentAlignment"/> values. The default is <c>MiddleCenter</c>.</value>
    [Category("Appearance")]
    [DefaultValue(typeof(ContentAlignment), "MiddleCenter")]
    public virtual ContentAlignment TextAlign
    {
      get { return _textAlign; }
      set
      {
        if (this.TextAlign != value)
        {
          _textAlign = value;

          this.OnTextAlignChanged(EventArgs.Empty);
        }
      }
    }

    /// <summary>
    /// Gets or sets the color of the text background.
    /// </summary>
    /// <value>The color of the text background.</value>
    [Category("Appearance")]
    [DefaultValue(typeof(Color), "Transparent")]
    public virtual Color TextBackColor
    {
      get { return _textBackColor; }
      set
      {
        if (this.TextBackColor != value)
        {
          _textBackColor = value;

          this.OnTextBackColorChanged(EventArgs.Empty);
        }
      }
    }

    /// <summary>
    /// Gets or sets the text display mode.
    /// </summary>
    /// <value>The text display mode.</value>
    [Category("Appearance")]
    [DefaultValue(typeof(ImageBoxGridDisplayMode), "Client")]
    public virtual ImageBoxGridDisplayMode TextDisplayMode
    {
      get { return _textDisplayMode; }
      set
      {
        if (this.TextDisplayMode != value)
        {
          _textDisplayMode = value;

          this.OnTextDisplayModeChanged(EventArgs.Empty);
        }
      }
    }

    /// <summary>
    ///   Gets or sets a value indicating whether the control acts as a virtual image box.
    /// </summary>
    /// <value>
    ///   <c>true</c> if the control acts as a virtual image box; otherwise, <c>false</c>.
    /// </value>
    /// <remarks>
    ///   When this property is set to <c>true</c>, the Image property is ignored in favor of the VirtualSize property. In addition, the VirtualDraw event is raised to allow custom painting of the image area.
    /// </remarks>
    [Category("Behavior")]
    [DefaultValue(false)]
    public virtual bool VirtualMode
    {
      get { return _virtualMode; }
      set
      {
        if (this.VirtualMode != value)
        {
          _virtualMode = value;

          this.OnVirtualModeChanged(EventArgs.Empty);
        }
      }
    }

    /// <summary>
    ///   Gets or sets the size of the virtual image.
    /// </summary>
    /// <value>The size of the virtual image.</value>
    [Category("Appearance")]
    [DefaultValue(typeof(Size), "0, 0")]
    public virtual Size VirtualSize
    {
      get { return _virtualSize; }
      set
      {
        if (this.VirtualSize != value)
        {
          _virtualSize = value;

          this.OnVirtualSizeChanged(EventArgs.Empty);
        }
      }
    }

    /// <summary>
    ///   Gets or sets the zoom.
    /// </summary>
    /// <value>The zoom.</value>
    [DefaultValue(100)]
    [Category("Appearance")]
    public virtual int Zoom
    {
      get { return _zoom; }
      set { this.SetZoom(value, value > this.Zoom ? ImageBoxZoomActions.ZoomIn : ImageBoxZoomActions.ZoomOut, ImageBoxActionSources.Unknown); }
    }

    /// <summary>
    ///   Gets the zoom factor.
    /// </summary>
    /// <value>The zoom factor.</value>
    [Browsable(false)]
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
    public virtual double ZoomFactor
    {
      get { return (double)this.Zoom / 100; }
    }

    /// <summary>
    ///   Gets or sets the zoom levels.
    /// </summary>
    /// <value>The zoom levels.</value>
    [Browsable(false) /*Category("Behavior"), DefaultValue(typeof(ZoomLevelCollection), "7, 10, 15, 20, 25, 30, 50, 70, 100, 150, 200, 300, 400, 500, 600, 700, 800, 1200, 1600")*/]
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
    public virtual ZoomLevelCollection ZoomLevels
    {
      get { return _zoomLevels; }
      set
      {
        if (this.ZoomLevels != value)
        {
          _zoomLevels = value;

          this.OnZoomLevelsChanged(EventArgs.Empty);
        }
      }
    }

    #endregion

    #region Protected Properties

    /// <summary>
    ///   Gets a value indicating whether painting of the control is allowed.
    /// </summary>
    /// <value>
    ///   <c>true</c> if painting of the control is allowed; otherwise, <c>false</c>.
    /// </value>
    protected virtual bool AllowPainting
    {
      get { return _updateCount == 0; }
    }

    /// <summary>
    /// Gets or sets a value indicating whether the current image is animated.
    /// </summary>
    /// <value><c>true</c> if the current image is animated; otherwise, <c>false</c>.</value>
    protected bool IsAnimating { get; set; }

    /// <summary>
    ///   Gets the height of the scaled image.
    /// </summary>
    /// <value>The height of the scaled image.</value>
    protected virtual int ScaledImageHeight
    {
      get { return Convert.ToInt32(this.ViewSize.Height * this.ZoomFactor); }
    }

    /// <summary>
    ///   Gets the width of the scaled image.
    /// </summary>
    /// <value>The width of the scaled image.</value>
    protected virtual int ScaledImageWidth
    {
      get { return Convert.ToInt32(this.ViewSize.Width * this.ZoomFactor); }
    }

    /// <summary>
    /// Gets the size of the view.
    /// </summary>
    /// <value>The size of the view.</value>
    protected virtual Size ViewSize
    {
      get { return this.VirtualMode ? this.VirtualSize : this.GetImageSize(); }
    }

    /// <summary>
    /// Gets or sets a value indicating whether a drag operation was cancelled.
    /// </summary>
    /// <value><c>true</c> if the drag operation was cancelled; otherwise, <c>false</c>.</value>
    protected bool WasDragCancelled { get; set; }

    #endregion

    #region Public Members

    /// <summary>
    ///   Resets the zoom to 100%.
    /// </summary>
    public virtual void ActualSize()
    {
      this.PerformActualSize(ImageBoxActionSources.Unknown);
    }

    /// <summary>
    ///   Disables any redrawing of the image box
    /// </summary>
    public virtual void BeginUpdate()
    {
      _updateCount++;
    }

    /// <summary>
    ///   Centers the given point in the image in the center of the control
    /// </summary>
    /// <param name="imageLocation">The point of the image to attempt to center.</param>
    public virtual void CenterAt(Point imageLocation)
    {
      this.ScrollTo(imageLocation, new Point(this.ClientSize.Width / 2, this.ClientSize.Height / 2));
    }

    /// <summary>
    ///   Centers the given point in the image in the center of the control
    /// </summary>
    /// <param name="x">The X co-ordinate of the point to center.</param>
    /// <param name="y">The Y co-ordinate of the point to center.</param>
    public void CenterAt(int x, int y)
    {
      this.CenterAt(new Point(x, y));
    }

    /// <summary>
    ///   Centers the given point in the image in the center of the control
    /// </summary>
    /// <param name="x">The X co-ordinate of the point to center.</param>
    /// <param name="y">The Y co-ordinate of the point to center.</param>
    public void CenterAt(float x, float y)
    {
      this.CenterAt(new Point((int)x, (int)y));
    }

    /// <summary>
    /// Resets the viewport to show the center of the image.
    /// </summary>
    public virtual void CenterToImage()
    {
      this.AutoScrollPosition = new Point((this.AutoScrollMinSize.Width - this.ClientSize.Width) / 2, (this.AutoScrollMinSize.Height - this.ClientSize.Height) / 2);
    }

    /// <summary>
    ///   Enables the redrawing of the image box
    /// </summary>
    public virtual void EndUpdate()
    {
      if (_updateCount > 0)
      {
        _updateCount--;
      }

      if (this.AllowPainting)
      {
        this.Invalidate();
      }
    }

    /// <summary>
    ///   Fits a given <see cref="T:System.Drawing.Rectangle" /> to match image boundaries
    /// </summary>
    /// <param name="rectangle">The rectangle.</param>
    /// <returns>
    ///   A <see cref="T:System.Drawing.Rectangle" /> structure remapped to fit the image boundaries
    /// </returns>
    public Rectangle FitRectangle(Rectangle rectangle)
    {
      int x;
      int y;
      int w;
      int h;

      x = rectangle.X;
      y = rectangle.Y;
      w = rectangle.Width;
      h = rectangle.Height;

      if (x < 0)
      {
        x = 0;
      }

      if (y < 0)
      {
        y = 0;
      }

      if (x + w > this.ViewSize.Width)
      {
        w = this.ViewSize.Width - x;
      }

      if (y + h > this.ViewSize.Height)
      {
        h = this.ViewSize.Height - y;
      }

      return new Rectangle(x, y, w, h);
    }

    /// <summary>
    ///   Fits a given <see cref="T:System.Drawing.RectangleF" /> to match image boundaries
    /// </summary>
    /// <param name="rectangle">The rectangle.</param>
    /// <returns>
    ///   A <see cref="T:System.Drawing.RectangleF" /> structure remapped to fit the image boundaries
    /// </returns>
    public RectangleF FitRectangle(RectangleF rectangle)
    {
      float x;
      float y;
      float w;
      float h;

      x = rectangle.X;
      y = rectangle.Y;
      w = rectangle.Width;
      h = rectangle.Height;

      if (x < 0)
      {
        x = 0;
      }

      if (y < 0)
      {
        y = 0;
      }

      if (x + w > this.ViewSize.Width)
      {
        w = this.ViewSize.Width - x;
      }

      if (y + h > this.ViewSize.Height)
      {
        h = this.ViewSize.Height - y;
      }

      return new RectangleF(x, y, w, h);
    }

    /// <summary>
    ///   Gets the image view port.
    /// </summary>
    /// <returns></returns>
    public virtual Rectangle GetImageViewPort()
    {
      Rectangle viewPort;

      if (!this.ViewSize.IsEmpty)
      {
        Rectangle innerRectangle;
        Point offset;
        int width;
        int height;

        innerRectangle = this.GetInsideViewPort(true);

        if (!this.HScroll && !this.VScroll) // if no scrolling is present, tinker the view port so that the image and any applicable borders all fit inside
        {
          innerRectangle.Inflate(-this.GetImageBorderOffset(), -this.GetImageBorderOffset());
        }

        if (this.SizeMode != ImageBoxSizeMode.Stretch)
        {
          if (this.AutoCenter)
          {
            int x;
            int y;

            x = !this.HScroll ? (innerRectangle.Width - (this.ScaledImageWidth + this.Padding.Horizontal)) / 2 : 0;
            y = !this.VScroll ? (innerRectangle.Height - (this.ScaledImageHeight + this.Padding.Vertical)) / 2 : 0;

            offset = new Point(x, y);
          }
          else
          {
            offset = Point.Empty;
          }

          width = Math.Min(this.ScaledImageWidth - Math.Abs(this.AutoScrollPosition.X), innerRectangle.Width);
          height = Math.Min(this.ScaledImageHeight - Math.Abs(this.AutoScrollPosition.Y), innerRectangle.Height);
        }
        else
        {
          offset = Point.Empty;
          width = innerRectangle.Width;
          height = innerRectangle.Height;
        }

        viewPort = new Rectangle(offset.X + innerRectangle.Left, offset.Y + innerRectangle.Top, width, height);
      }
      else
      {
        viewPort = Rectangle.Empty;
      }

      return viewPort;
    }

    /// <summary>
    ///   Gets the inside view port, excluding any padding.
    /// </summary>
    /// <returns></returns>
    public Rectangle GetInsideViewPort()
    {
      return this.GetInsideViewPort(false);
    }

    /// <summary>
    ///   Gets the inside view port.
    /// </summary>
    /// <param name="includePadding">
    ///   if set to <c>true</c> [include padding].
    /// </param>
    /// <returns></returns>
    public virtual Rectangle GetInsideViewPort(bool includePadding)
    {
      int left;
      int top;
      int width;
      int height;

      left = 0;
      top = 0;
      width = this.ClientSize.Width;
      height = this.ClientSize.Height;

      if (includePadding)
      {
        left += this.Padding.Left;
        top += this.Padding.Top;
        width -= this.Padding.Horizontal;
        height -= this.Padding.Vertical;
      }

      return new Rectangle(left, top, width, height);
    }

    /// <summary>
    ///   Returns the source <see cref="T:System.Drawing.Point" /> repositioned to include the current image offset and scaled by the current zoom level
    /// </summary>
    /// <param name="source">The source <see cref="Point"/> to offset.</param>
    /// <returns>A <see cref="Point"/> which has been repositioned to match the current zoom level and image offset</returns>
    public virtual Point GetOffsetPoint(Point source)
    {
      PointF offset;

      offset = this.GetOffsetPoint(new PointF(source.X, source.Y));

      return new Point((int)offset.X, (int)offset.Y);
    }

    /// <summary>
    ///   Returns the source co-ordinates repositioned to include the current image offset and scaled by the current zoom level
    /// </summary>
    /// <param name="x">The source X co-ordinate.</param>
    /// <param name="y">The source Y co-ordinate.</param>
    /// <returns>A <see cref="Point"/> which has been repositioned to match the current zoom level and image offset</returns>
    public Point GetOffsetPoint(int x, int y)
    {
      return this.GetOffsetPoint(new Point(x, y));
    }

    /// <summary>
    ///   Returns the source co-ordinates repositioned to include the current image offset and scaled by the current zoom level
    /// </summary>
    /// <param name="x">The source X co-ordinate.</param>
    /// <param name="y">The source Y co-ordinate.</param>
    /// <returns>A <see cref="Point"/> which has been repositioned to match the current zoom level and image offset</returns>
    public PointF GetOffsetPoint(float x, float y)
    {
      return this.GetOffsetPoint(new PointF(x, y));
    }

    /// <summary>
    ///   Returns the source <see cref="T:System.Drawing.PointF" /> repositioned to include the current image offset and scaled by the current zoom level
    /// </summary>
    /// <param name="source">The source <see cref="PointF"/> to offset.</param>
    /// <returns>A <see cref="PointF"/> which has been repositioned to match the current zoom level and image offset</returns>
    public virtual PointF GetOffsetPoint(PointF source)
    {
      Rectangle viewport;
      PointF scaled;
      int offsetX;
      int offsetY;

      viewport = this.GetImageViewPort();
      scaled = this.GetScaledPoint(source);
      offsetX = viewport.Left + this.Padding.Left + this.AutoScrollPosition.X;
      offsetY = viewport.Top + this.Padding.Top + this.AutoScrollPosition.Y;

      return new PointF(scaled.X + offsetX, scaled.Y + offsetY);
    }

    /// <summary>
    ///   Returns the source <see cref="T:System.Drawing.RectangleF" /> scaled according to the current zoom level and repositioned to include the current image offset
    /// </summary>
    /// <param name="source">The source <see cref="RectangleF"/> to offset.</param>
    /// <returns>A <see cref="RectangleF"/> which has been resized and repositioned to match the current zoom level and image offset</returns>
    public virtual RectangleF GetOffsetRectangle(RectangleF source)
    {
      RectangleF viewport;
      RectangleF scaled;
      float offsetX;
      float offsetY;

      viewport = this.GetImageViewPort();
      scaled = this.GetScaledRectangle(source);
      offsetX = viewport.Left + this.Padding.Left + this.AutoScrollPosition.X;
      offsetY = viewport.Top + this.Padding.Top + this.AutoScrollPosition.Y;

      return new RectangleF(new PointF(scaled.Left + offsetX, scaled.Top + offsetY), scaled.Size);
    }

    /// <summary>
    ///   Returns the source rectangle scaled according to the current zoom level and repositioned to include the current image offset
    /// </summary>
    /// <param name="x">The X co-ordinate of the source rectangle.</param>
    /// <param name="y">The Y co-ordinate of the source rectangle.</param>
    /// <param name="width">The width of the rectangle.</param>
    /// <param name="height">The height of the rectangle.</param>
    /// <returns>A <see cref="Rectangle"/> which has been resized and repositioned to match the current zoom level and image offset</returns>
    public Rectangle GetOffsetRectangle(int x, int y, int width, int height)
    {
      return this.GetOffsetRectangle(new Rectangle(x, y, width, height));
    }

    /// <summary>
    ///   Returns the source rectangle scaled according to the current zoom level and repositioned to include the current image offset
    /// </summary>
    /// <param name="x">The X co-ordinate of the source rectangle.</param>
    /// <param name="y">The Y co-ordinate of the source rectangle.</param>
    /// <param name="width">The width of the rectangle.</param>
    /// <param name="height">The height of the rectangle.</param>
    /// <returns>A <see cref="RectangleF"/> which has been resized and repositioned to match the current zoom level and image offset</returns>
    public RectangleF GetOffsetRectangle(float x, float y, float width, float height)
    {
      return this.GetOffsetRectangle(new RectangleF(x, y, width, height));
    }

    /// <summary>
    ///   Returns the source <see cref="T:System.Drawing.Rectangle" /> scaled according to the current zoom level and repositioned to include the current image offset
    /// </summary>
    /// <param name="source">The source <see cref="Rectangle"/> to offset.</param>
    /// <returns>A <see cref="Rectangle"/> which has been resized and repositioned to match the current zoom level and image offset</returns>
    public virtual Rectangle GetOffsetRectangle(Rectangle source)
    {
      Rectangle viewport;
      Rectangle scaled;
      int offsetX;
      int offsetY;

      viewport = this.GetImageViewPort();
      scaled = this.GetScaledRectangle(source);
      offsetX = viewport.Left + this.Padding.Left + this.AutoScrollPosition.X;
      offsetY = viewport.Top + this.Padding.Top + this.AutoScrollPosition.Y;

      return new Rectangle(new Point(scaled.Left + offsetX, scaled.Top + offsetY), scaled.Size);
    }

    /// <summary>
    ///   Returns the source <see cref="T:System.Drawing.Point" /> scaled according to the current zoom level
    /// </summary>
    /// <param name="x">The X co-ordinate of the point to scale.</param>
    /// <param name="y">The Y co-ordinate of the point to scale.</param>
    /// <returns>A <see cref="Point"/> which has been scaled to match the current zoom level</returns>
    public Point GetScaledPoint(int x, int y)
    {
      return this.GetScaledPoint(new Point(x, y));
    }

    /// <summary>
    ///   Returns the source <see cref="T:System.Drawing.Point" /> scaled according to the current zoom level
    /// </summary>
    /// <param name="x">The X co-ordinate of the point to scale.</param>
    /// <param name="y">The Y co-ordinate of the point to scale.</param>
    /// <returns>A <see cref="Point"/> which has been scaled to match the current zoom level</returns>
    public PointF GetScaledPoint(float x, float y)
    {
      return this.GetScaledPoint(new PointF(x, y));
    }

    /// <summary>
    ///   Returns the source <see cref="T:System.Drawing.Point" /> scaled according to the current zoom level
    /// </summary>
    /// <param name="source">The source <see cref="Point"/> to scale.</param>
    /// <returns>A <see cref="Point"/> which has been scaled to match the current zoom level</returns>
    public virtual Point GetScaledPoint(Point source)
    {
      return new Point((int)(source.X * this.ZoomFactor), (int)(source.Y * this.ZoomFactor));
    }

    /// <summary>
    ///   Returns the source <see cref="T:System.Drawing.PointF" /> scaled according to the current zoom level
    /// </summary>
    /// <param name="source">The source <see cref="PointF"/> to scale.</param>
    /// <returns>A <see cref="PointF"/> which has been scaled to match the current zoom level</returns>
    public virtual PointF GetScaledPoint(PointF source)
    {
      return new PointF((float)(source.X * this.ZoomFactor), (float)(source.Y * this.ZoomFactor));
    }

    /// <summary>
    ///   Returns the source rectangle scaled according to the current zoom level
    /// </summary>
    /// <param name="x">The X co-ordinate of the source rectangle.</param>
    /// <param name="y">The Y co-ordinate of the source rectangle.</param>
    /// <param name="width">The width of the rectangle.</param>
    /// <param name="height">The height of the rectangle.</param>
    /// <returns>A <see cref="Rectangle"/> which has been scaled to match the current zoom level</returns>
    public Rectangle GetScaledRectangle(int x, int y, int width, int height)
    {
      return this.GetScaledRectangle(new Rectangle(x, y, width, height));
    }

    /// <summary>
    ///   Returns the source rectangle scaled according to the current zoom level
    /// </summary>
    /// <param name="x">The X co-ordinate of the source rectangle.</param>
    /// <param name="y">The Y co-ordinate of the source rectangle.</param>
    /// <param name="width">The width of the rectangle.</param>
    /// <param name="height">The height of the rectangle.</param>
    /// <returns>A <see cref="RectangleF"/> which has been scaled to match the current zoom level</returns>
    public RectangleF GetScaledRectangle(float x, float y, float width, float height)
    {
      return this.GetScaledRectangle(new RectangleF(x, y, width, height));
    }

    /// <summary>
    ///   Returns the source rectangle scaled according to the current zoom level
    /// </summary>
    /// <param name="location">The location of the source rectangle.</param>
    /// <param name="size">The size of the source rectangle.</param>
    /// <returns>A <see cref="Rectangle"/> which has been scaled to match the current zoom level</returns>
    public Rectangle GetScaledRectangle(Point location, Size size)
    {
      return this.GetScaledRectangle(new Rectangle(location, size));
    }

    /// <summary>
    ///   Returns the source rectangle scaled according to the current zoom level
    /// </summary>
    /// <param name="location">The location of the source rectangle.</param>
    /// <param name="size">The size of the source rectangle.</param>
    /// <returns>A <see cref="Rectangle"/> which has been scaled to match the current zoom level</returns>
    public RectangleF GetScaledRectangle(PointF location, SizeF size)
    {
      return this.GetScaledRectangle(new RectangleF(location, size));
    }

    /// <summary>
    ///   Returns the source <see cref="T:System.Drawing.Rectangle" /> scaled according to the current zoom level
    /// </summary>
    /// <param name="source">The source <see cref="Rectangle"/> to scale.</param>
    /// <returns>A <see cref="Rectangle"/> which has been scaled to match the current zoom level</returns>
    public virtual Rectangle GetScaledRectangle(Rectangle source)
    {
      return new Rectangle((int)(source.Left * this.ZoomFactor), (int)(source.Top * this.ZoomFactor), (int)(source.Width * this.ZoomFactor), (int)(source.Height * this.ZoomFactor));
    }

    /// <summary>
    ///   Returns the source <see cref="T:System.Drawing.RectangleF" /> scaled according to the current zoom level
    /// </summary>
    /// <param name="source">The source <see cref="RectangleF"/> to scale.</param>
    /// <returns>A <see cref="RectangleF"/> which has been scaled to match the current zoom level</returns>
    public virtual RectangleF GetScaledRectangle(RectangleF source)
    {
      return new RectangleF((float)(source.Left * this.ZoomFactor), (float)(source.Top * this.ZoomFactor), (float)(source.Width * this.ZoomFactor), (float)(source.Height * this.ZoomFactor));
    }

    /// <summary>
    ///   Returns the source size scaled according to the current zoom level
    /// </summary>
    /// <param name="width">The width of the size to scale.</param>
    /// <param name="height">The height of the size to scale.</param>
    /// <returns>A <see cref="SizeF"/> which has been resized to match the current zoom level</returns>
    public SizeF GetScaledSize(float width, float height)
    {
      return this.GetScaledSize(new SizeF(width, height));
    }

    /// <summary>
    ///   Returns the source size scaled according to the current zoom level
    /// </summary>
    /// <param name="width">The width of the size to scale.</param>
    /// <param name="height">The height of the size to scale.</param>
    /// <returns>A <see cref="Size"/> which has been resized to match the current zoom level</returns>
    public Size GetScaledSize(int width, int height)
    {
      return this.GetScaledSize(new Size(width, height));
    }

    /// <summary>
    ///   Returns the source <see cref="T:System.Drawing.SizeF" /> scaled according to the current zoom level
    /// </summary>
    /// <param name="source">The source <see cref="SizeF"/> to scale.</param>
    /// <returns>A <see cref="SizeF"/> which has been resized to match the current zoom level</returns>
    public virtual SizeF GetScaledSize(SizeF source)
    {
      return new SizeF((float)(source.Width * this.ZoomFactor), (float)(source.Height * this.ZoomFactor));
    }

    /// <summary>
    ///   Returns the source <see cref="T:System.Drawing.Size" /> scaled according to the current zoom level
    /// </summary>
    /// <param name="source">The source <see cref="Size"/> to scale.</param>
    /// <returns>A <see cref="Size"/> which has been resized to match the current zoom level</returns>
    public virtual Size GetScaledSize(Size source)
    {
      return new Size((int)(source.Width * this.ZoomFactor), (int)(source.Height * this.ZoomFactor));
    }

    /// <summary>
    ///   Creates an image based on the current selection region
    /// </summary>
    /// <returns>An image containing the selection contents if a selection if present, otherwise null</returns>
    /// <remarks>The caller is responsible for disposing of the returned image</remarks>
    public Image GetSelectedImage()
    {
      Image result;

      result = null;

      if (!this.SelectionRegion.IsEmpty)
      {
        Rectangle rect;

        rect = this.FitRectangle(new Rectangle((int)this.SelectionRegion.X, (int)this.SelectionRegion.Y, (int)this.SelectionRegion.Width, (int)this.SelectionRegion.Height));

        if (rect.Width > 0 && rect.Height > 0)
        {
          result = new Bitmap(rect.Width, rect.Height);

          using (Graphics g = Graphics.FromImage(result))
          {
            g.DrawImage(this.Image, new Rectangle(Point.Empty, rect.Size), rect, GraphicsUnit.Pixel);
          }
        }
      }

      return result;
    }

    /// <summary>
    ///   Gets the source image region.
    /// </summary>
    /// <returns></returns>
    public virtual RectangleF GetSourceImageRegion()
    {
      RectangleF region;

      if (!this.ViewSize.IsEmpty)
      {
        if (this.SizeMode != ImageBoxSizeMode.Stretch)
        {
          float sourceLeft;
          float sourceTop;
          float sourceWidth;
          float sourceHeight;
          Rectangle viewPort;

          viewPort = this.GetImageViewPort();
          sourceLeft = (float)(-this.AutoScrollPosition.X / this.ZoomFactor);
          sourceTop = (float)(-this.AutoScrollPosition.Y / this.ZoomFactor);
          sourceWidth = (float)(viewPort.Width / this.ZoomFactor);
          sourceHeight = (float)(viewPort.Height / this.ZoomFactor);

          region = new RectangleF(sourceLeft, sourceTop, sourceWidth, sourceHeight);
        }
        else
        {
          region = new RectangleF(PointF.Empty, this.ViewSize);
        }
      }
      else
      {
        region = RectangleF.Empty;
      }

      return region;
    }

    /// <summary>
    ///   Determines whether the specified point is located within the image view port
    /// </summary>
    /// <param name="point">The point.</param>
    /// <returns>
    ///   <c>true</c> if the specified point is located within the image view port; otherwise, <c>false</c>.
    /// </returns>
    public virtual bool IsPointInImage(Point point)
    {
      return this.GetImageViewPort().Contains(point);
    }

    /// <summary>
    ///   Determines whether the specified point is located within the image view port
    /// </summary>
    /// <param name="x">The X co-ordinate of the point to check.</param>
    /// <param name="y">The Y co-ordinate of the point to check.</param>
    /// <returns>
    ///   <c>true</c> if the specified point is located within the image view port; otherwise, <c>false</c>.
    /// </returns>
    public bool IsPointInImage(int x, int y)
    {
      return this.IsPointInImage(new Point(x, y));
    }

    /// <summary>
    ///   Determines whether the specified point is located within the image view port
    /// </summary>
    /// <param name="x">The X co-ordinate of the point to check.</param>
    /// <param name="y">The Y co-ordinate of the point to check.</param>
    /// <returns>
    ///   <c>true</c> if the specified point is located within the image view port; otherwise, <c>false</c>.
    /// </returns>
    public bool IsPointInImage(float x, float y)
    {
      return this.IsPointInImage(new Point((int)x, (int)y));
    }

    /// <summary>
    ///   Converts the given client size point to represent a coordinate on the source image.
    /// </summary>
    /// <param name="point">The source point.</param>
    /// <returns><c>Point.Empty</c> if the point could not be matched to the source image, otherwise the new translated point</returns>
    public Point PointToImage(Point point)
    {
      return this.PointToImage(point, false);
    }

    /// <summary>
    ///   Converts the given client size point to represent a coordinate on the source image.
    /// </summary>
    /// <param name="x">The X co-ordinate of the point to convert.</param>
    /// <param name="y">The Y co-ordinate of the point to convert.</param>
    /// <returns><c>Point.Empty</c> if the point could not be matched to the source image, otherwise the new translated point</returns>
    public Point PointToImage(float x, float y)
    {
      return this.PointToImage(x, y, false);
    }

    /// <summary>
    ///   Converts the given client size point to represent a coordinate on the source image.
    /// </summary>
    /// <param name="x">The X co-ordinate of the point to convert.</param>
    /// <param name="y">The Y co-ordinate of the point to convert.</param>
    /// <param name="fitToBounds">
    ///   if set to <c>true</c> and the point is outside the bounds of the source image, it will be mapped to the nearest edge.
    /// </param>
    /// <returns><c>Point.Empty</c> if the point could not be matched to the source image, otherwise the new translated point</returns>
    public Point PointToImage(float x, float y, bool fitToBounds)
    {
      return this.PointToImage(new Point((int)x, (int)y), fitToBounds);
    }

    /// <summary>
    ///   Converts the given client size point to represent a coordinate on the source image.
    /// </summary>
    /// <param name="x">The X co-ordinate of the point to convert.</param>
    /// <param name="y">The Y co-ordinate of the point to convert.</param>
    /// <returns><c>Point.Empty</c> if the point could not be matched to the source image, otherwise the new translated point</returns>
    public Point PointToImage(int x, int y)
    {
      return this.PointToImage(x, y, false);
    }

    /// <summary>
    ///   Converts the given client size point to represent a coordinate on the source image.
    /// </summary>
    /// <param name="x">The X co-ordinate of the point to convert.</param>
    /// <param name="y">The Y co-ordinate of the point to convert.</param>
    /// <param name="fitToBounds">
    ///   if set to <c>true</c> and the point is outside the bounds of the source image, it will be mapped to the nearest edge.
    /// </param>
    /// <returns><c>Point.Empty</c> if the point could not be matched to the source image, otherwise the new translated point</returns>
    public Point PointToImage(int x, int y, bool fitToBounds)
    {
      return this.PointToImage(new Point(x, y), fitToBounds);
    }

    /// <summary>
    ///   Converts the given client size point to represent a coordinate on the source image.
    /// </summary>
    /// <param name="point">The source point.</param>
    /// <param name="fitToBounds">
    ///   if set to <c>true</c> and the point is outside the bounds of the source image, it will be mapped to the nearest edge.
    /// </param>
    /// <returns><c>Point.Empty</c> if the point could not be matched to the source image, otherwise the new translated point</returns>
    public virtual Point PointToImage(Point point, bool fitToBounds)
    {
      Rectangle viewport;
      int x;
      int y;

      viewport = this.GetImageViewPort();

      if (viewport.Contains(point) || fitToBounds)
      {
        if (this.AutoScrollPosition != Point.Empty)
        {
          point = new Point(point.X - this.AutoScrollPosition.X, point.Y - this.AutoScrollPosition.Y);
        }

        x = (int)((point.X - viewport.X) / this.ZoomFactor);
        y = (int)((point.Y - viewport.Y) / this.ZoomFactor);

        if (x < 0)
        {
          x = 0;
        }
        else if (x > this.ViewSize.Width)
        {
          x = this.ViewSize.Width;
        }

        if (y < 0)
        {
          y = 0;
        }
        else if (y > this.ViewSize.Height)
        {
          y = this.ViewSize.Height;
        }
      }
      else
      {
        x = 0; // Return Point.Empty if we couldn't match
        y = 0;
      }

      return new Point(x, y);
    }

    /// <summary>
    ///   Scrolls the control to the given point in the image, offset at the specified display point
    /// </summary>
    /// <param name="x">The X co-ordinate of the point to scroll to.</param>
    /// <param name="y">The Y co-ordinate of the point to scroll to.</param>
    /// <param name="relativeX">The X co-ordinate relative to the <c>x</c> parameter.</param>
    /// <param name="relativeY">The Y co-ordinate relative to the <c>y</c> parameter.</param>
    public void ScrollTo(int x, int y, int relativeX, int relativeY)
    {
      this.ScrollTo(new Point(x, y), new Point(relativeX, relativeY));
    }

    /// <summary>
    ///   Scrolls the control to the given point in the image, offset at the specified display point
    /// </summary>
    /// <param name="x">The X co-ordinate of the point to scroll to.</param>
    /// <param name="y">The Y co-ordinate of the point to scroll to.</param>
    /// <param name="relativeX">The X co-ordinate relative to the <c>x</c> parameter.</param>
    /// <param name="relativeY">The Y co-ordinate relative to the <c>y</c> parameter.</param>
    public void ScrollTo(float x, float y, float relativeX, float relativeY)
    {
      this.ScrollTo(new Point((int)x, (int)y), new Point((int)relativeX, (int)relativeY));
    }

    /// <summary>
    ///   Scrolls the control to the given point in the image, offset at the specified display point
    /// </summary>
    /// <param name="imageLocation">The point of the image to attempt to scroll to.</param>
    /// <param name="relativeDisplayPoint">The relative display point to offset scrolling by.</param>
    public virtual void ScrollTo(Point imageLocation, Point relativeDisplayPoint)
    {
      int x;
      int y;

      x = (int)(imageLocation.X * this.ZoomFactor) - relativeDisplayPoint.X;
      y = (int)(imageLocation.Y * this.ZoomFactor) - relativeDisplayPoint.Y;

      this.AutoScrollPosition = new Point(x, y);
    }

    /// <summary>
    ///   Creates a selection region which encompasses the entire image
    /// </summary>
    /// <exception cref="System.InvalidOperationException">Thrown if no image is currently set</exception>
    public virtual void SelectAll()
    {
      this.SelectionRegion = new RectangleF(PointF.Empty, this.ViewSize);
    }

    /// <summary>
    ///   Clears any existing selection region
    /// </summary>
    public virtual void SelectNone()
    {
      this.SelectionRegion = RectangleF.Empty;
    }

    /// <summary>
    ///   Zooms into the image
    /// </summary>
    public virtual void ZoomIn()
    {
      this.PerformZoomIn(ImageBoxActionSources.Unknown);
    }

    /// <summary>
    ///   Zooms out of the image
    /// </summary>
    public virtual void ZoomOut()
    {
      this.PerformZoomOut(ImageBoxActionSources.Unknown);
    }

    /// <summary>
    ///   Zooms to the maximum size for displaying the entire image within the bounds of the control.
    /// </summary>
    public virtual void ZoomToFit()
    {
      if (!this.ViewSize.IsEmpty)
      {
        Rectangle innerRectangle;
        double zoom;
        double aspectRatio;

        this.AutoScrollMinSize = Size.Empty;

        innerRectangle = this.GetInsideViewPort(true);

        if (this.ViewSize.Width > this.ViewSize.Height)
        {
          aspectRatio = (double)innerRectangle.Width / this.ViewSize.Width;
          zoom = aspectRatio * 100.0;

          if (innerRectangle.Height < ((this.ViewSize.Height * zoom) / 100.0))
          {
            aspectRatio = (double)innerRectangle.Height / this.ViewSize.Height;
            zoom = aspectRatio * 100.0;
          }
        }
        else
        {
          aspectRatio = (double)innerRectangle.Height / this.ViewSize.Height;
          zoom = aspectRatio * 100.0;

          if (innerRectangle.Width < ((this.ViewSize.Width * zoom) / 100.0))
          {
            aspectRatio = (double)innerRectangle.Width / this.ViewSize.Width;
            zoom = aspectRatio * 100.0;
          }
        }

        this.Zoom = (int)Math.Round(Math.Floor(zoom));
      }
    }

    /// <summary>
    ///   Adjusts the view port to fit the given region
    /// </summary>
    /// <param name="x">The X co-ordinate of the selection region.</param>
    /// <param name="y">The Y co-ordinate of the selection region.</param>
    /// <param name="width">The width of the selection region.</param>
    /// <param name="height">The height of the selection region.</param>
    public void ZoomToRegion(float x, float y, float width, float height)
    {
      this.ZoomToRegion(new RectangleF(x, y, width, height));
    }

    /// <summary>
    ///   Adjusts the view port to fit the given region
    /// </summary>
    /// <param name="x">The X co-ordinate of the selection region.</param>
    /// <param name="y">The Y co-ordinate of the selection region.</param>
    /// <param name="width">The width of the selection region.</param>
    /// <param name="height">The height of the selection region.</param>
    public void ZoomToRegion(int x, int y, int width, int height)
    {
      this.ZoomToRegion(new RectangleF(x, y, width, height));
    }

    /// <summary>
    ///   Adjusts the view port to fit the given region
    /// </summary>
    /// <param name="rectangle">The rectangle to fit the view port to.</param>
    public virtual void ZoomToRegion(RectangleF rectangle)
    {
      double ratioX;
      double ratioY;
      double zoomFactor;
      int cx;
      int cy;

      ratioX = this.ClientSize.Width / rectangle.Width;
      ratioY = this.ClientSize.Height / rectangle.Height;
      zoomFactor = Math.Min(ratioX, ratioY);
      cx = (int)(rectangle.X + (rectangle.Width / 2));
      cy = (int)(rectangle.Y + (rectangle.Height / 2));

      this.Zoom = (int)(zoomFactor * 100);
      this.CenterAt(new Point(cx, cy));
    }

    #endregion

    #region Protected Members

    /// <summary>
    ///   Adjusts the layout.
    /// </summary>
    protected virtual void AdjustLayout()
    {
      if (this.AllowPainting)
      {
        if (this.AutoSize)
        {
          this.AdjustSize();
        }
        else if (this.SizeMode != ImageBoxSizeMode.Normal)
        {
          this.ZoomToFit();
        }
        else if (this.AutoScroll)
        {
          this.AdjustViewPort();
        }

        this.Invalidate();
      }
    }

    /// <summary>
    ///   Adjusts the scroll.
    /// </summary>
    /// <param name="x">The x.</param>
    /// <param name="y">The y.</param>
    protected virtual void AdjustScroll(int x, int y)
    {
      Point scrollPosition;

      scrollPosition = new Point(this.HorizontalScroll.Value + x, this.VerticalScroll.Value + y);

      this.UpdateScrollPosition(scrollPosition);
    }

    /// <summary>
    ///   Adjusts the size.
    /// </summary>
    protected virtual void AdjustSize()
    {
      if (this.AutoSize && this.Dock == DockStyle.None)
      {
        base.Size = base.PreferredSize;
      }
    }

    /// <summary>
    ///   Adjusts the view port.
    /// </summary>
    protected virtual void AdjustViewPort()
    {
      if (this.AutoScroll && !this.ViewSize.IsEmpty)
      {
        this.AutoScrollMinSize = new Size(this.ScaledImageWidth + this.Padding.Horizontal, this.ScaledImageHeight + this.Padding.Vertical);
      }
    }

    /// <summary>
    ///   Creates the grid tile image.
    /// </summary>
    /// <param name="cellSize">Size of the cell.</param>
    /// <param name="firstColor">The first color.</param>
    /// <param name="secondColor">Color of the second.</param>
    /// <returns></returns>
    protected virtual Bitmap CreateGridTileImage(int cellSize, Color firstColor, Color secondColor)
    {
      float scale;

      // rescale the cell size
      switch (this.GridScale)
      {
        case ImageBoxGridScale.Medium:
          scale = 1.5F;
          break;

        case ImageBoxGridScale.Large:
          scale = 2;
          break;

        case ImageBoxGridScale.Tiny:
          scale = 0.5F;
          break;

        default:
          scale = 1;
          break;
      }

      cellSize = (int)(cellSize * scale);

      return CreateCheckerBoxTile(cellSize, firstColor, secondColor);
    }

    /// <summary>
    ///   Draws a drop shadow.
    /// </summary>
    /// <param name="g">The graphics. </param>
    /// <param name="viewPort"> The view port. </param>
    protected virtual void DrawDropShadow(Graphics g, Rectangle viewPort)
    {
      Rectangle rightEdge;
      Rectangle bottomEdge;

      rightEdge = new Rectangle(viewPort.Right + 1, viewPort.Top + this.DropShadowSize, this.DropShadowSize, viewPort.Height);
      bottomEdge = new Rectangle(viewPort.Left + this.DropShadowSize, viewPort.Bottom + 1, viewPort.Width + 1, this.DropShadowSize);

      using (Brush brush = new SolidBrush(this.ImageBorderColor))
      {
        g.FillRectangles(brush, new[]
                                {
                                  rightEdge, bottomEdge
                                });
      }
    }

    /// <summary>
    ///   Draws a glow shadow.
    /// </summary>
    /// <param name="g">The graphics.</param>
    /// <param name="viewPort">The view port.</param>
    protected virtual void DrawGlowShadow(Graphics g, Rectangle viewPort)
    {
      // Glow code adapted from http://www.codeproject.com/Articles/372743/gGlowBox-Create-a-glow-effect-around-a-focused-con

      g.SetClip(viewPort, CombineMode.Exclude); // make sure the inside glow doesn't appear

      using (GraphicsPath path = new GraphicsPath())
      {
        int glowSize;
        int feather;

        path.AddRectangle(viewPort);
        glowSize = this.DropShadowSize * 3;
        feather = 50;

        for (int i = 1; i <= glowSize; i += 2)
        {
          int alpha;

          alpha = feather - ((feather / glowSize) * i);

          using (Pen pen = new Pen(Color.FromArgb(alpha, this.ImageBorderColor), i)
                           {
                             LineJoin = LineJoin.Round
                           })
          {
            g.DrawPath(pen, path);
          }
        }
      }
    }

    /// <summary>
    ///   Draws the image.
    /// </summary>
    /// <param name="g">The g.</param>
    protected virtual void DrawImage(Graphics g)
    {
      InterpolationMode currentInterpolationMode;
      PixelOffsetMode currentPixelOffsetMode;

      currentInterpolationMode = g.InterpolationMode;
      currentPixelOffsetMode = g.PixelOffsetMode;

      g.InterpolationMode = this.InterpolationMode;

      // disable pixel offsets. Thanks to Rotem for the info.
      // http://stackoverflow.com/questions/14070311/why-is-graphics-drawimage-cropping-part-of-my-image/14070372#14070372
      g.PixelOffsetMode = PixelOffsetMode.HighQuality;

      try
      {
        // Animation. Thanks to teamalpha5441 for the contribution
        if (this.IsAnimating && !this.DesignMode)
        {
          ImageAnimator.UpdateFrames(this.Image);
        }

        g.DrawImage(this.Image, this.GetImageViewPort(), this.GetSourceImageRegion(), GraphicsUnit.Pixel);
      }
      catch (ArgumentException)
      {
        // ignore errors that occur due to the image being disposed
      }
      catch (OutOfMemoryException)
      {
        // also ignore errors that occur due to running out of memory
      }

      g.PixelOffsetMode = currentPixelOffsetMode;
      g.InterpolationMode = currentInterpolationMode;
    }

    /// <summary>
    ///   Draws a border around the image.
    /// </summary>
    /// <param name="graphics"> The graphics. </param>
    protected virtual void DrawImageBorder(Graphics graphics)
    {
      if (this.ImageBorderStyle != ImageBoxBorderStyle.None)
      {
        Rectangle viewPort;

        graphics.SetClip(this.GetInsideViewPort()); // make sure the image border doesn't overwrite the control border

        viewPort = this.GetImageViewPort();
        viewPort = new Rectangle(viewPort.Left - 1, viewPort.Top - 1, viewPort.Width + 1, viewPort.Height + 1);

        using (Pen borderPen = new Pen(this.ImageBorderColor))
        {
          graphics.DrawRectangle(borderPen, viewPort);
        }

        switch (this.ImageBorderStyle)
        {
          case ImageBoxBorderStyle.FixedSingleDropShadow:
            this.DrawDropShadow(graphics, viewPort);
            break;
          case ImageBoxBorderStyle.FixedSingleGlowShadow:
            this.DrawGlowShadow(graphics, viewPort);
            break;
        }

        graphics.ResetClip();
      }
    }

    /// <summary>
    /// Draws the specified text within the specified bounds using the specified device context.
    /// </summary>
    /// <param name="graphics">The device context in which to draw the text.</param>
    /// <param name="text">The text to draw.</param>
    /// <param name="bounds">The <see cref="Rectangle"/> that represents the bounds of the text.</param>
    protected void DrawLabel(Graphics graphics, string text, Rectangle bounds)
    {
      this.DrawLabel(graphics, text, this.Font, this.ForeColor, this.TextBackColor, this.TextAlign, bounds);
    }

    /// <summary>
    /// Draws the specified text within the specified bounds using the specified device context and font.
    /// </summary>
    /// <param name="graphics">The device context in which to draw the text.</param>
    /// <param name="text">The text to draw.</param>
    /// <param name="font">The <see cref="Font"/> to apply to the drawn text.</param>
    /// <param name="bounds">The <see cref="Rectangle"/> that represents the bounds of the text.</param>
    protected void DrawLabel(Graphics graphics, string text, Font font, Rectangle bounds)
    {
      this.DrawLabel(graphics, text, font, this.ForeColor, this.TextBackColor, this.TextAlign, bounds);
    }

    /// <summary>
    /// Draws the specified text within the specified bounds using the specified device context, font, and color.
    /// </summary>
    /// <param name="graphics">The device context in which to draw the text.</param>
    /// <param name="text">The text to draw.</param>
    /// <param name="font">The <see cref="Font"/> to apply to the drawn text.</param>
    /// <param name="foreColor">The <see cref="Color"/> to apply to the text.</param>
    /// <param name="bounds">The <see cref="Rectangle"/> that represents the bounds of the text.</param>
    protected void DrawLabel(Graphics graphics, string text, Font font, Color foreColor, Rectangle bounds)
    {
      this.DrawLabel(graphics, text, font, foreColor, this.TextBackColor, this.TextAlign, bounds);
    }

    /// <summary>
    /// Draws the specified text within the specified bounds using the specified device context, font, color, and back color.
    /// </summary>
    /// <param name="graphics">The device context in which to draw the text.</param>
    /// <param name="text">The text to draw.</param>
    /// <param name="font">The <see cref="Font"/> to apply to the drawn text.</param>
    /// <param name="foreColor">The <see cref="Color"/> to apply to the text.</param>
    /// <param name="backColor">The <see cref="Color"/> to apply to the area represented by <c>bounds</c>.</param>
    /// <param name="bounds">The <see cref="Rectangle"/> that represents the bounds of the text.</param>
    protected void DrawLabel(Graphics graphics, string text, Font font, Color foreColor, Color backColor, Rectangle bounds)
    {
      this.DrawLabel(graphics, text, font, foreColor, backColor, this.TextAlign, bounds);
    }

    /// <summary>
    /// Draws the specified text within the specified bounds using the specified device context, font, color, back color, and formatting instructions.
    /// </summary>
    /// <param name="graphics">The device context in which to draw the text.</param>
    /// <param name="text">The text to draw.</param>
    /// <param name="font">The <see cref="Font"/> to apply to the drawn text.</param>
    /// <param name="foreColor">The <see cref="Color"/> to apply to the text.</param>
    /// <param name="backColor">The <see cref="Color"/> to apply to the area represented by <c>bounds</c>.</param>
    /// <param name="textAlign">The <see cref="ContentAlignment"/> to apply to the text.</param>
    /// <param name="bounds">The <see cref="Rectangle"/> that represents the bounds of the text.</param>
    protected void DrawLabel(Graphics graphics, string text, Font font, Color foreColor, Color backColor, ContentAlignment textAlign, Rectangle bounds)
    {
      this.DrawLabel(graphics, text, font, foreColor, backColor, textAlign, bounds, this.ScaleText);
    }

    /// <summary>
    /// Draws the specified text within the specified bounds using the specified device context, font, color, back color, and formatting instructions.
    /// </summary>
    /// <param name="graphics">The device context in which to draw the text.</param>
    /// <param name="text">The text to draw.</param>
    /// <param name="font">The <see cref="Font"/> to apply to the drawn text.</param>
    /// <param name="foreColor">The <see cref="Color"/> to apply to the text.</param>
    /// <param name="backColor">The <see cref="Color"/> to apply to the area represented by <c>bounds</c>.</param>
    /// <param name="textAlign">The <see cref="ContentAlignment"/> to apply to the text.</param>
    /// <param name="bounds">The <see cref="Rectangle"/> that represents the bounds of the text.</param>
    /// <param name="scaleText">If set to <c>true</c> the font size is scaled according to the current zoom level.</param>
    protected virtual void DrawLabel(Graphics graphics, string text, Font font, Color foreColor, Color backColor, ContentAlignment textAlign, Rectangle bounds, bool scaleText)
    {
      TextFormatFlags flags;

      if (scaleText)
      {
        font = new Font(font.FontFamily, (float)(font.Size * this.ZoomFactor), font.Style);
      }

      flags = TextFormatFlags.NoPrefix | TextFormatFlags.WordEllipsis | TextFormatFlags.WordBreak | TextFormatFlags.NoPadding;

      switch (textAlign)
      {
        case ContentAlignment.TopLeft:
        case ContentAlignment.MiddleLeft:
        case ContentAlignment.BottomLeft:
          flags |= TextFormatFlags.Left;
          break;
        case ContentAlignment.TopRight:
        case ContentAlignment.MiddleRight:
        case ContentAlignment.BottomRight:
          flags |= TextFormatFlags.Right;
          break;
        default:
          flags |= TextFormatFlags.HorizontalCenter;
          break;
      }

      switch (textAlign)
      {
        case ContentAlignment.TopCenter:
        case ContentAlignment.TopLeft:
        case ContentAlignment.TopRight:
          flags |= TextFormatFlags.Top;
          break;
        case ContentAlignment.BottomCenter:
        case ContentAlignment.BottomLeft:
        case ContentAlignment.BottomRight:
          flags |= TextFormatFlags.Bottom;
          break;
        default:
          flags |= TextFormatFlags.VerticalCenter;
          break;
      }

      TextRenderer.DrawText(graphics, text, font, bounds, foreColor, backColor, flags);

      if (scaleText)
      {
        font.Dispose();
      }
    }

    /// <summary>
    ///   Draws a pixel grid.
    /// </summary>
    /// <param name="g">The graphics to draw the grid to.</param>
    protected virtual void DrawPixelGrid(Graphics g)
    {
      float pixelSize;

      pixelSize = (float)this.ZoomFactor;

      if (pixelSize > this.PixelGridThreshold)
      {
        Rectangle viewport;
        float offsetX;
        float offsetY;

        viewport = this.GetImageViewPort();
        offsetX = Math.Abs(this.AutoScrollPosition.X) % pixelSize;
        offsetY = Math.Abs(this.AutoScrollPosition.Y) % pixelSize;

        using (Pen pen = new Pen(this.PixelGridColor)
                         {
                           DashStyle = DashStyle.Dot
                         })
        {
          for (float x = viewport.Left + pixelSize - offsetX; x < viewport.Right; x += pixelSize)
          {
            g.DrawLine(pen, x, viewport.Top, x, viewport.Bottom);
          }

          for (float y = viewport.Top + pixelSize - offsetY; y < viewport.Bottom; y += pixelSize)
          {
            g.DrawLine(pen, viewport.Left, y, viewport.Right, y);
          }

          g.DrawRectangle(pen, viewport);
        }
      }
    }

    /// <summary>
    ///   Draws the selection region.
    /// </summary>
    /// <param name="e">
    ///   The <see cref="System.Windows.Forms.PaintEventArgs" /> instance containing the event data.
    /// </param>
    protected virtual void DrawSelection(PaintEventArgs e)
    {
      RectangleF rect;

      e.Graphics.SetClip(this.GetInsideViewPort(true));

      rect = this.GetOffsetRectangle(this.SelectionRegion);

      using (Brush brush = new SolidBrush(Color.FromArgb(128, this.SelectionColor)))
      {
        e.Graphics.FillRectangle(brush, rect);
      }

      using (Pen pen = new Pen(this.SelectionColor))
      {
        e.Graphics.DrawRectangle(pen, rect.X, rect.Y, rect.Width, rect.Height);
      }

      e.Graphics.ResetClip();
    }

    /// <summary>
    /// Draws the text.
    /// </summary>
    /// <param name="e">The <see cref="PaintEventArgs"/> instance containing the event data.</param>
    protected virtual void DrawText(PaintEventArgs e)
    {
      Rectangle bounds;

      bounds = this.TextDisplayMode == ImageBoxGridDisplayMode.Client ? this.GetInsideViewPort() : this.GetImageViewPort();

      this.DrawLabel(e.Graphics, this.Text, this.Font, this.ForeColor, this.TextBackColor, this.TextAlign, bounds, this.ScaleText);
    }

    /// <summary>
    /// Completes an ongoing selection or drag operation.
    /// </summary>
    protected virtual void EndDrag()
    {
      this.IsSelecting = false;
      this.OnSelected(EventArgs.Empty);
    }

    /// <summary>
    ///   Gets an offset based on the current image border style.
    /// </summary>
    /// <returns></returns>
    protected virtual int GetImageBorderOffset()
    {
      int offset;

      switch (this.ImageBorderStyle)
      {
        case ImageBoxBorderStyle.FixedSingle:
          offset = 1;
          break;

        case ImageBoxBorderStyle.FixedSingleDropShadow:
          offset = (this.DropShadowSize + 1);
          break;
        default:
          offset = 0;
          break;
      }

      return offset;
    }

    /// <summary>
    ///   Raises the <see cref="AllowClickZoomChanged" /> event.
    /// </summary>
    /// <param name="e">
    ///   The <see cref="System.EventArgs" /> instance containing the event data.
    /// </param>
    protected virtual void OnAllowClickZoomChanged(EventArgs e)
    {
      EventHandler handler;

      handler = this.AllowClickZoomChanged;

      if (handler != null)
      {
        handler(this, e);
      }
    }

    /// <summary>
    ///   Raises the <see cref="AllowDoubleClickChanged" /> event.
    /// </summary>
    /// <param name="e">
    ///   The <see cref="EventArgs" /> instance containing the event data.
    /// </param>
    protected virtual void OnAllowDoubleClickChanged(EventArgs e)
    {
      EventHandler handler;

      this.SetStyle(ControlStyles.StandardDoubleClick, this.AllowDoubleClick);

      handler = this.AllowDoubleClickChanged;

      if (handler != null)
      {
        handler(this, e);
      }
    }

    /// <summary>
    ///   Raises the <see cref="AllowZoomChanged" /> event.
    /// </summary>
    /// <param name="e">
    ///   The <see cref="System.EventArgs" /> instance containing the event data.
    /// </param>
    protected virtual void OnAllowZoomChanged(EventArgs e)
    {
      EventHandler handler;

      handler = this.AllowZoomChanged;

      if (handler != null)
      {
        handler(this, e);
      }
    }

    /// <summary>
    ///   Raises the <see cref="AutoCenterChanged" /> event.
    /// </summary>
    /// <param name="e">
    ///   The <see cref="System.EventArgs" /> instance containing the event data.
    /// </param>
    protected virtual void OnAutoCenterChanged(EventArgs e)
    {
      EventHandler handler;

      this.Invalidate();

      handler = this.AutoCenterChanged;

      if (handler != null)
      {
        handler(this, e);
      }
    }

    /// <summary>
    ///   Raises the <see cref="AutoPanChanged" /> event.
    /// </summary>
    /// <param name="e">
    ///   The <see cref="System.EventArgs" /> instance containing the event data.
    /// </param>
    protected virtual void OnAutoPanChanged(EventArgs e)
    {
      EventHandler handler;

      handler = this.AutoPanChanged;

      if (handler != null)
      {
        handler(this, e);
      }
    }

    /// <summary>
    ///   Raises the <see cref="DropShadowSizeChanged" /> event.
    /// </summary>
    /// <param name="e">
    ///   The <see cref="EventArgs" /> instance containing the event data.
    /// </param>
    protected virtual void OnDropShadowSizeChanged(EventArgs e)
    {
      this.Invalidate();

      EventHandler handler;

      handler = this.DropShadowSizeChanged;

      if (handler != null)
      {
        handler(this, e);
      }
    }

    /// <summary>
    ///   Raises the <see cref="GridCellSizeChanged" /> event.
    /// </summary>
    /// <param name="e">
    ///   The <see cref="System.EventArgs" /> instance containing the event data.
    /// </param>
    protected virtual void OnGridCellSizeChanged(EventArgs e)
    {
      EventHandler handler;

      this.InitializeGridTile();

      handler = this.GridCellSizeChanged;

      if (handler != null)
      {
        handler(this, e);
      }
    }

    /// <summary>
    ///   Raises the <see cref="GridColorAlternateChanged" /> event.
    /// </summary>
    /// <param name="e">
    ///   The <see cref="System.EventArgs" /> instance containing the event data.
    /// </param>
    protected virtual void OnGridColorAlternateChanged(EventArgs e)
    {
      EventHandler handler;

      this.InitializeGridTile();

      handler = this.GridColorAlternateChanged;

      if (handler != null)
      {
        handler(this, e);
      }
    }

    /// <summary>
    ///   Raises the <see cref="GridColorChanged" /> event.
    /// </summary>
    /// <param name="e">
    ///   The <see cref="System.EventArgs" /> instance containing the event data.
    /// </param>
    protected virtual void OnGridColorChanged(EventArgs e)
    {
      EventHandler handler;

      this.InitializeGridTile();

      handler = this.GridColorChanged;

      if (handler != null)
      {
        handler(this, e);
      }
    }

    /// <summary>
    ///   Raises the <see cref="GridDisplayModeChanged" /> event.
    /// </summary>
    /// <param name="e">
    ///   The <see cref="System.EventArgs" /> instance containing the event data.
    /// </param>
    protected virtual void OnGridDisplayModeChanged(EventArgs e)
    {
      EventHandler handler;

      this.InitializeGridTile();
      this.Invalidate();

      handler = this.GridDisplayModeChanged;

      if (handler != null)
      {
        handler(this, e);
      }
    }

    /// <summary>
    ///   Raises the <see cref="GridScaleChanged" /> event.
    /// </summary>
    /// <param name="e">
    ///   The <see cref="System.EventArgs" /> instance containing the event data.
    /// </param>
    protected virtual void OnGridScaleChanged(EventArgs e)
    {
      EventHandler handler;

      this.InitializeGridTile();

      handler = this.GridScaleChanged;

      if (handler != null)
      {
        handler(this, e);
      }
    }

    /// <summary>
    ///   Raises the <see cref="ImageBorderColorChanged" /> event.
    /// </summary>
    /// <param name="e">
    ///   The <see cref="EventArgs" /> instance containing the event data.
    /// </param>
    protected virtual void OnImageBorderColorChanged(EventArgs e)
    {
      EventHandler handler;

      this.Invalidate();

      handler = this.ImageBorderColorChanged;

      if (handler != null)
      {
        handler(this, e);
      }
    }

    /// <summary>
    ///   Raises the <see cref="ImageBorderStyleChanged" /> event.
    /// </summary>
    /// <param name="e">
    ///   The <see cref="EventArgs" /> instance containing the event data.
    /// </param>
    protected virtual void OnImageBorderStyleChanged(EventArgs e)
    {
      EventHandler handler;

      this.Invalidate();

      handler = this.ImageBorderStyleChanged;

      if (handler != null)
      {
        handler(this, e);
      }
    }

    /// <summary>
    ///   Raises the <see cref="ImageChanged" /> event.
    /// </summary>
    /// <param name="e">
    ///   The <see cref="System.EventArgs" /> instance containing the event data.
    /// </param>
    protected virtual void OnImageChanged(EventArgs e)
    {
      EventHandler handler;

      try
      {
        this.IsAnimating = ImageAnimator.CanAnimate(this.Image);
        if (this.IsAnimating)
        {
          ImageAnimator.Animate(this.Image, this.OnFrameChangedHandler);
        }
      }
      catch (ArgumentException)
      {
        // probably a disposed image, ignore
      }

      this.AdjustLayout();

      handler = this.ImageChanged;

      if (handler != null)
      {
        handler(this, e);
      }
    }

    /// <summary>
    ///   Raises the <see cref="InterpolationModeChanged" /> event.
    /// </summary>
    /// <param name="e">
    ///   The <see cref="System.EventArgs" /> instance containing the event data.
    /// </param>
    protected virtual void OnInterpolationModeChanged(EventArgs e)
    {
      EventHandler handler;

      this.Invalidate();

      handler = this.InterpolationModeChanged;

      if (handler != null)
      {
        handler(this, e);
      }
    }

    /// <summary>
    ///   Raises the <see cref="InvertMouseChanged" /> event.
    /// </summary>
    /// <param name="e">
    ///   The <see cref="System.EventArgs" /> instance containing the event data.
    /// </param>
    protected virtual void OnInvertMouseChanged(EventArgs e)
    {
      EventHandler handler;

      handler = this.InvertMouseChanged;

      if (handler != null)
      {
        handler(this, e);
      }
    }

    /// <summary>
    ///   Raises the <see cref="LimitSelectionToImageChanged" /> event.
    /// </summary>
    /// <param name="e">
    ///   The <see cref="System.EventArgs" /> instance containing the event data.
    /// </param>
    protected virtual void OnLimitSelectionToImageChanged(EventArgs e)
    {
      EventHandler handler;

      handler = this.LimitSelectionToImageChanged;

      if (handler != null)
      {
        handler(this, e);
      }
    }

    /// <summary>
    ///   Raises the <see cref="PanEnd" /> event.
    /// </summary>
    /// <param name="e">
    ///   The <see cref="System.EventArgs" /> instance containing the event data.
    /// </param>
    protected virtual void OnPanEnd(EventArgs e)
    {
      EventHandler handler;

      handler = this.PanEnd;

      if (handler != null)
      {
        handler(this, e);
      }
    }

    /// <summary>
    ///   Raises the <see cref="PanStart" /> event.
    /// </summary>
    /// <param name="e">
    ///   The <see cref="System.ComponentModel.CancelEventArgs" /> instance containing the event data.
    /// </param>
    protected virtual void OnPanStart(CancelEventArgs e)
    {
      EventHandler handler;

      handler = this.PanStart;

      if (handler != null)
      {
        handler(this, e);
      }
    }

    /// <summary>
    ///   Raises the <see cref="PixelGridColorChanged" /> event.
    /// </summary>
    /// <param name="e">
    ///   The <see cref="EventArgs" /> instance containing the event data.
    /// </param>
    protected virtual void OnPixelGridColorChanged(EventArgs e)
    {
      EventHandler handler;

      this.Invalidate();

      handler = this.PixelGridColorChanged;

      if (handler != null)
      {
        handler(this, e);
      }
    }

    /// <summary>
    /// Raises the <see cref="PixelGridThresholdChanged" /> event.
    /// </summary>
    /// <param name="e">The <see cref="EventArgs" /> instance containing the event data.</param>
    protected virtual void OnPixelGridThresholdChanged(EventArgs e)
    {
      EventHandler handler;

      handler = this.PixelGridThresholdChanged;

      if (handler != null)
      {
        handler(this, e);
      }
    }

    /// <summary>
    /// Raises the <see cref="ScaleTextChanged" /> event.
    /// </summary>
    /// <param name="e">The <see cref="EventArgs" /> instance containing the event data.</param>
    protected virtual void OnScaleTextChanged(EventArgs e)
    {
      EventHandler handler;

      this.Invalidate();

      handler = this.ScaleTextChanged;

      if (handler != null)
      {
        handler(this, e);
      }
    }

    /// <summary>
    ///   Raises the <see cref="Selected" /> event.
    /// </summary>
    /// <param name="e">
    ///   The <see cref="System.EventArgs" /> instance containing the event data.
    /// </param>
    protected virtual void OnSelected(EventArgs e)
    {
      EventHandler<EventArgs> handler;

      switch (this.SelectionMode)
      {
        case ImageBoxSelectionMode.Zoom:
          if (this.SelectionRegion.Width > SelectionDeadZone && this.SelectionRegion.Height > SelectionDeadZone)
          {
            this.ZoomToRegion(this.SelectionRegion);
            this.SelectNone();
          }
          break;
      }

      handler = this.Selected;

      if (handler != null)
      {
        handler(this, e);
      }
    }

    /// <summary>
    ///   Raises the <see cref="Selecting" /> event.
    /// </summary>
    /// <param name="e">
    ///   The <see cref="System.EventArgs" /> instance containing the event data.
    /// </param>
    protected virtual void OnSelecting(ImageBoxCancelEventArgs e)
    {
      EventHandler<ImageBoxCancelEventArgs> handler;

      handler = this.Selecting;

      if (handler != null)
      {
        handler(this, e);
      }
    }

    /// <summary>
    ///   Raises the <see cref="SelectionColorChanged" /> event.
    /// </summary>
    /// <param name="e">
    ///   The <see cref="System.EventArgs" /> instance containing the event data.
    /// </param>
    protected virtual void OnSelectionColorChanged(EventArgs e)
    {
      EventHandler handler;

      handler = this.SelectionColorChanged;

      if (handler != null)
      {
        handler(this, e);
      }
    }

    /// <summary>
    ///   Raises the <see cref="SelectionModeChanged" /> event.
    /// </summary>
    /// <param name="e">
    ///   The <see cref="System.EventArgs" /> instance containing the event data.
    /// </param>
    protected virtual void OnSelectionModeChanged(EventArgs e)
    {
      EventHandler handler;

      handler = this.SelectionModeChanged;

      if (handler != null)
      {
        handler(this, e);
      }
    }

    /// <summary>
    ///   Raises the <see cref="SelectionRegionChanged" /> event.
    /// </summary>
    /// <param name="e">
    ///   The <see cref="System.EventArgs" /> instance containing the event data.
    /// </param>
    protected virtual void OnSelectionRegionChanged(EventArgs e)
    {
      EventHandler handler;

      this.Invalidate();

      handler = this.SelectionRegionChanged;

      if (handler != null)
      {
        handler(this, e);
      }
    }

    /// <summary>
    ///   Raises the <see cref="ShortcutsEnabledChanged" /> event.
    /// </summary>
    /// <param name="e">
    ///   The <see cref="EventArgs" /> instance containing the event data.
    /// </param>
    protected virtual void OnShortcutsEnabledChanged(EventArgs e)
    {
      EventHandler handler;

      handler = this.ShortcutsEnabledChanged;

      if (handler != null)
      {
        handler(this, e);
      }
    }

    /// <summary>
    ///   Raises the <see cref="ShowPixelGridChanged" /> event.
    /// </summary>
    /// <param name="e">
    ///   The <see cref="EventArgs" /> instance containing the event data.
    /// </param>
    protected virtual void OnShowPixelGridChanged(EventArgs e)
    {
      EventHandler handler;

      this.Invalidate();

      handler = this.ShowPixelGridChanged;

      if (handler != null)
      {
        handler(this, e);
      }
    }

    /// <summary>
    /// Raises the <see cref="SizeModeChanged" /> event.
    /// </summary>
    /// <param name="e">The <see cref="EventArgs" /> instance containing the event data.</param>
    protected virtual void OnSizeModeChanged(EventArgs e)
    {
      EventHandler handler;

      this.AdjustLayout();

      handler = this.SizeModeChanged;

      if (handler != null)
      {
        handler(this, e);
      }
    }

    /// <summary>
    ///   Raises the <see cref="SizeToFitChanged" /> event.
    /// </summary>
    /// <param name="e">
    ///   The <see cref="System.EventArgs" /> instance containing the event data.
    /// </param>
    protected virtual void OnSizeToFitChanged(EventArgs e)
    {
      EventHandler handler;

      this.AdjustLayout();

      handler = this.SizeToFitChanged;

      if (handler != null)
      {
        handler(this, e);
      }
    }

    /// <summary>
    /// Raises the <see cref="TextAlignChanged" /> event.
    /// </summary>
    /// <param name="e">The <see cref="EventArgs" /> instance containing the event data.</param>
    protected virtual void OnTextAlignChanged(EventArgs e)
    {
      EventHandler handler;

      this.Invalidate();

      handler = this.TextAlignChanged;

      if (handler != null)
      {
        handler(this, e);
      }
    }

    /// <summary>
    /// Raises the <see cref="TextBackColorChanged" /> event.
    /// </summary>
    /// <param name="e">The <see cref="EventArgs" /> instance containing the event data.</param>
    protected virtual void OnTextBackColorChanged(EventArgs e)
    {
      EventHandler handler;

      this.Invalidate();

      handler = this.TextBackColorChanged;

      if (handler != null)
      {
        handler(this, e);
      }
    }

    /// <summary>
    /// Raises the <see cref="TextDisplayModeChanged" /> event.
    /// </summary>
    /// <param name="e">The <see cref="EventArgs" /> instance containing the event data.</param>
    protected virtual void OnTextDisplayModeChanged(EventArgs e)
    {
      EventHandler handler;

      this.Invalidate();

      handler = this.TextDisplayModeChanged;

      if (handler != null)
      {
        handler(this, e);
      }
    }

    /// <summary>
    ///   Raises the <see cref="VirtualDraw" /> event.
    /// </summary>
    /// <param name="e">
    ///   The <see cref="PaintEventArgs" /> instance containing the event data.
    /// </param>
    protected virtual void OnVirtualDraw(PaintEventArgs e)
    {
      PaintEventHandler handler;

      handler = this.VirtualDraw;

      if (handler != null)
      {
        handler(this, e);
      }
    }

    /// <summary>
    ///   Raises the <see cref="VirtualModeChanged" /> event.
    /// </summary>
    /// <param name="e">
    ///   The <see cref="EventArgs" /> instance containing the event data.
    /// </param>
    protected virtual void OnVirtualModeChanged(EventArgs e)
    {
      EventHandler handler;

      this.AdjustLayout();

      handler = this.VirtualModeChanged;

      if (handler != null)
      {
        handler(this, e);
      }
    }

    /// <summary>
    ///   Raises the <see cref="VirtualSizeChanged" /> event.
    /// </summary>
    /// <param name="e">
    ///   The <see cref="EventArgs" /> instance containing the event data.
    /// </param>
    protected virtual void OnVirtualSizeChanged(EventArgs e)
    {
      EventHandler handler;

      this.AdjustLayout();

      handler = this.VirtualSizeChanged;

      if (handler != null)
      {
        handler(this, e);
      }
    }

    /// <summary>
    ///   Raises the <see cref="ZoomChanged" /> event.
    /// </summary>
    /// <param name="e">
    ///   The <see cref="System.EventArgs" /> instance containing the event data.
    /// </param>
    protected virtual void OnZoomChanged(EventArgs e)
    {
      EventHandler handler;

      this.AdjustLayout();

      handler = this.ZoomChanged;

      if (handler != null)
      {
        handler(this, e);
      }
    }

    /// <summary>
    ///   Raises the <see cref="ZoomLevelsChanged" /> event.
    /// </summary>
    /// <param name="e">
    ///   The <see cref="EventArgs" /> instance containing the event data.
    /// </param>
    protected virtual void OnZoomLevelsChanged(EventArgs e)
    {
      EventHandler handler;

      handler = this.ZoomLevelsChanged;

      if (handler != null)
      {
        handler(this, e);
      }
    }

    /// <summary>
    /// Raises the <see cref="Zoomed" /> event.
    /// </summary>
    /// <param name="e">The <see cref="EventArgs" /> instance containing the event data.</param>
    protected virtual void OnZoomed(ImageBoxZoomEventArgs e)
    {
      EventHandler<ImageBoxZoomEventArgs> handler;

      handler = this.Zoomed;

      if (handler != null)
      {
        handler(this, e);
      }
    }

    /// <summary>
    ///   Processes shortcut keys for zooming and selection
    /// </summary>
    /// <param name="e">
    ///   The <see cref="KeyEventArgs" /> instance containing the event data.
    /// </param>
    protected virtual void ProcessImageShortcuts(KeyEventArgs e)
    {
      Point currentPixel;
      int currentZoom;
      Point relativePoint;

      relativePoint = this.CenterPoint;
      currentPixel = this.PointToImage(relativePoint);
      currentZoom = this.Zoom;

      switch (e.KeyCode)
      {
        case Keys.Home:
          if (this.AllowZoom)
          {
            this.PerformActualSize(ImageBoxActionSources.User);
          }
          break;

        case Keys.PageDown:
        case Keys.Oemplus:
          if (this.AllowZoom)
          {
            this.PerformZoomIn(ImageBoxActionSources.User);
          }
          break;

        case Keys.PageUp:
        case Keys.OemMinus:
          if (this.AllowZoom)
          {
            this.PerformZoomOut(ImageBoxActionSources.User);
          }
          break;
      }

      if (this.Zoom != currentZoom)
      {
        this.ScrollTo(currentPixel, relativePoint);
      }
    }

    /// <summary>
    ///   Processes zooming with the mouse. Attempts to keep the pre-zoom image pixel under the mouse after the zoom has completed.
    /// </summary>
    /// <param name="isZoomIn">
    ///   if set to <c>true</c> zoom in, otherwise zoom out.
    /// </param>
    /// <param name="cursorPosition">The cursor position.</param>
    protected virtual void ProcessMouseZoom(bool isZoomIn, Point cursorPosition)
    {
      Point currentPixel;
      int currentZoom;

      currentPixel = this.PointToImage(cursorPosition);
      currentZoom = this.Zoom;

      this.SetZoom(isZoomIn ? this.ZoomLevels.NextZoom(this.Zoom) : this.ZoomLevels.PreviousZoom(this.Zoom), isZoomIn ? ImageBoxZoomActions.ZoomIn : ImageBoxZoomActions.ZoomOut, ImageBoxActionSources.User);

      if (this.Zoom != currentZoom)
      {
        this.ScrollTo(currentPixel, cursorPosition);
      }
    }

    /// <summary>
    ///   Performs mouse based panning
    /// </summary>
    /// <param name="e">
    ///   The <see cref="MouseEventArgs" /> instance containing the event data.
    /// </param>
    protected virtual void ProcessPanning(MouseEventArgs e)
    {
      if (this.AutoPan && !this.ViewSize.IsEmpty && this.SelectionMode == ImageBoxSelectionMode.None)
      {
        if (!this.IsPanning && (this.HScroll | this.VScroll))
        {
          _startMousePosition = e.Location;
          this.IsPanning = true;
        }

        if (this.IsPanning)
        {
          int x;
          int y;
          Point position;

          if (!this.InvertMouse)
          {
            x = -_startScrollPosition.X + (_startMousePosition.X - e.Location.X);
            y = -_startScrollPosition.Y + (_startMousePosition.Y - e.Location.Y);
          }
          else
          {
            x = -(_startScrollPosition.X + (_startMousePosition.X - e.Location.X));
            y = -(_startScrollPosition.Y + (_startMousePosition.Y - e.Location.Y));
          }

          position = new Point(x, y);

          this.UpdateScrollPosition(position);
        }
      }
    }

    /// <summary>
    ///   Processes shortcut keys for scrolling
    /// </summary>
    /// <param name="e">
    ///   The <see cref="KeyEventArgs" /> instance containing the event data.
    /// </param>
    protected virtual void ProcessScrollingShortcuts(KeyEventArgs e)
    {
      switch (e.KeyCode)
      {
        case Keys.Left:
          this.AdjustScroll(-(e.Modifiers == Keys.None ? this.HorizontalScroll.SmallChange : this.HorizontalScroll.LargeChange), 0);
          break;

        case Keys.Right:
          this.AdjustScroll(e.Modifiers == Keys.None ? this.HorizontalScroll.SmallChange : this.HorizontalScroll.LargeChange, 0);
          break;

        case Keys.Up:
          this.AdjustScroll(0, -(e.Modifiers == Keys.None ? this.VerticalScroll.SmallChange : this.VerticalScroll.LargeChange));
          break;

        case Keys.Down:
          this.AdjustScroll(0, e.Modifiers == Keys.None ? this.VerticalScroll.SmallChange : this.VerticalScroll.LargeChange);
          break;
      }
    }

    /// <summary>
    ///   Performs mouse based region selection
    /// </summary>
    /// <param name="e">
    ///   The <see cref="MouseEventArgs" /> instance containing the event data.
    /// </param>
    protected virtual void ProcessSelection(MouseEventArgs e)
    {
      if (this.SelectionMode != ImageBoxSelectionMode.None && e.Button == MouseButtons.Left && !this.WasDragCancelled)
      {
        if (!this.IsSelecting)
        {
          this.StartDrag(e);
        }

        if (this.IsSelecting)
        {
          float x;
          float y;
          float w;
          float h;
          Point imageOffset;
          RectangleF selection;

          imageOffset = this.GetImageViewPort().Location;

          if (e.X < _startMousePosition.X)
          {
            x = e.X;
            w = _startMousePosition.X - e.X;
          }
          else
          {
            x = _startMousePosition.X;
            w = e.X - _startMousePosition.X;
          }

          if (e.Y < _startMousePosition.Y)
          {
            y = e.Y;
            h = _startMousePosition.Y - e.Y;
          }
          else
          {
            y = _startMousePosition.Y;
            h = e.Y - _startMousePosition.Y;
          }

          x = x - imageOffset.X - this.AutoScrollPosition.X;
          y = y - imageOffset.Y - this.AutoScrollPosition.Y;

          x = x / (float)this.ZoomFactor;
          y = y / (float)this.ZoomFactor;
          w = w / (float)this.ZoomFactor;
          h = h / (float)this.ZoomFactor;

          selection = new RectangleF(x, y, w, h);
          if (this.LimitSelectionToImage)
          {
            selection = this.FitRectangle(selection);
          }

          this.SelectionRegion = selection;
        }
      }
    }

    /// <summary>
    /// Resets the <see cref="SizeMode"/> property whilsts retaining the original <see cref="Zoom"/>.
    /// </summary>
    protected void RestoreSizeMode()
    {
      if (this.SizeMode != ImageBoxSizeMode.Normal)
      {
        int previousZoom;

        previousZoom = this.Zoom;
        this.SizeMode = ImageBoxSizeMode.Normal;
        this.Zoom = previousZoom; // Stop the zoom getting reset to 100% before calculating the new zoom
      }
    }

    /// <summary>
    /// Initializes a selection or drag operation.
    /// </summary>
    /// <param name="e">The <see cref="MouseEventArgs"/> instance containing the event data.</param>
    protected virtual void StartDrag(MouseEventArgs e)
    {
      ImageBoxCancelEventArgs args;

      args = new ImageBoxCancelEventArgs(e.Location);

      this.OnSelecting(args);

      this.WasDragCancelled = args.Cancel;
      this.IsSelecting = !args.Cancel;
      if (this.IsSelecting)
      {
        this.SelectNone();

        _startMousePosition = e.Location;
      }
    }

    /// <summary>
    ///   Updates the scroll position.
    /// </summary>
    /// <param name="position">The position.</param>
    protected virtual void UpdateScrollPosition(Point position)
    {
      this.AutoScrollPosition = position;
      this.Invalidate();
      this.OnScroll(new ScrollEventArgs(ScrollEventType.EndScroll, 0));
    }

    #endregion

    #region Private Members

    /// <summary>
    /// Gets the size of the image.
    /// </summary>
    /// <remarks>If an error occurs, for example due to the image being disposed, an empty size is returned</remarks>
    /// <returns>Size.</returns>
    private Size GetImageSize()
    {
      Size result;

      // HACK: This whole thing stinks. Hey MS, how about an IsDisposed property for images?

      if (this.Image != null)
      {
        try
        {
          result = this.Image.Size;
        }
        catch
        {
          result = Size.Empty;
        }
      }
      else
      {
        result = Size.Empty;
      }

      return result;
    }

    /// <summary>
    ///   Initializes the grid tile.
    /// </summary>
    private void InitializeGridTile()
    {
      if (_texture != null)
      {
        _texture.Dispose();
      }

      if (_gridTile != null)
      {
        _gridTile.Dispose();
      }

      if (this.GridDisplayMode != ImageBoxGridDisplayMode.None && this.GridCellSize != 0)
      {
        if (this.GridScale != ImageBoxGridScale.None)
        {
          _gridTile = this.CreateGridTileImage(this.GridCellSize, this.GridColor, this.GridColorAlternate);
          _texture = new TextureBrush(_gridTile);
        }
        else
        {
          _texture = new SolidBrush(this.GridColor);
        }
      }

      this.Invalidate();
    }

    /// <summary>
    /// Called when the animation frame changes.
    /// </summary>
    /// <param name="sender">The source of the event.</param>
    /// <param name="eventArgs">The <see cref="EventArgs"/> instance containing the event data.</param>
    private void OnFrameChangedHandler(object sender, EventArgs eventArgs)
    {
      this.Invalidate();
    }

    /// <summary>
    /// Resets the zoom to 100%.
    /// </summary>
    /// <param name="source">The source that initiated the action.</param>
    private void PerformActualSize(ImageBoxActionSources source)
    {
      this.SizeMode = ImageBoxSizeMode.Normal;
      this.SetZoom(100, ImageBoxZoomActions.ActualSize | (this.Zoom < 100 ? ImageBoxZoomActions.ZoomIn : ImageBoxZoomActions.ZoomOut), source);
    }

    /// <summary>
    /// Zooms into the image
    /// </summary>
    /// <param name="source">The source that initiated the action.</param>
    private void PerformZoomIn(ImageBoxActionSources source)
    {
      this.RestoreSizeMode();
      this.SetZoom(this.ZoomLevels.NextZoom(this.Zoom), ImageBoxZoomActions.ZoomIn, source);
    }

    /// <summary>
    /// Zooms out of the image
    /// </summary>
    /// <param name="source">The source that initiated the action.</param>
    private void PerformZoomOut(ImageBoxActionSources source)
    {
      this.RestoreSizeMode();
      this.SetZoom(this.ZoomLevels.PreviousZoom(this.Zoom), ImageBoxZoomActions.ZoomOut, source);
    }

    /// <summary>
    /// Updates the current zoom.
    /// </summary>
    /// <param name="value">The new zoom value.</param>
    /// <param name="actions">The zoom actions that caused the value to be updated.</param>
    /// <param name="source">The source of the zoom operation.</param>
    private void SetZoom(int value, ImageBoxZoomActions actions, ImageBoxActionSources source)
    {
      int previousZoom;

      previousZoom = this.Zoom;

      if (value < MinZoom)
      {
        value = MinZoom;
      }
      else if (value > MaxZoom)
      {
        value = MaxZoom;
      }

      if (_zoom != value)
      {
        _zoom = value;

        this.OnZoomChanged(EventArgs.Empty);

        this.OnZoomed(new ImageBoxZoomEventArgs(actions, source, previousZoom, this.Zoom));
      }
    }

    #endregion
  }
}

Donate

Donate