Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Completely Filling Custom Geometries

Tags:

c#

wpf

I'm trying to figure out how to completely fill a custom geometry. It seems like it should be a common question, but I haven't really been able to find any solutions.

I have the following example geometry:

    <Path Fill="White" Stretch="Fill" Stroke="Black" StrokeThickness="2">
        <Path.Data>
            <PathGeometry
                Figures="M112,296C112,296 136,296 136,320 M112,344C112,344 136,344 136,320 M112,296L112,296 96,296 96,344 112,344"/>
        </Path.Data>
    </Path>

Which produces the following result:

Undesired Fill Result

This is the result I would like to see:

Desired Fill Result

Any Ideas? I know I could make a single arc and that would resolve this particular case, but in my application the user can draw any type of geometry so the result could be composed of any number of "primitives" (PolyLineSegments, EllipseGeometries, ArcSegments, etc) and in the case that the resultant geometry contains some type of closed area, I'd like to accurately fill that area.

EDIT:

Here is an example of what a CombinedGeometry looks like if I ensure all three adjacent geometries overlap and create a unioned CombinedGeometry with the following code:

        <Path Grid.Row="2" Fill="White" Stretch="Fill" Stroke="Black" StrokeThickness="2">
        <Path.Data>
            <CombinedGeometry GeometryCombineMode="Union">
                <CombinedGeometry.Geometry1>
                    <CombinedGeometry GeometryCombineMode="Union">
                        <CombinedGeometry.Geometry1>
                            <PathGeometry
                                Figures="M111,293C111,296 136,293 136,325"/>
                        </CombinedGeometry.Geometry1>
                        <CombinedGeometry.Geometry2>
                            <PathGeometry
                                Figures="M111,346C111,344 136,344 136,320"/>
                        </CombinedGeometry.Geometry2>
                    </CombinedGeometry>
                </CombinedGeometry.Geometry1>
                <CombinedGeometry.Geometry2>
                    <PathGeometry
                        Figures="M125,296L115,296 96,296 96,344 120,344"/>
                </CombinedGeometry.Geometry2>
            </CombinedGeometry>
        </Path.Data>
    </Path>

And here is the result:

Combined Geometry

I was hoping it would union just the strokes and automagically figure out the correct filling for the new contiguous polygon... bummer.

EDIT 2:

Hmm so I think I've come up with a couple possible solutions, neither of which are as easy as I was hoping they would be. The first option would be to combine all of the geometries as above using a CombineGeometry structure, then I called "GetFlattenedPathGeometry" on the resultant "CombineGeometry" in order to get a PathGeometry. Next I iterate over each Figure in the PathGeometry and just remove those which are holes (which I think you should be able to do by getting rid of all the figures which are entirely contained by another, or holes may follow a convention of having either clockwise or counter-clockwise coordinates, not sure..), if all goes well you should be left with a single fully filled geometry.

The second option would be to again call "GetFlattenedPathGeometry" on whatever the resultant path is, so as to get a vertex based polygonal approximation of the path (without all the curve, arc, ellipse, etc notation, we want a path containing only points and lines). After that, you would just combine all the resultant figures/segments into a single figure whose segments are ordered either clockwise or counter-clockwise.

I've tested both approaches and they seem to work with at least the simple test case outlined above, though they will not work with more complex shapes (self-intersecting, concave, etc).. support which I require.. so the question remains, how do I do this??

EDIT 3:

Here is a more complicated geometry in which ordering/combining becomes more difficult:

        <Path Fill="White" Stretch="Fill" Stroke="Black" StrokeThickness="2">
        <Path.Data>
            <PathGeometry
            Figures="M104,160C104,160 72,160 72,136
                M104,128C104,128 72,128 72,152
                M152,232L152,232 152,216 120,216 120,160 128,160
                M152,232L152,232 72,232 104,216 104,160 96,160
                M104,128L104,128 168,128
                M128,160L128,160 168,160
                M165,160L168,160 200,128
                M200,160L200,160 200,128
                M200,160L200,160 168,128 152,128"/>
        </Path.Data>
    </Path>

Which produces:

enter image description here

like image 716
Craig Avatar asked Dec 13 '11 21:12

Craig


1 Answers

Your example geometry is a combination of three adjacent shapes which do not overlap. The stroke is drawn on the outside edges simply because the shapes are not closed and the inner stroke lines do not exist. Although it may appear that the shapes are being merged and the stroke is applied to the geometry as a whole, that is not what is happening.

Closing the hole becomes a more complex problem of programmatically detecting the hole and closing it with the appropriate shape or by creating a new combined shape which doesn't have the hole (possibly by detecting and tracing the outer points). I am not aware of any functionality in WPF which can help you with this task. There does exist a CombinedGeometry class which can produce a union of two overlapping shapes. The shapes in this example are not overlapping.

Without context, it is hard to recommend a solution. If this is a free form drawing program, perhaps the user simply has to draw another shape in the middle to close the geometry.

like image 155
Scott Avatar answered Oct 17 '22 13:10

Scott