title: Zooming a large picture with Windows Forms
body: <p>I have to display a large image inside a windows forms application. The user should have the possibility to mark an area of the image, which should be then be zoomed like in the example illustrated below.</p>
  
  <p><img src="https://i.stack.imgur.com/HImy3.png" alt="Zoom illustration"></p>
  
  <p>As mentioned before the image will be quite large, so my question is: Is it possible to achieve this with the default <code>PictureBox</code> control or am I better of using a 3rd party control? If so, please recommend a library which contains a control that offers these features.</p>
  
  <p>As promised, here is the source of the control I made:</p>
  
  <pre><code>/// &lt;summary&gt;
  /// A panel used to display an image and zoom into areas of the displayed
  /// image.
  /// &lt;/summary&gt;
  public sealed class PictureZoomPanel : Panel
  {
      // The image to dispay, set in the Image property
      private Image _image;
      // The current zoom factor
      private float _zoom = 1;
      // The zoom rectangle on the panel.
      private Rectangle _panelZoomRect;
      // _panelZoomRect on the actual image
      private Rectangle? _imageZoomRect;
      // Used in the mouse event handlers
      private bool _mouseDown;
      // The pen used to draw the zoom rectangle
      private Pen _zoomPen;
  
      /// &lt;summary&gt;
      /// Create a new &lt;see cref="PictureZoomPanel"/&gt;
      /// &lt;/summary&gt;
      public PictureZoomPanel()
      {
          // To prevent flickering
          DoubleBuffered = true;
          // To make resizing smoother
          ResizeRedraw = true;
          // Set default zoom pen
          ZoomPen = null;
      }
  
      /// &lt;summary&gt;
      /// The image to be displayed
      /// &lt;/summary&gt;
      [Category("Appearance"), 
       Description("The image to be displayed.")]
      public Image Image
      {
          get { return _image; }
          set
          {
              _image = value;
              ZoomToFit();
          }
      }
  
      /// &lt;summary&gt;
      /// The pen used to draw the zoom rectangle.
      /// &lt;/summary&gt;
      [Category("Appearance"), 
       Description("The pen used to draw the zoom rectangle.")]
      public Pen ZoomPen
      {
          get { return _zoomPen; }
          set {
              _zoomPen = value ?? new Pen(Color.Green, 2);
          }
      }
  
      /// &lt;summary&gt;
      /// Sets the zoom to a value where the whole image is visible.
      /// &lt;/summary&gt;
      public void ZoomToFit()
      {
          _imageZoomRect = null;
          _mouseDown = false;
          _zoom = 1;
  
          // If no image is present, there is nothing further to do
          if (_image == null)
              return;
  
          var widthZoom = (float) Width / _image.Width;
          var heightZoom = (float) Height / _image.Height;
  
          // Make sure the whole image is visible
          _zoom = widthZoom &lt; heightZoom ? widthZoom : heightZoom;
  
          // Force redraw
          Invalidate();
      }
  
      protected override void OnMouseDown(MouseEventArgs e)
      {
          if (_image == null)
              return;
  
          _mouseDown = true;
          _panelZoomRect = new Rectangle(e.X, e.Y, 0, 0);
      }
  
      protected override void OnMouseUp(MouseEventArgs e)
      {
          if (_image == null || !_mouseDown)
              return;
  
          _mouseDown = false;
  
          // Without this, doubling clicking the control would cause zoom
          if (_panelZoomRect.Height == 0 || _panelZoomRect.Width == 0)
              return;
  
          // Tell the paint method to zoom
          _imageZoomRect = CalculateImageZoomRectangle();
          _zoom = RecalculateZoom();
      }
  
      protected override void OnMouseMove(MouseEventArgs e)
      {
          if (_image == null)
              return;
  
          // This makes sure that the left mouse button is pressed.
          if (e.Button == MouseButtons.Left)
          {
              // Draws the rectangle as the mouse moves
              _panelZoomRect = new Rectangle(
                  _panelZoomRect.Left, 
                  _panelZoomRect.Top, 
                  e.X - _panelZoomRect.Left, 
                  e.Y - _panelZoomRect.Top);
          }
  
          // Force redraw to make sure the zoomRegion is painted
          Invalidate();
      }
  
      private Rectangle CalculateImageZoomRectangle()
      {
          // Calculate all the coordinates to required to transform
          var topLeft = new Point(_panelZoomRect.X, 
              _panelZoomRect.Y);
          var topRight = new Point(_panelZoomRect.X + _panelZoomRect.Width, 
              _panelZoomRect.Y);
          var bottomLeft = new Point(_panelZoomRect.X,
              _panelZoomRect.Y - _panelZoomRect.Height);
          var bottomRight = new Point(_panelZoomRect.X + _panelZoomRect.Height,
              _panelZoomRect.Y - _panelZoomRect.Height);
  
          var points = new [] { topLeft, topRight, bottomLeft, bottomRight };
  
          // Converts the points from panel to image position
          var mx = new Matrix(_zoom, 0, 0, _zoom, 0, 0);
          mx.Invert();
          mx.TransformPoints(points);
  
          var rectangleWidth = points[1].X - points[0].X;
          var rectangleHeight = points[0].Y - points[2].Y;
  
          // _imageZoom != null, means that we are zooming in on an
          // already zoomed in image. We must add the original values
          // to zoom in deeper
          return _imageZoomRect == null
              ? new Rectangle(points[0].X, 
                  points[0].Y, 
                  rectangleWidth,
                  rectangleHeight)
              : new Rectangle(points[0].X + _imageZoomRect.Value.X,
                  points[0].Y + _imageZoomRect.Value.Y, 
                  rectangleWidth,
                  rectangleHeight);
      }
  
      private float RecalculateZoom()
      {
          if (!_imageZoomRect.HasValue)
              return _zoom;
  
          var widthZoom = (float)Width / _imageZoomRect.Value.Width;
          var heightZoom = (float)Height / _imageZoomRect.Value.Height;
  
          return widthZoom &lt; heightZoom ? widthZoom : heightZoom;
      }
  
      protected override void OnPaint(PaintEventArgs e)
      {
          if (_image == null)
          {
              OnPaintBackground(e);
              return;
          }
  
          e.Graphics.Transform = new Matrix(_zoom, 0, 0, _zoom, 0, 0);
  
          // Turn of interpolation when zoomed
          e.Graphics.InterpolationMode = _imageZoomRect != null 
              ? InterpolationMode.NearestNeighbor 
              : InterpolationMode.Default;
  
          DrawImage(e);
  
          if (_mouseDown)
              DrawZoomRectangle(e);
  
          base.OnPaint(e);
      }
  
      private void DrawImage(PaintEventArgs e)
      {
          var destRec = !_imageZoomRect.HasValue
              ? new Rectangle(0, 0, _image.Width, _image.Height)
              : new Rectangle(0, 0, _imageZoomRect.Value.Width, 
                  _imageZoomRect.Value.Height);
  
          var sourceRec = !_imageZoomRect.HasValue
              ? new Rectangle(0, 0, _image.Width, _image.Height)
              : _imageZoomRect.Value;
  
          e.Graphics.DrawImage(_image, destRec, 
              sourceRec.Location.X, sourceRec.Location.Y,
              sourceRec.Width, sourceRec.Height,
              GraphicsUnit.Pixel);
      }
  
      private void DrawZoomRectangle(PaintEventArgs e)
      {
          e.Graphics.Transform = new Matrix();
          e.Graphics.DrawRectangle(_zoomPen, _panelZoomRect);
      }
  }
  </code></pre>