使用 Windows 窗体缩放大图片

发布于 2024-12-29 11:56:09 字数 6890 浏览 4 评论 0原文

我必须在 Windows 窗体应用程序中显示大图像。用户应该能够标记图像的一个区域,然后该区域应该被缩放,如下图所示。

Zoom illustration

正如前面提到的,图像会很大,所以我的问题是:是否可以使用默认值来实现这一点PictureBox 控件还是我最好使用第 3 方控件?如果是这样,请推荐一个包含提供这些功能的控件的库。

正如所承诺的,这是我所做的控件的来源:

/// <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);
    }
}

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);
    }
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

黯淡〆 2025-01-05 11:56:09

您可以通过平移图形对象来实现缩放。我认为 双缓冲面板 是更好的选择工具,而不是使用 PictureBox。

您可以处理绘制事件,并从那里调整它的矩阵表示。此外,您还需要调整 AutoScrollMinSize 属性以使滚动条代表缩放图像的正确范围。

简单示例:

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));
  }
}

此方法用于跟踪缩放图像的鼠标移动:

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];
}

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];
}
别再吹冷风 2025-01-05 11:56:09

尝试以下链接 - 它们可能不是您的最终解决方案,但其中的一部分应该可以帮助您构建自己的解决方案:

  1. 平移和缩放非常大的图像
  2. 图像目标缩放(平移缩放)
  3. A可滚动、可缩放、可缩放的图片框

Try the following links - they may not be the end solution for you but a part of them should help you to build your own:

  1. Pan and Zoom Very Large Images
  2. Image Target Zoom (Pan Zoom)
  3. A scrollable, zoomable, and scalable picture box
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文