【发布时间】:2014-11-10 15:10:59
【问题描述】:
作为我正在进行的项目的一部分,我必须从图像中存储和恢复魔杖区域。为了获取存储数据,我使用了GetRegionData 方法。按照规范的规定,这个方法:
返回一个 RegionData,表示描述该区域的信息。
我将RegionData.Data 属性中保存的byte[] 存储在一个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)));
}
}
上面的代码导致在我的图片控件中呈现以下内容:
这里的轮廓是正确的 - 这正是我最初用我的魔杖工具标记的。但是,当我绘制矩形时,我也会得到水平线,这不是原始魔棒选择的一部分。因此,我无法再查看实际图像,并且我的魔杖工具现在使图像无用。
我想我只会在屏幕上绘制每个矩形的左右边缘,所以我最终会在屏幕上画出一堆点,为我勾勒出图像的轮廓。为此,我尝试了以下代码:
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);
结果如下:
虽然更近了,但还是没有雪茄。
我正在寻找一种方法来连接这些点,以便在我的区域中创建矩形的轮廓。对人类来说微不足道的事情,但我一生都无法弄清楚如何指导计算机为我做这件事。
我已经尝试通过计算每个矩形的 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);
}
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;
}
}
【问题讨论】:
-
不清楚您是否需要一些 WPF 解决方案?你标记了 WPF,但你所有的代码都是关于 Winforms 的。我不明白你在这里想要什么。请注意,WPF 中的某些操作比 Winforms 中的操作要容易得多,所以不要假设它们是相同的(我的意思是不要认为一旦您知道如何在 Winforms 中进行操作,您也可以知道如何操作在 WPF 中)。
-
@KingKing 你说得对,我弄错了问题。最终的解决方案将在 LeadTools 的第三方查看器中实现,它是 WPF 包装器内的 WinForms(它使用许多
System.Drawing组件)。但是,您的观点是正确的,这个问题被错误地标记了,我会纠正它。 -
我完全不懂这个问题。您以
Region开头。想必这本来就很好。但是你必须序列化数据(这很好)。但是当你反序列化它,得到一个与你开始的Region对象相同的新Region实例时,你不再想将它用作Region,而是想提出一些方案来绘制数据。为什么?序列化/反序列化在这里有什么意义?与原版Region没有相同的绘图问题吗?如果没有,你为什么不能像第一个一样画出新的Region? -
@PeterDuniho 因为第一个区域不是我画的,它是由魔杖工具选择的。对不起,如果我的解释有点模棱两可。问题在于重新创建魔杖工具原来为我所做的事情。
-
那么序列化/反序列化和这里有什么关系呢?
标签: c# drawing rectangles region