Draw an arbitrary shape

I need to draw lines to bitmaps using a custom aperture. The example below has two lines. The left line is horizontal. The right line is diagonal - down and to the right: Example image In the example image, the aperture and path are shown in red. The resulting geometry is shown in black. Both of the examples above use the same aperture, but wipe it down a different path.

I would like to draw with an arbitrary curly pen, although the above example only shows rectangles.

I tried to use the System.Drawing.2D namespace, but it doesn't seem to do what I need it to do. The use of custom end caps on the handles seemed promising, but the end caps rotate with the direction of the line. Also, getting the correct line width seems like a daunting task.failed end-caps example

I've considered drawing the diaphragm over and over again, centered at different points along the path, but that doesn't seem to be indicative. It is also quite difficult to minimize the number of aperture draws.

The best idea I got was to try and draw a "line" like a completed shape. At first I thought the convex hull algorithm would be the answer - just take the aperture vertices at the start of the draw and at the end of the draw and run them through the convex hull algorithm to find the "outer" vertices. This works for my first example above, but the star aperture demonstrates that this solution is incomplete. It only works when the aperture is convex itself. enter image description here

Simply placing all vertices through the convex hull algorithm will result in the areas highlighted in blue being filled, but I only need to fill the black areas.

+3


source to share


4 answers


The best idea I have is for redundant drawing, but I think it will work.

Treat the aperture polygon as a list of line segments. For each line segment in the list, draw a parallelogram using the aperture line segment at the start of the move, and using the same aperture line segment at the end of the move Parallelogram Draw

The above image shows this for three of the ten line segments that make up the star. Notice that most of the green area is already filled with blue area and red area, with a small area in the lower right star, only green. Overfilling these pixels is useless, but I think it will result in the correct image.



Other thoughts:

Perhaps there is a way to do this without overfilling in the same area, and this is probably due to beams. Notice how the intersection of the red and green lines in the lower right corner will fall to a vertex that is not part of the original aperture. You will need to draw a ray from the right vertex of the right polygon along the direction of travel and find where that intersects the green line in order to draw the final shape correctly.

0


source


It's an ugly, inelegant solution, but you can take a vector that represents the direction and length of the stroke and redraw your shape along that vector many times. (You will need to choose the interval at which you want to draw it so you don't leave any gaps.) This will produce correct results if you want aliases; it will smooth out any anti-aliasing done by GDI + because the pixels with midtones will potentially be drawn, making them darker than they should be.



+1


source


In fact, you are on the right track with the solution you created ... using separate aperture segments to create parallelograms. However, you will run into problems when trying to fill this with anything other than a solid brush, because in reality you will be doing repeating fills.

Your best bet is to create all of these polygons as you said, but then merge them all together. You will get one polygon representing the outline you want, then you can simply fill it however you want.

If you're using WPF, just use GeometryGroup

with all your polygons as children, and then use that group as the first geometry in CombinedGeometry

Union or Exclude modes, passing null for the second parameter.

If you are not using WPF, you can try a library like Clipper, which has a version written in C # /. NET, but more for freehand drawing than just WPF.

Hope this helps!

+1


source


I solved this problem in WPF. I needed a solution that could make complex shapes with curves and holes. The closest I got was using GetFlattenedPathGeometry in its original shape, which converts the curves to line segments and then runs the method mentioned above. Lots of redundant computation, but combining parallelograms and then β€œembossing” the original shape at the beginning and end indicates relatively few geometries in the final geometry.

public Geometry translateGeo(Geometry baseGeo, Point translate)
{
    // sweeps a geometry linearly from current location through vector

    Debug.WriteLine("Translating Geometry");
    Debug.WriteLine("Original Outline Verticies: " + CountVerticies(baseGeo.GetFlattenedPathGeometry()));

    Geometry sweptPathGeo = baseGeo.Clone();
    Geometry capGeo = baseGeo.Clone();
   
    capGeo.Transform = new TranslateTransform(translate.X, translate.Y);
    sweptPathGeo = new CombinedGeometry(GeometryCombineMode.Union, sweptPathGeo, capGeo);
    sweptPathGeo = sweptPathGeo.GetFlattenedPathGeometry();


    geometry = sweptPathGeo.Clone();


    PathGeometry pathGeo = baseGeo.GetFlattenedPathGeometry();

    foreach (PathFigure figure in pathGeo.Figures)
    {
        Point startPoint = figure.StartPoint;
        //Debug.WriteLine(startPoint.X + ", " + startPoint.Y);
        foreach (PathSegment segment in figure.Segments)
        {
            PolyLineSegment polySegment = segment as PolyLineSegment;
            if (polySegment != null)
            {
                foreach (Point point in polySegment.Points)
                {
                    sweptPathGeo = new CombinedGeometry(GeometryCombineMode.Union, sweptPathGeo, getShadow(startPoint, point, translate));
                    startPoint = point;
                }
            }

            LineSegment lineSegment = segment as LineSegment;
            if (lineSegment != null)
            {
                sweptPathGeo = new CombinedGeometry(GeometryCombineMode.Union, sweptPathGeo, getShadow(startPoint, lineSegment.Point, translate));
                startPoint = lineSegment.Point;
            }
        }
    }

    //sweptPathGeo = sweptPathGeo.GetOutlinedPathGeometry();
    Debug.WriteLine("Finale Outline Verticies: " + CountVerticies(sweptPathGeo.GetFlattenedPathGeometry()));
    return sweptPathGeo;

}
public Geometry getShadow(Point startPoint, Point endPoint, Point translate)
{

    PointCollection points = new PointCollection();
    points.Add(startPoint);
    points.Add(endPoint);
    points.Add(new Point(endPoint.X + translate.X, endPoint.Y + translate.Y));
    points.Add(new Point(startPoint.X + translate.X, startPoint.Y + translate.Y));
    points.Add(startPoint);

    Polygon poly = new Polygon();
    poly.Points = points;
    poly.Arrange(geometry.Bounds);
    poly.Measure(geometry.Bounds.Size);

        PathGeometry returnGeo = poly.RenderedGeometry.GetOutlinedPathGeometry();

    return returnGeo;
    //foreach (Point point in points) Debug.WriteLine(point.X + ", " + point.Y);

}
private int CountVerticies(PathGeometry geo)
{
    int verticies = 0;
    foreach (PathFigure figure in geo.Figures)
    {
        Point startPoint = figure.StartPoint;
        verticies += 1;
        foreach (PathSegment segment in figure.Segments)
        {
            PolyLineSegment polySegment = segment as PolyLineSegment;
            if (polySegment != null) verticies += polySegment.Points.Count;
            
            LineSegment lineSegment = segment as LineSegment;
            if (lineSegment != null) verticies += 1;
        }
    }
    return verticies;
}
      

Run codeHide result


0


source







All Articles