如何:控制复合形状的填充
更新:2007 年 11 月
GeometryGroup 或 PathGeometry 的 FillRule 属性指定复合形状用来确定给定点是否是几何形状的一部分的“规则”。FillRule 有两个可能值:EvenOdd 和 Nonzero。下面几节介绍如何使用这两个规则。
**EvenOdd:**此规则确定一个点是否位于填充区域内,具体方法是从该点沿任意方向画一条无限长的射线,然后计算该射线在给定形状中因交叉而形成的路径段数。如果该数为奇数,则点在内部;如果为偶数,则点在外部。
例如,下面的 XAML 将 FillRule 设置为 EvenOdd,创建一个由一系列同心环(目标)组成的复合形状。
<Path Stroke="Black" StrokeThickness="1" Fill="#CCCCFF">
<Path.Data>
<GeometryGroup FillRule="EvenOdd">
<EllipseGeometry RadiusX="50" RadiusY="50" Center="75,75" />
<EllipseGeometry RadiusX="70" RadiusY="70" Center="75,75" />
<EllipseGeometry RadiusX="100" RadiusY="100" Center="75,75" />
<EllipseGeometry RadiusX="120" RadiusY="120" Center="75,75" />
</GeometryGroup>
</Path.Data>
</Path>
下图显示在上一示例中创建的形状。
在上图中,请注意中心和第 3 环未填充。这是因为从这两个环中的任何一个内部的任何点绘制的射线都穿过偶数数目的段。请参见下图:
**NonZero:**此规则确定一个点是否位于路径的填充区域内,具体方法是从该点沿任意方向画一条无限长的射线,然后检查形状段与射线的交点。从零开始计数,每当线段从左向右与射线相交时就加 1,而每当路径段从右向左与射线相交时就减 1。计算交点的数目后,如果结果为零,则说明该点位于路径外部。否则,它位于路径内部。
<Path Stroke="Black" StrokeThickness="1" Fill="#CCCCFF">
<Path.Data>
<GeometryGroup FillRule="NonZero">
<EllipseGeometry RadiusX="50" RadiusY="50" Center="75,75" />
<EllipseGeometry RadiusX="70" RadiusY="70" Center="75,75" />
<EllipseGeometry RadiusX="100" RadiusY="100" Center="75,75" />
<EllipseGeometry RadiusX="120" RadiusY="120" Center="75,75" />
</GeometryGroup>
</Path.Data>
</Path>
使用上面的示例,FillRule 的 Nonzero 值给出了如下图所示的结果:
您可以看到,所有环都填充。这是因为所有段都沿相同的方向绘制,因此从任何点画的射线都将与一个或多个段交叉,交点的总数不等于零。例如,在下图中,红色箭头表示段的绘制方向,白色箭头表示从内部环中的点绘制的任意射线。从零开始,对于射线与之交叉的每个段,都增加值一,因为段按照从左向右的方向与射线交叉。
为了更好地演示 Nonzero 规则的行为,需要一个具有沿不同方向绘制的段的更复杂形状。下面的 XAML 代码创建一个与上面的示例类似的形状,不同之处在于它是使用 PathGeometry 创建的,而不是使用 EllipseGeometry 创建的,结果是创建四个同心弧,而不是创建完全封闭的同心圆。
<Path Stroke="Black" StrokeThickness="1" Fill="#CCCCFF">
<Path.Data>
<GeometryGroup FillRule="NonZero">
<PathGeometry>
<PathGeometry.Figures>
<!-- Inner Ring -->
<PathFigure StartPoint="10,120">
<PathFigure.Segments>
<PathSegmentCollection>
<ArcSegment Size="50,50" IsLargeArc="True" SweepDirection="CounterClockwise" Point="25,120" />
</PathSegmentCollection>
</PathFigure.Segments>
</PathFigure>
<!-- Second Ring -->
<PathFigure StartPoint="10,100">
<PathFigure.Segments>
<PathSegmentCollection>
<ArcSegment Size="70,70" IsLargeArc="True" SweepDirection="CounterClockwise" Point="25,100" />
</PathSegmentCollection>
</PathFigure.Segments>
</PathFigure>
<!-- Third Ring (Not part of path) -->
<PathFigure StartPoint="10,70">
<PathFigure.Segments>
<PathSegmentCollection>
<ArcSegment Size="100,100" IsLargeArc="True" SweepDirection="CounterClockwise" Point="25,70" />
</PathSegmentCollection>
</PathFigure.Segments>
</PathFigure>
<!-- Outer Ring -->
<PathFigure StartPoint="10,300">
<PathFigure.Segments>
<ArcSegment Size="130,130" IsLargeArc="True" SweepDirection="Clockwise" Point="25,300" />
</PathFigure.Segments>
</PathFigure>
</PathGeometry.Figures>
</PathGeometry>
</GeometryGroup>
</Path.Data>
</Path>
下图显示在上一示例中创建的形状。
请注意,从中心开始数的第三个弧未填充。下图说明了原因。图中,红色箭头表示段的绘制方向。两个白色箭头表示从“非填充”区域中的点移出的两个任意射线。从图中可看出,给定射线与其路径中的段的交叉值总和是零。前面已定义,总和为零意味着该点不是几何形状的一部分(不是填充区域的一部分),而总和不为 零(包括负值)的点是几何形状的一部分。
**注意:**为了说明 FillRule,所有形状都被视为是封闭的。如果段中存在间隙,则绘制一条假想线来使它封闭。在上面的示例中,环中存在小的间隙。在给定上述条件的情况下,某个用户可能希望穿过此间隙的射线与另一方向的射线有不同的结果。下面是上述间隙之一以及使它封闭的“假想段”(为应用 FillRule 而绘制的段)的放大图。