Reputation: 1258
This question is quite difficult for me to explain, so I'll be illustrating with some images as well as text.
For a steel engraving machine I need to use .NET's normal graphics framework to create a "document" that is sent to the engraving machine - it is treated just like a normal printer. The machine in question is this one:
http://www.rolanddga.com/products/impactprinters/mpx90/features.asp
I can print a text-outline on it in C# with this:
// ALL UNITS ARE SET IN MILIMETERS (MM)
Graphics g = <instantiated from my printers-printpage-event>;
// The following values are set as "constants" here for the purpose of my question
// they normally are passed as parameters
string s = "ABC";
float fontSize = 4.0F;
RectangleF r = new RectangleF(0, 30.0F, 100.0F, 40.0F);
StringFormat sfDraw = new StringFormat();
sfDraw.Alignment = StringAlignment.Center;
FontStyle fStyle = FontStyle.Regular;
using (var gpDraw = new GraphicsPath())
{
gpDraw.AddString(text, fFamily, (int)fStyle, fSize, r, sfDraw);
SolidBrush brushFG = new SolidBrush(Color.Black);
Pen pen = new Pen(brushFG, 0.01F);
g.DrawPath(pen, gpDraw);
}
It gives an output similar to this: http://i47.tinypic.com/mruu4j.jpg
What I want now is to fill this outline. Not simply with a brush-fill (as can easily be accomplished with g.FillPath(brushFG, gpDraw).
It should instead be "filled" with smaller and smaller outlines, like shown on this image: http://i46.tinypic.com/b3kb29.png
(the different line colors are only used to make the example clearer).
As I made the example in Photoshop, I realized that what I am actually trying to do is to mimmick the functionality, that you find in Photoshop's Select/Modify/Contract.
But I am at my wit's end as to how I accomplish this.
Any help? I'm not looking for a complete solution, but I am at the moment completely stuck. I've tried simple scaling, which probably is the wrong way (since it does not produce the right result...)
UPDATE 2012-07-16: I am now using the Clipper Library http://www.angusj.com/delphi/clipper.php which has a wonderful function called OffsetPolygons.
My test-code is shown here: http://pastie.org/4264890
It works fine with "single" polygons - e.g. a "C" since it only consists of a single polygon. An "O" consist of two polygons - an inside and outside. Likewise with "A". And these give me some trouble. See these images:
C: http://i46.tinypic.com/ap304.png O: http://i45.tinypic.com/35k60xg.jpg A: http://i50.tinypic.com/1zyaibm.png B: http://i49.tinypic.com/5lbb40.png
You get the picture (heh heh... ;-)
I think the problem is, that I extract everything from GraphicsPath as a single polygon, when there are actually 2 (in the case of A and O), and 3 in the case of a B.
Clipper's OffsetPolygons actually takes an array of polygons, so I guess it is able to do this right. But I don't know how to extract my paths from GraphicsPath as seperate polygons.
UPDATE 2012-07-16 (later in the day):
Okay I've actually managed to pull it off now, and will explain it in an answer, in the hope that it might help others with similar problems.
And a big thank you to everybody who helped along the way! Only reason that I accept my own answer is so that others might benefit from this question with a full-baked solution.
Upvotes: 3
Views: 384
Reputation: 1258
Using the Clipper library was only half of the battle.
I extracted all points from GraphicsPath in a single array, thus inadvertently creating a misshapen polygon based on 2 seperate polygons (in the case of "A").
Instead I needed to examine the PointTypes array property on GraphicsPath. Everytime a point has a PointType == 0 it means the beginning of a new polygon. So the extracting method should use this and instead return an array of polygons instead of just a single polygon:
private ClipperPolygons graphicsPathToPolygons(GraphicsPath gp)
{
ClipperPolygons polyList = new ClipperPolygons();
ClipperPolygon poly = null;
for (int i = 0; i < gp.PointCount; i++)
{
PointF p = gp.PathPoints[i];
byte pType = gp.PathTypes[i];
if (pType == 0)
{
if (poly != null)
polyList.Add(poly);
poly = new ClipperPolygon();
}
IntPoint ip = new IntPoint();
ip.X = (int)(p.X * pointScale);
ip.Y = (int)(p.Y * pointScale);
poly.Add(ip);
}
if (poly != null)
polyList.Add(poly);
return polyList;
}
Clipper's OffsetPolygons actually WANTS a list of polygons, so this ought to have been obvious to me earlier.
The entire code can be seen here: http://pastie.org/4265265
And if you're curious, I've zipped the entire test-project here to open in Visual Studio and compile.
http://gehling.dk/wp-content/uploads/2012/07/TestClipper.zip
It has not been optimized for speed in any way.
/ Carsten
Upvotes: 1
Reputation: 19981
Take a look at An algorithm for inflating/deflating (offsetting, buffering) polygons -- the questioner there is actually asking about the reverse operation, but the answers there apply to your case as well. One of them (the highest rated) has a pointer to an open source library that has a C# version.
The usual name for the operation you describe is "polygon offsetting", by the way.
Upvotes: 2