iOS Winding Rules 纏繞規則

QiShare發表於2019-03-04

級別: ★★☆☆☆
標籤:「iOS」「layer」「貝塞爾」「WindingRules」
作者: 憶思夢
審校: QiShare團隊

前言:之前在iOS開源掃碼專案——QiQRCode中:在實現一個鏤空的效果時,發現路徑的方向會影響最終實現的效果,所以進一步研究了一下。

填充路徑所包含的區域時,會通過纏繞規則來判斷需要填充的區域。通過給定區域內的任意一點到路徑外畫一條射線,根據與路徑的交叉數判斷點是否在區域內。

纏繞規則:

  • NSNonZeroWindingRule:非零纏繞。射線從左到右每交叉路徑一次+1,從右到左每交叉一次-1。如果最終交叉數為0,則該點在路徑之外;如果交叉數不為0,則在路徑之內。預設纏繞規則。

  • NSEvenOddWindingRule:奇偶纏繞。計算射線與路徑的交叉總數,如果為偶數,則在路徑之外;如果為奇數,則在路徑之內,需要填充。

填充操作適用於開放式路徑和閉合路徑。開放式路徑會從路徑的最後一個點到第一個點建立一個隱式的線(不渲染),來閉合路徑。

developer.apple.com/library/arc… 中的 Winding Rules中:
When you fill a partial subpath, NSBezierPath closes it for you automatically by creating an implicit (non-rendered) line from the first to the last point of the subpath.
文件中描述是從第一個點到最後一個點,但是根據分析與文件上的圖以及實驗,圖與結果相同,但是描述錯誤,下面會詳細介紹。如果是我理解錯誤,懇請指出。

本文demo

閉合路徑

1. 非零纏繞:外邊框和內邊框同一方向

CGRect aRect = CGRectMake(100, 100, 200, 200);
UIBezierPath * aPath = [UIBezierPath bezierPathWithRect:aRect];
CGRect bRect = CGRectInset(aRect, 50, 50);
UIBezierPath * bPath = [UIBezierPath bezierPathWithRect:bRect];
    
[aPath appendPath:bPath];
    
CAShapeLayer * shapeLayer = [CAShapeLayer layer];
shapeLayer.path = aPath.CGPath;
shapeLayer.fillColor = [UIColor yellowColor].CGColor;
    
[self.view.layer addSublayer:shapeLayer];
複製程式碼
iOS Winding Rules 纏繞規則

內部的點向外畫射線,由於兩個貝塞爾曲線是同向,射線由右至左跨過路徑兩次,aRect 以內的所有的點的射線交叉數只有兩種情況:0-1=-1,或者0-1-1=-2。都不為0,所以內部的點都在路徑之內,需要渲染。

2. 非零纏繞:內邊框與外邊框反向

// - (UIBezierPath *)bezierPathByReversingPath;  將路徑翻轉。
// 上面程式碼只需要修改 bPath
UIBezierPath * bPath = [[UIBezierPath bezierPathWithRect:bRect] bezierPathByReversingPath];
複製程式碼
iOS Winding Rules 纏繞規則

分為兩種情況:

  • bPath 以內的點的射線與路徑交叉只有一種:0+1-1=0,因此 bPath 內部的點都在路徑最終路徑之外,bPath 以內的點不需要渲染。

  • bPath 以外 aPath 以內的點的射線與路徑交叉有兩種:0-1=-1,或者0+1+1-1=1。兩種情況都不為0,所以在路徑之內,需要渲染。

奇偶纏繞規則

奇偶纏繞規則下,與內外路徑方向無影響。預設 fillRule 為非零,新增如下程式碼。

shapeLayer.fillRule = kCAFillRuleEvenOdd;
複製程式碼

只判斷射線與路徑的交叉,所以有兩種情況:

  • bRect內部的點,射線與路徑的交叉有兩個,為偶數,所以不在範圍內,不需要渲染。

  • bRect之外aRect以內,射線與路徑的交叉數可能為1或3,為奇數,所以需要渲染。


開放式路徑

盜取蘋果文件的圖來分析一下,圖 c、d 中的射線,按照從上到下從 左->右 命名為 p1,p2,p3。

iOS Winding Rules 纏繞規則

非零纏繞規則下 圖c:

  • p1 從 左->右 穿過隱式路徑,在從 左->右 穿過路徑,,0+1+1=2,在路徑之內,需要渲染。
  • p2 先是從 左->右 穿過路徑,再從 右->左 穿過,0+1-1=0,不在路徑之內,不需要渲染。
  • p3 兩次從 左->右 穿過路徑,0+1+1=2,在路徑之內,需要渲染。

這裡分析一下隱式線的方向問題,修改一下 p2 的方向為垂直向上。

  • 隱式線的方向是 start->end,首先從 右->左 穿過路徑,然後還是從 右->左 穿過隱式線,0-1-1=-2,不為0,應該是屬於路徑之內,需要渲染的,與原結果衝突。
  • 隱式線的方向是 end->start,首先從 右->左 穿過路徑,然後從 左->右 穿過隱式線,0-1+1=0,為0不在路徑內,不需要渲染,與原結果相同。

奇偶纏繞規則下 圖d:

  • p1 穿過隱式線和一次路徑,共兩次,偶數,不在範圍內,不會渲染。
  • p2和p3 穿過兩次路徑,共兩次,偶數,不在範圍內,不會渲染。

小編微信:可加並拉入《QiShare技術交流群》。

iOS Winding Rules 纏繞規則

關注我們的途徑有:
QiShare(簡書)
QiShare(掘金)
QiShare(知乎)
QiShare(GitHub)
QiShare(CocoaChina)
QiShare(StackOverflow)
QiShare(微信公眾號)

推薦文章:
iOS 簽名機制
iOS 掃描二維碼/條形碼
iOS 瞭解Xcode Bitcode
iOS 重繪之drawRect
奇舞週刊

相關文章