如何绘制矩形集合的轮廓?

时间:2014-11-10 15:11:00

标签: c# drawing rectangles region

作为我正在研究的项目的一部分,我必须从图像中存储和恢复魔棒区域。为了获取存储数据,我正在使用GetRegionData方法。正如规范所指定的,这种方法:

  

返回RegionData,表示描述此Region的信息。

我将保存在byte[]属性中的RegionData.Data存储在base64字符串中,以便稍后通过一种非常规的方法检索RegionData

// This "instantiates" a RegionData object by simply initiating an object and setting the object type pointer to the specified type.
// Constructors don't run, but in this very specific case - they don't have to. The RegionData's only member is a "data" byte array, which we set right after.
var regionData =
    (RegionData)FormatterServices.GetUninitializedObject(typeof(RegionData));
regionData.Data = bytes;

然后我创建一个Region并在构造函数中传递上面的RegionData对象并调用GetRegionScans来获取构成该区域的矩形对象:

var region = new Region(regionData);
RectangleF[] rectangles = region.GetRegionScans(new Matrix());

这样我最终会得到一组用于绘制和重建区域的矩形。我已将整个绘图过程隔离到WinForms应用程序,并且我使用以下代码在图像控件上绘制此矩形集合:

using (var g = Graphics.FromImage(picBox.Image))
{
    var p = new Pen(Color.Black, 1f);
    var alternatePen = new Pen(Color.BlueViolet, 1f);
    var b = new SolidBrush(picBox.BackColor);
    var niceBrush = new SolidBrush(Color.Orange);

    foreach (var r in rectangles)
    {
        g.DrawRectangle(p,
            new Rectangle(new Point((int)r.Location.X, (int)r.Location.Y),
                new Size((int)r.Width, (int)r.Height)));
    }
}

以上代码导致在我的图片控件中呈现以下内容:

Rectangles rendered

这里的大纲是正确的 - 这正是我最初使用魔棒工具标记的内容。然而,当我绘制矩形时,我也最终得到了水平线,这些线不是原始魔棒选择的一部分。因此,我无法再查看实际图像,而我的魔棒工具现在使图像变得无用。

我想我只会在屏幕上绘制每个矩形的左右边缘,所以我最终会在屏幕上显示一堆点,为我勾勒出图像的轮廓。为此,我尝试了以下代码:

var pointPair = new[]
{
    new Point((int) r.Left, (int) r.Y),
    new Point((int) r.Right, (int) r.Y)
};

g.DrawRectangle(p, pointPair[0].X, pointPair[0].Y, 1, 1);
g.DrawRectangle(p, pointPair[1].X, pointPair[1].Y, 1, 1);

结果可以在下面观察到:

Points Rendered

虽然距离较近,但仍然没有雪茄。

我正在寻找一种连接点的方法,以便在我的区域中创建矩形的轮廓。对于人类来说,这是微不足道的事情,但我无法弄清楚如何指导计算机为我做这件事。

我已经尝试通过计算每个矩形的LeftRight点上的最邻近点并将它们渲染为单个矩形来尝试从每个矩形创建新点,但是没有用。

任何帮助都会非常感激,因为我真的很茫然。

谢谢!

解决方案

感谢 Peter Duniho 的回答,我设法解决了这个问题。为了完整起见,我在下面包含了SafeHandle包装类和我用来完成这项工作的代码。

绘图代码

以下代码是我用来绘制区域轮廓的代码。

    private void DrawRegionOutline(Graphics graphics, Color color, Region region)
    {
        var regionHandle = new SafeRegionHandle(region, region.GetHrgn(graphics));
        var deviceContext = new SafeDeviceContextHandle(graphics);
        var brushHandle = new SafeBrushHandle(color);

        using (regionHandle)
        using (deviceContext)
        using (brushHandle)
            FrameRgn(deviceContext.DangerousGetHandle(), regionHandle.DangerousGetHandle(), brushHandle.DangerousGetHandle(), 1, 1);
    }

SafeHandleNative

SafeHandleZeroOrMinusOneIsInvalid周围的小包装,以确保在完成句柄后进行清理。

[HostProtection(MayLeakOnAbort = true)]
[SuppressUnmanagedCodeSecurity]
public abstract class SafeNativeHandle : SafeHandleZeroOrMinusOneIsInvalid
{
    #region Platform Invoke

