aev*_*tas 5 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;
Run Code Online (Sandbox Code Playgroud)
然后,我创建一个Region并将上述RegionData对象传递到构造函数中,并调用GetRegionScans以获取组成该区域的矩形对象:
var region = new Region(regionData);
RectangleF[] rectangles = region.GetRegionScans(new Matrix());
Run Code Online (Sandbox Code Playgroud)
这样我最终得到了用于绘制和重建该区域的矩形集合。我已将整个绘图过程隔离到 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)));
}
}
Run Code Online (Sandbox Code Playgroud)
上面的代码导致在我的图片控件中呈现以下内容:

这里的轮廓是正确的——这正是我最初用魔棒工具标记的。然而,当我绘制矩形时,我最终也得到了水平线,这不是原始魔棒选择的一部分。因此,我无法再查看实际图像,而且我的魔杖工具现在使图像变得毫无用处。
我想我只会在屏幕上绘制每个矩形的左边缘和右边缘,所以我最终会在屏幕上得到一堆点,为我勾画出图像的轮廓。为此,我尝试了以下代码:
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);
Run Code Online (Sandbox Code Playgroud)
结果如下:

虽然近了,但它仍然不是雪茄。
我正在寻找一种连接点的方法,以便在我的区域中创建矩形的轮廓。这对人类来说是微不足道的事情,但我一生都无法弄清楚如何指示计算机为我做这件事。
Left我已经尝试通过计算每个矩形的和点上最相邻的点Right,并将它们渲染为单独的矩形来从每个矩形创建新点,但无济于事。
任何帮助将不胜感激,因为我在这里真的很茫然。
谢谢!
感谢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);
}
Run Code Online (Sandbox Code Playgroud)
周围有小包装纸,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);
}
}
Run Code Online (Sandbox Code Playgroud)
我创建了另外三个包装器,一个用于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;
}
}
Run Code Online (Sandbox Code Playgroud)
我仍然不完全确定我理解这个问题。然而,在我看来,您只是想通过勾勒出给定区域的轮廓来绘制它,而不是填充它。
不幸的是,据我所知.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);
}
}
}
Run Code Online (Sandbox Code Playgroud)
DrawRegion()从Paint事件处理程序或拥有实例的其他适当上下文中调用该方法Graphics,例如绘制到Image示例中的对象中。
显然,您可以将其作为扩展方法以更加方便。另外,虽然在这个例子中我直接处理句柄的初始化和释放,但更好的实现是将句柄包装在适当的SafeHandle子类中,以便您可以方便地使用using而不是 try/finally ,并获得终结的备份(以防您忘记丢弃)。