在上篇文章中,咱們簡單的理解了繪圖上下文,今天咱們來認識一下Quartz-2D中另外一個重要的概念,路徑(Paths)。數組
1、理解路徑數據結構
路徑定義了一個或多個形狀,或是子路徑。一個子路徑可由直線,曲線,或者同時由二者構成。它能夠是開放的,也能夠是閉合的。一個子路徑能夠是簡單的形狀,如線、圓、矩形、星形;也能夠是復雜的形狀,如山脈的輪廓或者是塗鴉。圖3-1顯示了一些我們能夠創建的路徑。左上角的直線能夠是虛線;直線也能夠是實線。上邊中間的路徑是由多條曲線組成的開放路徑。右上角的同心圓填充了顏色,但沒有描邊。左下角的加利福尼亞州是閉合路徑,由許多曲線和直線構成,且對路徑進行填充和描邊。兩個星形闡明瞭填充路徑的兩種方式,我們將詳細描述。函數
2、創建及繪制路徑ui
路徑創建及路徑繪制是兩個獨立的工做。首先我們創建路徑。當我們須要渲染路徑時,我們須要使用Quartz來繪制它。正如圖3-1中所示,我們能夠選擇對路徑進行描邊,填充路徑,或同時進行這兩種操做。我們同樣能夠將其它對象繪制到路徑所表示的範圍內,即對對象進行裁減。
圖3-2繪制了一個路徑,該路徑包含兩個子路徑。左邊的子路徑是一個矩形,右邊的子路徑是由直線和曲線組成的抽象形狀。兩個子路徑都進行了填充及描邊。spa
圖3-3顯示了多條獨立繪制的路徑。每一個路徑飲食隨機生成的曲線,一些進行填充,另外一些進行了描邊。這些路徑都包含在一個圓形裁減區域內。code
3、構建塊(Building Block)
子路徑是由直線、弧和曲線構成的。Quartz一樣也提供了簡便的函數用於添加矩形或橢圓等形狀。點也是路徑最基本的構建塊,由於點定義了形狀的起始點與終止點。
點
點由x, y座標值定義,用於在用戶空間指定 一個位置。咱們能夠調用函數CGContextMoveToPoint來爲新的子路徑指定起始點。Quartz跟蹤當前點,用於記錄路徑構建過程當中最新的位置。例如,若是調用函數CGContextMoveToPoint並設置位置爲(10, 10),即將當前點移動到位置(10, 10)。若是在水平位置繪製50個單位長度的直線,則直線的終點爲(60, 10),該點變成當前點。直線、弧和曲線老是從當前點開始繪製。
一般咱們經過傳遞(x, y)值給Quartz函數來指定一個點。一些函數須要咱們傳遞一個CGPoint數據結構,該結構包含兩個浮點值。
直線
直線由兩個端點定義。起始點一般是當前點,因此建立直線時,咱們只須要指定終止點。咱們使用函數CGContextAddLineToPoint來添加一條直線到子路徑中。
咱們能夠調用CGContextAddLines函數添加一系列相關的直線到子路徑中。咱們傳遞一個點數組給這個函數。第一個點必須是第一條直線的起始點;剩下的點是端點。Quartz從第一個點開始繪製一個新子路徑,而後每兩個相鄰點鏈接成一條線段。
弧
弧是圓弧段。Quartz提供了兩個函數來建立弧。函數CGContextAddArc從圓中來建立一個曲線段。咱們指定一個圓心,半徑和放射角(以弧度爲單位)。放射角爲2 PI時,建立的是一個圓。圖3-4顯示了多個獨立的路徑。每一個路徑飲食一個自動生成的圓;一些是填充的,另外一些是描邊的。對象
函數CGContextAddArcToPoint用於爲矩形建立內切弧的場景。Quartz使用咱們提供的端點建立兩條正切線。一樣咱們須要提供圓的半徑。弧心是兩條半徑的交叉點,每條半徑都與相應的正切線垂直。弧的兩個端點是正切線的正切點,如圖3-5所示。紅色的部分是實際繪製的部分。blog
下面分別是畫直線和圓弧的代碼圖片
直線:ip
1 // 1. 獲取一個與視圖相關聯的上下文 2 CGContextRef context = UIGraphicsGetCurrentContext(); 3 4 // 2. 構建路徑 5 // 2.1 設置上下文路徑起點 6 CGContextMoveToPoint(context, 85, 85); 7 // 2.2 增長路徑內容…… 8 CGContextAddLineToPoint(context, 150, 150); 9 CGContextAddLineToPoint(context, 250, 50); 10 // 3. 保存上下文狀態 11 // 4. 設置上下文狀態 12 // 4.1 設置邊線顏色 13 CGContextSetRGBStrokeColor(context, 1, 0, 0, 1); 14 // 4.2 設置線寬 15 CGContextSetLineWidth(context, 10); 16 // 4.3 設置線段鏈接樣式 17 CGContextSetLineJoin(context, kCGLineJoinRound); 18 // 4.4 設置線段收尾樣式 19 CGContextSetLineCap(context, kCGLineCapRound); 20 // 4.5 設置虛線樣式 21 // 4. 繪製路徑 22 CGContextDrawPath(context, kCGPathStroke);
圓弧:
// 1. 獲取一個與視圖相關聯的上下文 CGContextRef context = UIGraphicsGetCurrentContext(); [[UIColor redColor]set]; // 2. 添加弧線 // 2.1 上下文 // 2.2 中心點座標 // 2.3 半徑 // 2.4 開始角度,結束角度,角度的單位是「弧度」 CGContextAddArc(context, 160, 230, 100, M_PI, -M_PI_2, 0); // 繪製路徑 CGContextStrokePath(context);
曲線
二次與三次Bezier曲線是代數曲線,能夠指定任意的曲線形狀。曲線上的點經過一個應用於起始、終點及一個或多個控制點的多項式計算得出。這種方式定義的形狀是向量圖的基礎。這個公式比將位數組更容易存儲,而且曲線能夠在任何分辨下從新建立。
圖3-6顯示了一些路徑的曲線。每條路徑包含一條隨機生成的曲線;一些是填充的,另外一些是描邊的。
咱們使用函數CGContextAddCurveToPoint將Bezier曲線鏈接到當前點,並傳遞控制點和端點做爲參數,如圖3-7所示。兩個控制點的位置決定了曲線的形狀。若是兩個控制點都在兩個端點上面,則曲線向上凸起。若是兩個控制點都在兩個端點下面,則曲線向下凹。若是第二個控制點比第一個控制點離得當前點近,則曲線自交叉,建立了一個迴路。
咱們也能夠調用函數CGContextAddQuadCurveToPoint來建立Bezier,並傳遞端點及一個控制點,如圖3-8所示。控制點決定了曲線彎曲的方向。因爲只使用一個控制點,因此沒法建立出如三次Bezier曲線同樣多的曲線。例如咱們沒法建立出交叉的曲線
1 /*畫貝塞爾曲線*/ 2 //二次曲線 3 CGContextMoveToPoint(context, 120, 300);//設置Path的起點 4 CGContextAddQuadCurveToPoint(context,190, 310, 120, 390);//設置貝塞爾曲線的控制點座標和終點座標 5 CGContextStrokePath(context); 6 //三次曲線函數 7 CGContextMoveToPoint(context, 200, 300);//設置Path的起點 8 CGContextAddCurveToPoint(context,250, 280, 250, 400, 280, 300);//設置貝塞爾曲線的控制點座標和控制點座標終點座標 9 CGContextStrokePath(context);
閉合路徑
咱們能夠調用函數CGContextClosePath來閉合曲線。該函數用一條直接來鏈接當前點與起始點,以使路徑閉合。起始與終點重合的直線、弧和曲線並不自動閉合路徑,咱們必須調用CGContextClosePath來閉合路徑。
Quartz的一些函數將路徑的子路徑當作是閉合的。這些函數顯示地添加一條直線來閉合 子路徑,如同調用了CGContextClosePath函數。
在閉合一條子路徑後,若是程序再添加直線、弧或曲線到路徑,Quartz將在閉合的子路徑的起點開始建立一個子路徑。
橢圓
橢圓是一種特殊的圓。橢圓是經過定義兩個焦點,在平面內全部與這兩個焦點的距離之和相等的點所構成的圖形。圖3-9顯示了一些獨立的路徑。每一個路徑都包含一個隨機生成的橢圓;一些進行了填充,另外一邊進行了描邊。
咱們能夠調用CGContextAddEllipseInRect函數來添加一個橢圓到當前路徑。咱們提供一個矩形來定義一個橢圓。Quartz利用一系列的Bezier曲線來模擬橢圓。橢圓的中心就是矩形的中心。若是矩形的寬與高相等,則橢圓變成了圓,且圓的半徑爲矩形寬度的一半。若是矩形的寬與高不相等,則定義了橢圓的長軸與短軸。
添加到路徑中的橢圓開始於一個move-to操做,結束於一個close-subpath操做,全部的移動方向都是順時針。
矩形
咱們能夠調用CGContextAddRect來添加一個矩形到當前路徑中,並提供一個CGRect結構體(包含矩形的原點及大小)做爲參數。
添加到路徑的矩形開始於一個move-to操做,結束於一個close-subpath操做,全部的移動方向都是順時針。
咱們也可能調用CGContextAddRects函數來添加一系列的矩形到當前路徑,並傳遞一個CGRect結構體的數組。圖3-10顯示了一些獨立的路徑。每一個路徑包含一個隨機生成的矩形;一些進行了填充,另外一邊進行了描邊。
4、建立路徑
當咱們須要在一個圖形上下文中構建一個路徑時,咱們須要調用CGContextBeginPath來標記Quartz。而後,咱們調用函數CGContextMovePoint來設置每個圖形或子路徑的起始點。在構建起始點後,咱們能夠添加直線、弧、曲線。記住以下規則:
在繪製路徑後,將清空圖形上下文。咱們也許想保留路徑,特別是在繪製複雜場景時,咱們須要反覆使用。基於此,Quartz提供了兩個數據類型來建立可複用路徑—CGPathRef和CGMutablePathRef。咱們能夠調用函數CGPathCreateMutable來建立可變的CGPath對象,並可向該對象添加直線、弧、曲線和矩形。Quartz提供了一個相似於操做圖形上下文的CGPath的函數集合。這些路徑函數操做CGPath對象,而不是圖形上下文。這些函數包括:
若是想要添加一個路徑到圖形上下文,能夠調用CGContextAddPath。路徑將保留在圖形上下文中,直到Quartz繪製它。咱們能夠調用CGContextAddPath再次添加路徑。
5、繪制路徑
咱們能夠繪製填充或描邊的路徑。描邊(Stroke)是繪製路徑的邊框。填充是繪製路徑包含的區域。Quartz提供了函數來填充或描邊路徑。描邊線的屬性(寬度、顏色等),填充色及Quartz用於計算填充區域的方法都是圖形狀態的一部分。
影響描邊的屬性
咱們可使用表3-1中的屬性來決定如何對路徑進行描邊操做。這邊屬性是圖形上下文的一部分,這意味着咱們設置的值將會影響到後續的描邊操做,直到咱們個性這些值。
linewidth是線的總寬度,單位是用戶空間單元。
linejoin屬性指定如何繪製線段間的聯接點。Quartz支持表3-2中描述的聯接樣式。
Table 3-2 直線聯接樣式
linecap指定如何繪製直線的端點。Quartz支持表3-3所示的線帽類型。默認的是butt cap。
閉合路徑將起始點看做是一個聯接點;起始點一樣也使用選定的直線鏈接方法進行渲染。若是經過添加一條鏈接到起始點的直線來閉合路徑,則路徑的兩個端點都使用選定的線帽類型來繪製。
Linedash pattern(虛線模式)容許咱們沿着描邊繪製虛線。咱們經過在CGContextSetLineDash結構體中指定虛線數組和虛線相位來控制虛線的大小及位置。
CGContextSetLineDash結構以下:
1 void CGContextSetLineDash ( 2 CGContextRef ctx, 3 float phase, 4 const float lengths[], 5 size_t count, 6 );
其中lengths屬性指定了虛線段的長度,該值是在繪製片段與未繪製片段之間交替。phase屬性指定虛線模式的起始點。圖3-11顯示了虛線模式:
路徑描邊的函數
Quartz提供了表3-4中的函數來描邊當前路徑。其中一些是描邊矩形及橢圓的便捷函數。
表3-4 描邊路徑函數
函數CGContextStrokeLineSegments等同於以下代碼
1 CGContextBeginPath(context); for(k = 0; k < count; k += 2) { CGContextMoveToPoint(context,s[k].x, s[k].y); CGContextAddLineToPoint(context,s[k+1].x, s[k+1].y);}CGContextStrokePath(context);
當咱們調用CGContextStrokeLineSegments時,咱們經過點數組來指定線段,並組織成點對的形式。每一對是由線段的起始點與終止點組成。例如,數組的第一個點指定了第一條直線的起始點,第二個點是第一條直線的終點,第三個點是第二條直線的起始點,依此類推。
6、填充路徑
當咱們填充當前路徑時,Quartz將路徑包含的每一個子路徑都看做是閉合的。而後,使用這些閉合路徑並計算填充的像素。 Quartz有兩種方式來計算填充區域。橢圓和矩形這樣的路徑其區域都很明顯。可是若是路徑是由幾個重疊的部分組成或者路徑包含多個子路徑(如圖3-12所示),咱們則有兩種規則來定義填充區域。
默認的規則是非零纏繞數規則(nonzero windingnumber rule)。爲了肯定一個點是否須要繪製,咱們從該點開始繪製一條直線穿過繪圖的邊界。從0開始計數,每次路徑片段從左到右穿過直線是,計數加1;而從右到左穿過直線時,計數減1。若是結果爲0,則不繪製該點,不然繪製。路徑片段繪製的方向會影響到結果。圖3-13顯示了使用非纏繞數規則對內圓和外圓進行填充的結果。當兩個圓繪製方向相同時,兩個圓都被填充。若是方向相反,則內圓不填充。
咱們也可使用偶數-奇數規則。爲了肯定一個點是否被繪製,咱們從該點開始繪製一條直線穿過繪圖的邊界。計算穿過該直線的路徑片段的數目。若是是奇數,則繪製該點,若是是偶數,則不繪製該點。路徑片段繪製的方向不影響結果。如圖3-12所示,不管兩個圓的繪製方向是什麼,填充結果都是同樣的。
Quartz提供了表3-5中的函數來填充當前路徑。其中一些是填充矩形及橢圓的便捷函數。
表3-5 填充路徑的函數
好,說了這麼多下面上代碼
1 - (void)drawLine1 2 { 3 // 1. 獲取一個與視圖相關聯的上下文 4 CGContextRef context = UIGraphicsGetCurrentContext(); 5 6 // 2. 建立一個可變路徑 7 // 2.1 建立路徑 8 CGMutablePathRef path = CGPathCreateMutable(); 9 // 2.2 設置路徑起點 10 CGPathMoveToPoint(path, NULL, 50, 50); 11 // 2.3 增長路徑內容…… 12 CGPathAddLineToPoint(path, NULL, 150, 150); 13 CGPathAddLineToPoint(path, NULL, 50, 150); 14 // CGPathAddLineToPoint(path, NULL, 50, 50); 15 // 閉合路徑,關閉路徑,閉合路徑是收尾相連的 16 CGPathCloseSubpath(path); 17 18 // 3. 將路徑添加到上下文; 19 CGContextAddPath(context, path); 20 21 // 4. 設置上下文狀態 22 // 4.1 設置邊線顏色 23 // 顏色數值 = RGB數值 / 255 24 CGContextSetRGBStrokeColor(context, 255.0 / 255.0, 0.0, 0.0 , 1.0); 25 // 4.2 設置填充顏色 26 CGContextSetRGBFillColor(context, 0.0, 0.0, 128.0 / 255.0, 1.0); 27 // 4.3 設置線寬 28 CGContextSetLineWidth(context, 5); 29 // 4.4 設置線段鏈接樣式 30 CGContextSetLineJoin(context, kCGLineJoinBevel); 31 // 4.5 設置線段首尾樣式 32 CGContextSetLineCap(context, kCGLineCapRound); 33 // 4.6 設置虛線樣式 34 // lengths 設置公有幾條虛線,每條虛線的長度 35 // count 指的是lengths數組的長度 36 CGFloat lengthes[2] = {10.0, 10.0}; 37 CGContextSetLineDash(context, 0, lengthes, 2); 38 // 5. 繪製路徑 39 /** 40 kCGPathStroke 繪製邊線 41 kCGPathFill 填充 42 */ 43 CGContextDrawPath(context, kCGPathFillStroke); 44 // 6. 釋放路徑,不一樣對象對應着不一樣的release方法 45 CGPathRelease(path); 46 }
7、裁剪路徑
當前裁剪區域是從路徑中建立,做爲一個遮罩,從而容許遮住咱們不想繪製的部分。例如,咱們有一個很大的圖片,但只須要顯示其中一小部分,則能夠設置裁減區域來顯示咱們想顯示的部分。
當咱們繪製的時候,Quartz只渲染裁剪區域裏面的東西。裁剪區域內的閉合路徑是可見的;而在區域外的部分是不可見的。
當圖形上下文初始建立時,裁減區域包含上下文全部的可繪製區域(例如,PDF上下文的media box)。咱們能夠經過設置當前路徑來改變裁剪區域,而後使用裁減函數來取代繪製函數。裁剪函數與當前已有的裁剪區域求交集以得到路徑的填充區域。所以,咱們能夠求交取得裁減區域,縮小圖片的可視區域,可是不能擴展裁減區域。
裁減區域是圖形狀態的一部分。爲了恢復先前的裁減區域,咱們能夠在裁減前保存圖形狀態,並在裁減繪製後恢復圖形狀態。
1 Listing 3-1 Setting up a circular clip area 2 CGContextBeginPath(context); 3 CGContextAddArc(context, w/2, h/2, ((w>h) ? h : w)/2, 0, 2*PI, 0); 4 CGContextClosePath(context); 5 CGContextClip(context);
表3-6 裁減圖形上下文的函數