Carsten Gehling
Carsten Gehling

Reputation: 1258

Filling a path with smaller versions of same path

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

Answers (2)

Carsten Gehling
Carsten Gehling

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

Gareth McCaughan
Gareth McCaughan

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

Related Questions