【问题标题】:Translate Rectangle Position in Zoom Mode Picturebox在缩放模式下平移矩形位置 Picturebox
【发布时间】:2018-12-16 07:38:58
【问题描述】:

我正在确定图像中的矩形区域并在 PictureBox 中将其显示给用户。
由于图像有时可能非常大,因此我使用的是 PictureBox,其 SizeMode 设置为 Zoom

我正在使用以下代码来转换 Rectangle (X, Y) 坐标:

public Point TranslateZoomMousePosition(Point coordinates)
{
    // test to make sure our image is not null
    if (pictureBox5.Image == null) return coordinates;
    // Make sure our control width and height are not 0 and our 
    // image width and height are not 0
    if (pictureBox5.Width == 0 || pictureBox5.Height == 0 || pictureBox5.Image.Width == 0 || pictureBox5.Image.Height == 0) return coordinates;
    // This is the one that gets a little tricky. Essentially, need to check 
    // the aspect ratio of the image to the aspect ratio of the control
    // to determine how it is being rendered
    float imageAspect = (float)pictureBox5.Image.Width / pictureBox5.Image.Height;
    float controlAspect = (float)pictureBox5.Width / pictureBox5.Height;
    float newX = coordinates.X;
    float newY = coordinates.Y;
    if (imageAspect > controlAspect)
    {
        // This means that we are limited by width, 
        // meaning the image fills up the entire control from left to right
        float ratioWidth = (float)pictureBox5.Image.Width / pictureBox5.Width;
        newX *= ratioWidth;
        float scale = (float)pictureBox5.Width / pictureBox5.Image.Width;
        float displayHeight = scale * pictureBox5.Image.Height;
        float diffHeight = pictureBox5.Height - displayHeight;
        diffHeight /= 2;
        newY -= diffHeight;
        newY /= scale;
    }
    else
    {
        // This means that we are limited by height, 
        // meaning the image fills up the entire control from top to bottom
        float ratioHeight = (float)pictureBox5.Image.Height / pictureBox5.Height;
        newY *= ratioHeight;
        float scale = (float)pictureBox5.Height / pictureBox5.Image.Height;
        float displayWidth = scale * pictureBox5.Image.Width;
        float diffWidth = pictureBox5.Width - displayWidth;
        diffWidth /= 2;
        newX -= diffWidth;
        newX /= scale;
    }
    return new Point((int)newX, (int)newY);
}

在确定的位置添加框架控件:

pictureBox5.Controls.Clear();
var c = new FrameControl();
c.Size = new Size(myrect.Width, myrect.Height);
c.Location=TranslateZoomMousePosition(newPoint(myrect.Location.X,myrect.Location.Y));
pictureBox5.Controls.Add(c);

但确定的框架/矩形位置不正确。
我做错了什么?

更新: 我正在尝试使用类似的代码将图像上的 Rectangle 转换为 PictureBox 上的 Frame Control

