在上篇文章中,我們簡單的理解了繪圖上下文,今天我們來認識一下Quartz-2D中另一個重要的概念,路徑(Paths)。
一、理解路徑
路徑定義了一個或多個形狀,或是子路徑。一個子路徑可由直線,曲線,或者同時由兩者構成。它可以是開放的,也可以是閉合的。一個子路徑可以是簡單的形狀,如線、圓、矩形、星形;也可以是復雜的形狀,如山脈的輪廓或者是涂鴉。圖3-1顯示了一些我們可以創建的路徑。左上角的直線可以是虛線;直線也可以是實線。上邊中間的路徑是由多條曲線組成的開放路徑。右上角的同心圓填充了顏色,但沒有描邊。左下角的加利福尼亞州是閉合路徑,由許多曲線和直線構成,且對路徑進行填充和描邊。兩個星形闡明了填充路徑的兩種方式,我們將詳細描述。

二、創建及繪制路徑
路徑創建及路徑繪制是兩個獨立的工作。首先我們創建路徑。當我們需要渲染路徑時,我們需要使用Quartz來繪制它。正如圖3-1中所示,我們可以選擇對路徑進行描邊,填充路徑,或同時進行這兩種操作。我們同樣可以將其它對象繪制到路徑所表示的范圍內,即對對象進行裁減。
圖3-2繪制了一個路徑,該路徑包含兩個子路徑。左邊的子路徑是一個矩形,右邊的子路徑是由直線和曲線組成的抽象形狀。兩個子路徑都進行了填充及描邊。

圖3-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所示。紅色的部分是實際繪制的部分。

下面分別是畫直線和圓弧的代碼
直線:
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顯示了一些獨立的路徑。每個路徑包含一個隨機生成的矩形;一些進行了填充,另一邊進行了描邊。
四、創建路徑
當我們需要在一個圖形上下文中構建一個路徑時,我們需要調用CGContextBeginPath來標記Quartz。然后,我們調用函數CGContextMovePoint來設置每一個圖形或子路徑的起始點。在構建起始點后,我們可以添加直線、弧、曲線。記住如下規則:
在繪制路徑后,將清空圖形上下文。我們也許想保留路徑,特別是在繪制復雜場景時,我們需要反復使用。基于此,Quartz提供了兩個數據類型來創建可復用路徑—CGPathRef和CGMutablePathRef。我們可以調用函數CGPathCreateMutable來創建可變的CGPath對象,并可向該對象添加直線、弧、曲線和矩形。Quartz提供了一個類似于操作圖形上下文的CGPath的函數集合。這些路徑函數操作CGPath對象,而不是圖形上下文。這些函數包括:
如果想要添加一個路徑到圖形上下文,可以調用CGContextAddPath。路徑將保留在圖形上下文中,直到Quartz繪制它。我們可以調用CGContextAddPath再次添加路徑。
五、繪制路徑
我們可以繪制填充或描邊的路徑。描邊(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時,我們通過點數組來指定線段,并組織成點對的形式。每一對是由線段的起始點與終止點組成。例如,數組的第一個點指定了第一條直線的起始點,第二個點是第一條直線的終點,第三個點是第二條直線的起始點,依此類推。
六、填充路徑
當我們填充當前路徑時,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數值 / 25524 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 }
七、裁剪路徑
當前裁剪區域是從路徑中創建,作為一個遮罩,從而允許遮住我們不想繪制的部分。例如,我們有一個很大的圖片,但只需要顯示其中一小部分,則可以設置裁減區域來顯示我們想顯示的部分。
當我們繪制的時候,Quartz只渲染裁剪區域里面的東西。裁剪區域內的閉合路徑是可見的;而在區域外的部分是不可見的。
當圖形上下文初始創建時,裁減區域包含上下文所有的可繪制區域(例如,PDF上下文的media box)。我們可以通過設置當前路徑來改變裁剪區域,然后使用裁減函數來取代繪制函數。裁剪函數與當前已有的裁剪區域求交集以獲得路徑的填充區域。因此,我們可以求交取得裁減區域,縮小圖片的可視區域,但是不能擴展裁減區域。
裁減區域是圖形狀態的一部分。為了恢復先前的裁減區域,我們可以在裁減前保存圖形狀態,并在裁減繪制后恢復圖形狀態。
1 Listing 3-1 Setting up a circular clip area2 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 裁減圖形上下文的函數

新聞熱點
疑難解答