Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Zooming a large picture with Windows Forms

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.

Zoom illustration

As mentioned before the image will be quite large, so my question is: Is it possible to achieve this with the default PictureBox 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.

As promised, here is the source of the control I made:

/// <summary>
/// A panel used to display an image and zoom into areas of the displayed
/// image.
/// </summary>
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;

    /// <summary>
    /// Create a new <see cref="PictureZoomPanel"/>
    /// </summary>
    public PictureZoomPanel()
    {
        // To prevent flickering
        DoubleBuffered = true;
        // To make resizing smoother
        ResizeRedraw = true;
        // Set default zoom pen
        ZoomPen = null;
    }

    /// <summary>
    /// The image to be displayed
    /// </summary>
    [Category("Appearance"), 
     Description("The image to be displayed.")]
    public Image Image
    {
        get { return _image; }
        set
        {
            _image = value;
            ZoomToFit();
        }
    }

    /// <summary>
    /// The pen used to draw the zoom rectangle.
    /// </summary>
    [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);
        }
    }

    /// <summary>
    /// Sets the zoom to a value where the whole image is visible.
    /// </summary>
    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 < 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 < 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);
    }
}
like image 544
xsl Avatar asked Jan 30 '12 10:01

xsl


People also ask

How do I zoom in Winforms?

The zoom factor can be controlled using Ctrl+MouseWheel for zoom in and zoom out functionality. Left Button MouseDown+Move for pan/scroll functionality.

How do I zoom in on a JPEG image?

Upload the chosen image in PNG or JPG directly into the editor or drag-n-drop it to the editor. Use the Zoom In/Out slider at the bottom of your screen to zoom into your photo. Focus on the specific part of the image while zooming in and make edits where required.


1 Answers

You can achieve zooming through translating the graphic object. Instead of using a PictureBox, I think a double buffered Panel is the better tool of choice.

You handle the paint event, and adjust the matrix presentation of it from there. Also, you need to adjust the AutoScrollMinSize property to have the scrollbars represent the correct range of the scaled image.

Quick example:

Bitmap bmp = new Bitmap(@"c:\myimage.png");
int zoom = 2;

private void Form1_Load(object sender, EventArgs e) {
  panel1.AutoScrollMinSize = new Size(bmp.Width * zoom, bmp.Height * zoom);
}

private void panel1_Paint(object sender, PaintEventArgs e) {
  using (Matrix mx = new Matrix(zoom, 0, 0, zoom, 0, 0)) {
    mx.Translate(panel1.AutoScrollPosition.X / zoom, panel1.AutoScrollPosition.Y / zoom);
    e.Graphics.Transform = mx;
    e.Graphics.DrawImage(bmp, new Point(0, 0));
  }
}

This method is for tracking the mouse movement of a scaled image:

protected Point BacktrackMouse(MouseEventArgs e)
{
  Matrix mx = new Matrix(_zoom, 0, 0, _zoom, 0, 0);
  mx.Translate(this.AutoScrollPosition.X * (1.0f / zoom), 
               this.AutoScrollPosition.Y * (1.0f / zoom));
  mx.Invert();
  Point[] p = new Point[] { new Point(e.X, e.Y) };
  mx.TransformPoints(p);
  return p[0];
}
like image 169
LarsTech Avatar answered Oct 16 '22 14:10

LarsTech