public Rectangle GetRectangeOnPictureBox(PictureBox p, Rectangle selectionRect,Bitmap bit)
    {
        var method = typeof(PictureBox).GetMethod("ImageRectangleFromSizeMode",
            System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
        var imageRect = (Rectangle)method.Invoke(p, new object[] { p.SizeMode });
        if (p.Image == null)
            return selectionRect;
        int cx = bit.Width / imageRect.Width;
        int cy = bit.Height / imageRect.Height;
        Rectangle trsRectangle = new Rectangle(selectionRect.X * cx, selectionRect.Y * cy, selectionRect.Width * cx, selectionRect.Height * cy);

        trsRectangle.Offset(imageRect.X, imageRect.Y);
        return trsRectangle;
    }

这会产生无效的结果。请指教

【问题讨论】:

    标签: c# .net winforms picturebox system.drawing


    【解决方案1】:

    您可以通过这种方式将图片框上的选定矩形转换为图像上的矩形:

    public RectangleF GetRectangeOnImage(PictureBox p, Rectangle selectionRect)
    {
        var method = typeof(PictureBox).GetMethod("ImageRectangleFromSizeMode",
            System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
        var imageRect = (Rectangle)method.Invoke(p, new object[] { p.SizeMode });
        if (p.Image == null)
            return selectionRect;
        var cx = (float)p.Image.Width / (float)imageRect.Width;
        var cy = (float)p.Image.Height / (float)imageRect.Height;
        var r2 = Rectangle.Intersect(imageRect, selectionRect);
        r2.Offset(-imageRect.X, -imageRect.Y);
        return new RectangleF(r2.X * cx, r2.Y * cy, r2.Width * cx, r2.Height * cy);
    }
    

    注意:您可以找到 ImageRectangleFromSizeMode 方法 source code here 并将其用作编写此类方法作为应用程序代码的一部分。

    示例 - 裁剪具有 SizeMode = Zoom 的 PictureBox 图像

    例如,以下代码将裁剪图片框 1 的给定矩形,并将结果设置为图片框 2 的图像:

    var selectedRectangle = new Rectangle(7, 30, 50, 40);
    var result = GetRectangeOnImage(pictureBox1, selectedRectangle);
    using (var bm = new Bitmap((int)result.Width, (int)result.Height))
    {
        using (var g = Graphics.FromImage(bm))
            g.DrawImage(pictureBox1.Image, 0, 0, result, GraphicsUnit.Pixel);
        pictureBox2.Image = (Image)bm.Clone();
    }
    

    这是输入图像:

    结果如下:

    【讨论】:

    • ImageRectangleFromSizeMode 哇,这是在深入挖掘隐藏的金库!
    • @techno 稍作更改以支持Stretch 模式。
    • @TaW 添加了指向ImageRectangleFromSizeMode源代码的链接,以便未来的读者可以使用它并将此类方法作为其应用程序代码的一部分编写。
    • 哈! ImageRectangleFromSizeMode 是您通常在堆栈跟踪中看到的内容(当出现问题时)。干得好!
    • @RezaAghaei 实际上也需要做相反的事情,即:我在原始图像上有矩形坐标。当这个图像显示在图片框中时,我需要将可移动框架放置在正确的位置翻译其在原始图像中的已知位置。
    【解决方案2】:

    一个专门的类,它提供一些帮助工具来确定选择的缩放因子并将选择坐标转换为缩放的Bitmap 坐标。
    版本仅适用于缩放图像。

    ZoomFactor 类提供了这些方法

    PointF TranslateZoomPosition(PointF Coordinates, SizeF ContainerSize, SizeF ImageSize)
    将容器内 Point 位置的PointF 转换后的坐标返回到 Bitmap 内的 Point 位置,放大容器。

    RectangleF TranslateZoomSelection(RectangleF Selection, SizeF ContainerSize, SizeF ImageSize)
    返回一个RectangleF,表示在容器内创建的选择,转换为位图坐标。

    RectangleF TranslateSelectionToZoomedSel(RectangleF SelectionRect, SizeF ContainerSize, SizeF ImageSize)
    返回一个RectangleF,表示原始位图的预选区域,转换为容器内的缩放选择图像。

    PointF GetImageScaledOrigin(SizeF ContainerSize, SizeF ImageSize)
    返回 Container 内缩放后的 Image 原点坐标的 PointF 引用。

    SizeF GetImageScaledSize(SizeF ContainerSize, SizeF ImageSize)
    在 Container 内缩放时返回 Image 的 SizeF 引用。

    示例用法,展示如何使用在 Container 控件中创建的选择矩形裁剪位图。 TranslateZoomSelection 方法返回选择区域对应的位图部分:

    ZoomFactor zoomHelper = new ZoomFactor()
    Bitmap originalBitmap;
    
    RectangleF currentSelection = [Current Selection Rectangle];
    RectangleF bitmapRect = zoomHelper.TranslateZoomSelection(currentSelection, [Container].Size, originalBitmap.Size);
    
    var croppedBitmap = new Bitmap((int)bitmapRect.Width, (int)bitmapRect.Height, originalBitmap.PixelFormat))
    using (var g = Graphics.FromImage(croppedBitmap))
    {
        g.DrawImage(originalBitmap, new Rectangle(Point.Empty, Size.Round(bitmapRect.Size)), 
                    bitmapRect, GraphicsUnit.Pixel);
        [Container].Image = croppedBitmap;
    }
    

    上述行为示例

    注意在示例中,Portrait 中的图像预选反转了WidthHeight

    ZoomFactor

    public class ZoomFactor
    {
        public ZoomFactor() { }
    
        public PointF TranslateZoomPosition(PointF coordinates, SizeF containerSize, SizeF imageSize)
        {
            PointF imageOrigin = TranslateCoordinatesOrigin(coordinates, containerSize, imageSize);
            float scaleFactor = GetScaleFactor(containerSize, imageSize);
            return new PointF(imageOrigin.X / scaleFactor, imageOrigin.Y / scaleFactor);
        }
    
        public RectangleF TranslateZoomSelection(RectangleF selectionRect, SizeF containerSize, SizeF imageSize)
        {
            PointF selectionTrueOrigin = TranslateZoomPosition(selectionRect.Location, containerSize, imageSize);
            float scaleFactor = GetScaleFactor(containerSize, imageSize);
    
            SizeF selectionTrueSize = new SizeF(selectionRect.Width / scaleFactor, selectionRect.Height / scaleFactor);
            return new RectangleF(selectionTrueOrigin, selectionTrueSize);
        }
    
        public RectangleF TranslateSelectionToZoomedSel(RectangleF selectionRect, SizeF containerSize, SizeF imageSize)
        {
            float scaleFactor = GetScaleFactor(containerSize, imageSize);
            RectangleF zoomedSelectionRect = new
                RectangleF(selectionRect.X * scaleFactor, selectionRect.Y * scaleFactor,
                           selectionRect.Width * scaleFactor, selectionRect.Height * scaleFactor);
    
            PointF imageScaledOrigin = GetImageScaledOrigin(containerSize, imageSize);
            zoomedSelectionRect.Location = new PointF(zoomedSelectionRect.Location.X + imageScaledOrigin.X,
                                                      zoomedSelectionRect.Location.Y + imageScaledOrigin.Y);
            return zoomedSelectionRect;
        }
    
        public PointF TranslateCoordinatesOrigin(PointF coordinates, SizeF containerSize, SizeF imageSize)
        {
            PointF imageOrigin = GetImageScaledOrigin(containerSize, imageSize);
            return new PointF(coordinates.X - imageOrigin.X, coordinates.Y - imageOrigin.Y);
        }
    
        public PointF GetImageScaledOrigin(SizeF containerSize, SizeF imageSize)
        {
            SizeF imageScaleSize = GetImageScaledSize(containerSize, imageSize);
            return new PointF((containerSize.Width - imageScaleSize.Width) / 2,
                              (containerSize.Height - imageScaleSize.Height) / 2);
        }
    
        public SizeF GetImageScaledSize(SizeF containerSize, SizeF imageSize)
        {
            float scaleFactor = GetScaleFactor(containerSize, imageSize);
            return new SizeF(imageSize.Width * scaleFactor, imageSize.Height * scaleFactor);
    
        }
        internal float GetScaleFactor(SizeF scaled, SizeF original)
        {
            return (original.Width > original.Height) ? (scaled.Width / original.Width)
                                                      : (scaled.Height / original.Height);
        }
    }
    

    【讨论】:

    • 非常感谢:)。我会看看你的两个答案。
    • 其实也需要做相反的事情,即:我在原始图像上有矩形坐标。当这个图像显示在图片框中时,我需要通过平移将可移动框架放置在正确的位置。原始图像中的已知位置。
    • @techno 查看编辑。我添加了一个选择翻译器,从相对于原始图像的坐标到缩放图像(以及一个示例动画:)。我没有太多时间,所以没有正确测试,但我在不同的条件下做了一些选择,看起来还可以。我明天再修改。
    • 如何检测控件是否在图片框中调整大小或移动?目前我使用图片框鼠标鼠标事件。有没有更好的办法?
    • 什么是鼠标事件? :) 我假设它是MouseMove。我不确定你是如何实现这个控件的(可能是前面提到的 TaW 或 Reza Aghaei 的自定义控件),但当然这个自定义控件 1)只能在 PictureBox 的范围内移动(如果不是,请参见此处:@ 987654322@), 2) 公开一个属性或引发一个引用其在 PictureBox 中的当前位置的事件。 When the selection control is moved(...)
    猜你喜欢
    • 2021-09-04
    • 2011-02-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-05-09
    • 2016-04-15
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多