[iOS]CAShapeLayerの二つのfillRuleの違い(修正版)

2016.11.10

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

こんぬづは、久々にゲームを遊んでいたらなんらかの不具合でセーブがまったくできない状態になってしまい絶望している田中です。
ルートクリア後に現れるルートとかどうしろと...。

以前同じ題材で記事を書いていたのですが、内容に誤りがあったため修正版として今記事を執筆していきます。
間違っていた元記事は以下になります、申し訳ありませんでした!

fillRuleの使いどころと記事の執筆の動機

以下のリンク先がやりたかったことです。
UIViewをくり抜く - Qiita

色付きのLayerとそのLayerをくりぬくためのLayerを用意して前者に後者をmaskとして使う。
これをその通り実装して動きはしましたが、

maskLayer.fillRule = kCAFillRuleEvenOdd

この箇所の意味だけ理解できず、調べてみたのでまとめてみました。

CAShapeLayerのfillRuleとは?

fillRuleとは、pathの塗りつぶしのルールのことです。
塗りつぶしルールとは以下のような二つのパスが重なった図形があった場合に、内側の円の領域を塗りつぶすのかはたまた塗りつぶさない(=くり抜く)のかなどを判別するためのルールです。

IMG_5279

fillRuleには以下の二つがあります。

  • kCAFillRuleNonZero
  • kCAFillRuleEvenOdd

それぞれの違いについて見ていきましょう。

二つのfillRuleの違い

ある重なったパスが塗りつぶしの対象となる領域内なのか領域外なのかを判別するのが塗りつぶしルールとなるわけなのですが、それを判別するには判別したい領域から任意の方向に無限遠まで伸びる射線を引きます。
判別したい領域から引いた射線と、設定しているパスとの交点の関係をどう判定するのかが塗りつぶしルールの違いになります。

kCAFillRuleNonZero(デフォルト)

ある点から引いた無限遠まで伸びる射線と交わったパスが、射線を北向きと考えたときに射線に対してパスが左から右へ交差する場合は+1、右から左へ交差する場合は-1としてカウントし、合計が0であった場合は領域外、0以外だった場合は領域内と判別するルールです。

IMG_5292

IMG_5291

kCAFillRuleEvenOdd

ある点から引いた無限遠まで伸びる射線とパスの交わった回数をカウントし、合計が0または偶数ならばその点は領域外、奇数ならば領域内と判別するルールです。

IMG_5290

サンプルコード

以下のGitHubに置いておきました。

cm-TanakaKenji/FillRuleTest: The project to make sure of FillRule.

動きはこのような感じです。

スクリーンショット 2016-11-10 11.25.20

まとめ

考え方やルールを理解するのは楽しいですね!

くり抜いた描画を行う機会はあまり多くはありませんが、リッチな表現を実装したいときや、ユーザーの注意を引くためのチュートリアルなどでこういった実装を行うライブラリなどが存在します。
サンプルコードにも記載した通りですが、コード量も多くはないのでサクッと実装したいときにも考え方を理解していれば簡単なのでオススメです。
お役立ていただければと思います。

また、元記事にて間違えた内容を記載していたことを改めてお詫びいたします!m(_ _)m

参考