級別: ★★☆☆☆
標籤:「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.
文件中描述是從第一個點到最後一個點,但是根據分析與文件上的圖以及實驗,圖與結果相同,但是描述錯誤,下面會詳細介紹。如果是我理解錯誤,懇請指出。
閉合路徑
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];
複製程式碼
內部的點向外畫射線,由於兩個貝塞爾曲線是同向,射線由右至左跨過路徑兩次,aRect 以內的所有的點的射線交叉數只有兩種情況:0-1=-1,或者0-1-1=-2。都不為0,所以內部的點都在路徑之內,需要渲染。
2. 非零纏繞:內邊框與外邊框反向
// - (UIBezierPath *)bezierPathByReversingPath; 將路徑翻轉。
// 上面程式碼只需要修改 bPath
UIBezierPath * bPath = [[UIBezierPath bezierPathWithRect:bRect] bezierPathByReversingPath];
複製程式碼
分為兩種情況:
-
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。
非零纏繞規則下 圖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技術交流群》。
關注我們的途徑有:
QiShare(簡書)
QiShare(掘金)
QiShare(知乎)
QiShare(GitHub)
QiShare(CocoaChina)
QiShare(StackOverflow)
QiShare(微信公眾號)
推薦文章:
iOS 簽名機制
iOS 掃描二維碼/條形碼
iOS 瞭解Xcode Bitcode
iOS 重繪之drawRect
奇舞週刊