在實現(xiàn)一個鏤空的效果時使套,發(fā)下路徑的方向病往,會影響最終實現(xiàn)的效果美尸,所以進一步研究了一下冤议。
填充路徑所包含的區(qū)域時,會通過纏繞規(guī)則來判斷需要填充的區(qū)域师坎。通過給定區(qū)域內(nèi)的任意一點到路徑外畫一條射線恕酸,根據(jù)與路徑的交叉數(shù)判斷點是否在區(qū)域內(nèi)。
纏繞規(guī)則:
- NSNonZeroWindingRule:非零纏繞胯陋。射線從左到右每交叉路徑一次+1蕊温,從右到左每交叉一次-1袱箱。如果最終交叉數(shù)為0,則該點在路徑之外义矛;如果交叉數(shù)不為0发笔,則在路徑之內(nèi)。默認(rèn)纏繞規(guī)則凉翻。
- NSEvenOddWindingRule:奇偶纏繞了讨。計算射線與路徑的交叉總數(shù),如果為偶數(shù)噪矛,則在路徑之外量蕊;如果為奇數(shù),則在路徑之內(nèi)艇挨,需要填充残炮。
填充操作適用于開放式路徑和閉合路徑。開放式路徑會從路徑的最后一個點到第一個點創(chuàng)建一個隱式的線(不渲染)缩滨,來閉合路徑势就。
https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/CocoaDrawingGuide/Paths/Paths.html#//apple_ref/doc/uid/TP40003290-CH206-BAJIJJGD 中的 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.
文檔中描述是從第一個點到最后一個點,但是根據(jù)分析與文檔上的圖以及實驗脉漏,圖與結(jié)果相同苞冯,但是描述錯誤,下面會詳細介紹侧巨。如果是我理解錯誤舅锄,懇請指出。
閉合路徑
1. 非零纏繞:外邊框和內(nèi)邊框同一方向
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];
內(nèi)部的點向外畫射線司忱,由于兩個貝塞爾曲線是同向皇忿,射線由右至左跨過路徑兩次,aRect 以內(nèi)的所有的點的射線交叉數(shù)只有兩種情況:0-1=-1坦仍,或者0-1-1=-2鳍烁。都不為0,所以內(nèi)部的點都在路徑之內(nèi)繁扎,需要渲染幔荒。
2. 非零纏繞:內(nèi)邊框與外邊框反向
// - (UIBezierPath *)bezierPathByReversingPath; 將路徑翻轉(zhuǎn)。
// 上面代碼只需要修改 bPath
UIBezierPath * bPath = [[UIBezierPath bezierPathWithRect:bRect] bezierPathByReversingPath];
分為兩種情況:
- bPath 以內(nèi)的點的射線與路徑交叉只有一種:0+1-1=0梳玫,因此 bPath 內(nèi)部的點都在路徑最終路徑之外爹梁,bPath 以內(nèi)的點不需要渲染。
- bPath 以外 aPath 以內(nèi)的點的射線與路徑交叉有兩種:0-1=-1提澎,或者0+1+1-1=1卫键。兩種情況都不為0,所以在路徑之內(nèi)虱朵,需要渲染莉炉。
奇偶纏繞規(guī)則
奇偶纏繞規(guī)則下钓账,與內(nèi)外路徑方向無影響。默認(rèn) fillRule 為非零絮宁,添加如下代碼梆暮。
shapeLayer.fillRule = kCAFillRuleEvenOdd;
只判斷射線與路徑的交叉,所以有兩種情況:
- bRect內(nèi)部的點绍昂,射線與路徑的交叉有兩個啦粹,為偶數(shù),所以不在范圍內(nèi)窘游,不需要渲染唠椭。
- bRect之外aRect以內(nèi),射線與路徑的交叉數(shù)可能為1或3忍饰,為奇數(shù)贪嫂,所以需要渲染。
開放式路徑
盜取蘋果文檔的圖來分析一下艾蓝,圖 c力崇、d 中的射線,按照從上到下從 左->右 命名為 p1,p2,p3赢织。
非零纏繞規(guī)則下 圖c:
- p1 從 左->右 穿過隱式路徑亮靴,在從 左->右 穿過路徑,于置,0+1+1=2茧吊,在路徑之內(nèi),需要渲染八毯。
- p2 先是從 左->右 穿過路徑搓侄,再從 右->左 穿過,0+1-1=0宪彩,不在路徑之內(nèi)休讳,不需要渲染讲婚。
- p3 兩次從 左->右 穿過路徑尿孔,0+1+1=2,在路徑之內(nèi)筹麸,需要渲染活合。
這里分析一下隱式線的方向問題,修改一下 p2 的方向為垂直向上物赶。
- 隱式線的方向是 start->end白指,首先從 右->左 穿過路徑,然后還是從 右->左 穿過隱式線酵紫,0-1-1=-2告嘲,不為0错维,應(yīng)該是屬于路徑之內(nèi),需要渲染的橄唬,與原結(jié)果沖突赋焕。
- 隱式線的方向是 end->start,首先從 右->左 穿過路徑仰楚,然后從 左->右 穿過隱式線隆判,0-1+1=0,為0不在路徑內(nèi)僧界,不需要渲染侨嘀,與原結(jié)果相同。
奇偶纏繞規(guī)則下 圖d
- p1 穿過隱式線和一次路徑捂襟,共兩次咬腕,偶數(shù),不在范圍內(nèi)笆豁,不會渲染郎汪。
- p2和p3 穿過兩次路徑,共兩次闯狱,偶數(shù)煞赢,不在范圍內(nèi),不會渲染哄孤。