As part of a project I'm working on I have to store and restore magic wand regions from an image. To obtain the data for storage, I'm utilizing the GetRegionData method. As the specification specifies, this method:
Returns a RegionData that represents the information that describes this Region.
I store the byte[]
kept in the RegionData.Data
property in a base64 string, so I can retrieve the RegionData
later through a somewhat unconventional method:
// 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;
I then create a Region
and pass the above RegionData
object in the constructor and call GetRegionScans
to obtain the rectangle objects which comprise the region:
var region = new Region(regionData);
RectangleF[] rectangles = region.GetRegionScans(new Matrix());
This way I end up with a collection of rectangles that I use to draw and reconstruct the region. I have isolated the entire drawing process to a WinForms application, and I'm using the following code to draw this collection of rectangles on an image control:
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)));
}
}
The above code results in the following being rendered in my picture control:
The outline here is correct - that's exactly what I originally marked with my magic wand tool. However, as I'm drawing rectangles, I also end up with horizontal lines, which weren't a part of the original magic wand selection. Because of this, I can't view the actual image anymore, and my wand tool now makes the image useless.
I figured I'd only draw the left and right edges of each of the rectangles on the screen, so I'd end up with a bunch of points on the screen which outline the image for me. To do this, I tried the following code:
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);
And the result can be observed below:
Though closer, it's still no cigar.
I'm looking for a way to connect the dots in order to create an outline of the rectangles in my region. Something that's trivial for humans to do, but I can not figure out for the life of me how to instruct a computer to do this for me.
I've already tried creating new points from each of the rectangles by calculating the most adjacent points on both the Left
and Right
points of each rectangle, and rendering those as individual rectangles, but to no avail.
Any help would be greatly appreciated, as I'm really at a loss here.
Thanks!
Solution
Thanks to Peter Duniho's answer, I managed to solve this problem. I'm including the SafeHandle
wrapper classes and the code I've used to make this work below, for the sake of completeness.
Drawing code
The code below is what I've used to draw the region outline.
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
Small wrapper around SafeHandleZeroOrMinusOneIsInvalid
to ensure cleanup after we're done with the handles.
[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);
}
}
I've created three other wrappers, one for the Region
object, one for the Brush
and one for the device context handles. These all inherit from SafeNativeHandle
, but in order not to spam I'll only provide the one I've used for the region below. The other two wrappers are virtually identical, but use the respective Win32 API required to clean up their own resources.
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;
}
}
I'm still not entirely sure I understand the question. However, it sounds to me as though you simply want to draw the given region by outlining it, rather than filling it.
Unfortunately, as far as I know the .NET API does not support this. However, the native Windows API does. Here is some code that should do what you want:
[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);
}
}
}
Call the DrawRegion()
method from your Paint
event handler or other appropriate context where you have a Graphics
instance, such as drawing into an Image
object as in your example.
Obviously you could make this an extension method for more convenience. Also, while in this example I am dealing with the initialization and releasing of the handles directly, a better implementation would wrap the handles in appropriate SafeHandle
subclasses, so that you can conveniently use using
instead of try/finally, and to get the backup of finalization (in case you forget to dispose).
来源:https://stackoverflow.com/questions/26846813/how-do-i-draw-the-outline-of-a-collection-of-rectangles