如何:控制复合形状的填充
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>
下图显示在上一个示例中创建的形状。
在前面的图中,请注意,中心和第三个环并未填充。 这是因为射线是从穿过偶数段的这两个环中的点绘制的。 请参阅下图:
NonZero:此规则通过从一点向任意方向绘制一条射向无穷远的射线,并检查一段形状与射线相交的位置,从而确定该点是否位于路径的填充区域。 从零计数开始,从左到右每次添加与射线相交的一个段,然后从右到左每次减去与射线相交的一个路径段。 在对交叉点进行计数后,如果结果为零,那么该点则位于路径外。 否则,该点则在路径内。
<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 值的结果如下图所示:
如图所示,所有环都已填充。 这是因为所有段按同一方向运行,因此从任一点绘制的射线将与一个或多个段相交,并且交点总数不会等于零。 例如,在下图中,红色箭头表示段的绘制方向,白色箭头表示从最内部环中的某一个点运行的任意一条射线。 从零值开始,对于该射线相交的每个段,会加值“1”,因为从左到右该段与该射线相交。
为更好地演示 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 而绘制的段)。