如何:控制复合形状的填充

GeometryGroupPathGeometryFillRule 属性指定复合形状用来确定给定点是否为几何图形的一部分的“规则”。 FillRule有两个可能的值:EvenOddNonzero。 以下部分将介绍如何使用这两个规则。

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>

下图显示了在上一示例中创建的形状。

由具有交替颜色的系列同心环组成的圆圈。

在上图中,请注意中心环和第三环未填充。 这是因为射线是从穿过偶数段的这两个环中的点绘制的。 请参阅下图:

显示在圆圈中绘制的 EvenOdd 射线的示意图。

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>

使用前面的示例,FillRuleNonzero 值的结果如下图所示:

一个由一系列同心环组成的圆圈都被填充了相同的颜色。

如图所示,所有环都已填充。 这是因为所有段都以相同的方向运行,因此从任何点绘制的光线将交叉一个或多个段,交叉的总和不等于零。 例如,在下图中,红色箭头表示绘制段的方向,白色箭头表示从最内环的点运行的任意光线。 从零值开始,对于该射线相交的每个段,会加值“1”,因为从左到右该段与该射线相交。

关系图,显示 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 而绘制的段)。

显示始终闭合的 FillRule 段的示意图。

另请参阅