    [DllImport("kernel32.dll", SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    protected internal static extern bool CloseHandle(IntPtr hObject);

    #endregion

    /// <summary>
    /// Initializes a new instance of the <see cref="SafeNativeHandle"/> class.
    /// </summary>
    protected SafeNativeHandle() : base(true)
    {}

    /// <summary>
    /// Initializes a new instance of the <see cref="SafeNativeHandle"/> class.
    /// </summary>
    /// <param name="handle">The handle.</param>
    protected SafeNativeHandle(IntPtr handle)
        : base(true)
    {
        SetHandle(handle);
    }
}

我创建了另外三个包装器,一个用于Region对象,一个用于Brush,另一个用于设备上下文句柄。这些都继承自SafeNativeHandle,但为了不发送垃圾邮件,我只会提供我用于下面区域的那个。其他两个包装器实际上是相同的,但使用相应的Win32 API来清理自己的资源。

public class SafeRegionHandle : SafeNativeHandle
{
    private readonly Region _region;

    /// <summary>
    /// Initializes a new instance of the <see cref="SafeRegionHandle" /> class.
    /// </summary>
    /// <param name="region">The region.</param>
    /// <param name="handle">The handle.</param>
    public SafeRegionHandle(Region region, IntPtr handle)
    {
        _region = region;
        base.handle = handle;
    }

    /// <summary>
    /// When overridden in a derived class, executes the code required to free the handle.
    /// </summary>
    /// <returns>
    /// true if the handle is released successfully; otherwise, in the event of a catastrophic failure, false. In this case, it generates a releaseHandleFailed MDA Managed Debugging Assistant.
    /// </returns>
    [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
    protected override bool ReleaseHandle()
    {
        try
        {
            _region.ReleaseHrgn(handle);
        }
        catch
        {
            return false;
        }

        return true;
    }
}

1 个答案:

答案 0 :(得分:1)

我仍然不完全确定我理解这个问题。然而,听起来好像你只是想通过概述它来绘制给定区域,而不是填充它。

不幸的是,据我所知,.NET API不支持此功能。但是,本机Windows API可以。这是一些应该做你想做的代码:

[DllImport("gdi32")]
static extern bool FrameRgn(System.IntPtr hDC, System.IntPtr hRgn, IntPtr hBrush, int nWidth, int nHeight);

[DllImport("gdi32")]
static extern IntPtr CreateSolidBrush(uint colorref);

[DllImport("gdi32.dll")]
static extern bool DeleteObject([In] IntPtr hObject);

[StructLayout(LayoutKind.Explicit)]
struct COLORREF
{
    [FieldOffset(0)]
    public uint colorref;
    [FieldOffset(0)]
    public byte red;
    [FieldOffset(1)]
    public byte green;
    [FieldOffset(2)]
    public byte blue;

    public COLORREF(Color color)
        : this()
    {
        red = color.R;
        green = color.G;
        blue = color.B;
    }
}

void DrawRegion(Graphics graphics, Color color, Region region)
{
    COLORREF colorref = new COLORREF(color);
    IntPtr hdc = IntPtr.Zero, hbrush = IntPtr.Zero, hrgn = IntPtr.Zero;

    try
    {
        hrgn = region.GetHrgn(graphics);
        hdc = graphics.GetHdc();
        hbrush = CreateSolidBrush(colorref.colorref);

        FrameRgn(hdc, hrgn, hbrush, 1, 1);
    }
    finally
    {
        if (hrgn != IntPtr.Zero)
        {
            region.ReleaseHrgn(hrgn);
        }

        if (hbrush != IntPtr.Zero)
        {
            DeleteObject(hbrush);
        }

        if (hdc != IntPtr.Zero)
        {
            graphics.ReleaseHdc(hdc);
        }
    }
}

从您的DrawRegion()事件处理程序或其他具有Paint实例的适当上下文中调用Graphics方法,例如绘制到示例中的Image对象

显然,您可以将此作为一种扩展方法,以获得更多便利。另外,虽然在这个例子中我直接处理句柄的初始化和释放,但是更好的实现会将句柄包装在适当的SafeHandle子类中,这样你就可以方便地使用using而不是try /最后,并获得最终确定的备份(如果你忘了处理)。