Como controlar o preenchimento de uma forma composta

A FillRule propriedade de um GeometryGroup ou um PathGeometry, especifica uma "regra" que a forma composta usa para determinar se um determinado ponto faz parte da geometria. Há dois valores possíveis para FillRule: EvenOdd e Nonzero. As seções a seguir descreverão como usar essas duas regras.

EvenOdd: Essa regra determina se um ponto está na região de preenchimento desenhando um raio desse ponto para infinito em qualquer direção e contando o número de segmentos de caminho dentro da forma determinada que o raio cruza. Se esse número for ímpar, o ponto estará dentro; se for par, o ponto estará fora.

Por exemplo, o XAML abaixo cria uma forma composta de uma série de anéis concêntricos (destino) com um FillRule definido como 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>

A ilustração a seguir mostra a forma criada no exemplo anterior.

Um círculo composto por anéis concêntricos de série com cores alternadas.

Na ilustração anterior, observe que o centro e o terceiro anel não estão preenchidos. Isso ocorre porque um raio extraído de qualquer ponto dentro de qualquer um desses dois anéis passa por um número par de segmentos. Confira a ilustração a seguir:

Diagrama mostrando os raios EvenOdd desenhados no círculo.

NonZero: Esta regra determina se um ponto está na região de preenchimento do caminho ao desenhar um raio desse ponto para o infinito em qualquer direção e então examinar os lugares onde um segmento da forma cruza o raio. Começando com uma contagem de zero, adicione uma cada vez que um Segmento cruza o raio da esquerda para a direita e subtraia um cada vez que um segmento de caminho cruza o raio da direita para a esquerda. Depois de contar as travessias, se o resultado for zero, o ponto estará fora do caminho. Caso contrário, está dentro.

<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>

Usando o exemplo anterior, um valor de Nonzero para FillRule resulta na seguinte ilustração:

Um círculo composto por anéis concêntricos de série, todos preenchidos com a mesma cor.

Como você pode ver, todos os anéis estão preenchidos. Isso ocorre porque todos os segmentos estão alinhados na mesma direção e, portanto, um raio traçado de qualquer ponto atravessará um ou mais segmentos, e o número de interseções não será igual a zero. Por exemplo, na ilustração a seguir, as setas vermelhas representam a direção em que os segmentos são desenhados e a seta branca representa um raio arbitrário originando de um ponto do anel mais interno. Começando com um valor zero, para cada segmento que o raio cruza, um valor de um é adicionado porque o segmento cruza o raio da esquerda para a direita.

Diagrama mostrando o valor da propriedade FillRule igual a Nonzero.

Para demonstrar melhor o comportamento da Nonzero regra, é necessária uma forma mais complexa com segmentos que corram em direções diferentes. O código XAML abaixo cria uma forma semelhante ao exemplo anterior, exceto que ela é criada com um PathGeometry em vez de um EllipseGeometry, o que resulta em quatro arcos concêntricos em vez de círculos concêntricos totalmente fechados.

<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>

A ilustração a seguir mostra a forma criada no exemplo anterior.

Um círculo composto por anéis concêntricos de série com cores alternadas com o terceiro arco não preenchido.

Observe que o terceiro arco do centro não está preenchido. A ilustração a seguir mostra por que isso é. Na ilustração, as setas vermelhas representam a direção em que os segmentos são desenhados. As duas setas brancas representam dois raios arbitrários que se movem de um ponto na região "vazia". Como pode ser visto na ilustração, a soma dos valores de um determinado raio que cruza os segmentos em seu caminho é zero. Conforme definido acima, uma soma de zero significa que o ponto não faz parte da geometria (não faz parte do preenchimento), enquanto uma soma que não é zero, incluindo um valor negativo, faz parte da geometria.

Diagrama mostrando os raios arbitrários cruzando segmentos.

Observação

Para fins de FillRule, todas as formas são consideradas fechadas. Se houver uma lacuna em um segmento, desenhe uma linha imaginária para fechá-la. No exemplo acima, há pequenas lacunas nos anéis. Dado isso, pode-se esperar que um raio que percorre a lacuna dê um resultado diferente do que um raio que percorre em outra direção. Veja abaixo uma ilustração ampliada de uma dessas lacunas e do "segmento imaginário" (segmento que é desenhado para fins de aplicação do FillRule) que o fecha.

Diagrama mostrando segmentos FillRule que estão sempre fechados.

Exemplo

Consulte também