Quartz-2D绘图之路径(Paths)详解
? 在上篇文章中,我們簡單的理解了繪圖上下文,今天我們來認(rèn)識一下Quartz-2D中另一個重要的概念,路徑(Paths)。
一、理解路徑
? 路徑定義了一個或多個形狀,或是子路徑。一個子路徑可由直線,曲線,或者同時由兩者構(gòu)成。它可以是開放的,也可以是閉合的。一個子路徑可以是簡單的形狀,如線、圓、矩形、星形;也可以是復(fù)雜的形狀,如山脈的輪廓或者是涂鴉。圖3-1顯示了一些我們可以創(chuàng)建的路徑。左上角的直線可以是虛線;直線也可以是實(shí)線。上邊中間的路徑是由多條曲線組成的開放路徑。右上角的同心圓填充了顏色,但沒有描邊。左下角的加利福尼亞州是閉合路徑,由許多曲線和直線構(gòu)成,且對路徑進(jìn)行填充和描邊。兩個星形闡明了填充路徑的兩種方式,我們將詳細(xì)描述。
?
二、創(chuàng)建及繪制路徑
路徑創(chuàng)建及路徑繪制是兩個獨(dú)立的工作。首先我們創(chuàng)建路徑。當(dāng)我們需要渲染路徑時,我們需要使用Quartz來繪制它。正如圖3-1中所示,我們可以選擇對路徑進(jìn)行描邊,填充路徑,或同時進(jìn)行這兩種操作。我們同樣可以將其它對象繪制到路徑所表示的范圍內(nèi),即對對象進(jìn)行裁減。
圖3-2繪制了一個路徑,該路徑包含兩個子路徑。左邊的子路徑是一個矩形,右邊的子路徑是由直線和曲線組成的抽象形狀。兩個子路徑都進(jìn)行了填充及描邊。
?
圖3-3顯示了多條獨(dú)立繪制的路徑。每個路徑飲食隨機(jī)生成的曲線,一些進(jìn)行填充,另一些進(jìn)行了描邊。這些路徑都包含在一個圓形裁減區(qū)域內(nèi)。
?
三、構(gòu)建塊(Building Block)
子路徑是由直線、弧和曲線構(gòu)成的。Quartz同樣也提供了簡便的函數(shù)用于添加矩形或橢圓等形狀。點(diǎn)也是路徑最基本的構(gòu)建塊,因?yàn)辄c(diǎn)定義了形狀的起始點(diǎn)與終止點(diǎn)。
點(diǎn)
點(diǎn)由x, y坐標(biāo)值定義,用于在用戶空間指定 一個位置。我們可以調(diào)用函數(shù)CGContextMoveToPoint來為新的子路徑指定起始點(diǎn)。Quartz跟蹤當(dāng)前點(diǎn),用于記錄路徑構(gòu)建過程中最新的位置。例如,如果調(diào)用函數(shù)CGContextMoveToPoint并設(shè)置位置為(10, 10),即將當(dāng)前點(diǎn)移動到位置(10, 10)。如果在水平位置繪制50個單位長度的直線,則直線的終點(diǎn)為(60, 10),該點(diǎn)變成當(dāng)前點(diǎn)。直線、弧和曲線總是從當(dāng)前點(diǎn)開始繪制。
通常我們通過傳遞(x, y)值給Quartz函數(shù)來指定一個點(diǎn)。一些函數(shù)需要我們傳遞一個CGPoint數(shù)據(jù)結(jié)構(gòu),該結(jié)構(gòu)包含兩個浮點(diǎn)值。
直線
直線由兩個端點(diǎn)定義。起始點(diǎn)通常是當(dāng)前點(diǎn),所以創(chuàng)建直線時,我們只需要指定終止點(diǎn)。我們使用函數(shù)CGContextAddLineToPoint來添加一條直線到子路徑中。
我們可以調(diào)用CGContextAddLines函數(shù)添加一系列相關(guān)的直線到子路徑中。我們傳遞一個點(diǎn)數(shù)組給這個函數(shù)。第一個點(diǎn)必須是第一條直線的起始點(diǎn);剩下的點(diǎn)是端點(diǎn)。Quartz從第一個點(diǎn)開始繪制一個新子路徑,然后每兩個相鄰點(diǎn)連接成一條線段。
弧
弧是圓弧段。Quartz提供了兩個函數(shù)來創(chuàng)建弧。函數(shù)CGContextAddArc從圓中來創(chuàng)建一個曲線段。我們指定一個圓心,半徑和放射角(以弧度為單位)。放射角為2 PI時,創(chuàng)建的是一個圓。圖3-4顯示了多個獨(dú)立的路徑。每個路徑飲食一個自動生成的圓;一些是填充的,另一些是描邊的。
函數(shù)CGContextAddArcToPoint用于為矩形創(chuàng)建內(nèi)切弧的場景。Quartz使用我們提供的端點(diǎn)創(chuàng)建兩條正切線。同樣我們需要提供圓的半徑?;⌒氖莾蓷l半徑的交叉點(diǎn),每條半徑都與相應(yīng)的正切線垂直?;〉膬蓚€端點(diǎn)是正切線的正切點(diǎn),如圖3-5所示。紅色的部分是實(shí)際繪制的部分。
下面分別是畫直線和圓弧的代碼
直線:
?
1 // 1. 獲取一個與視圖相關(guān)聯(lián)的上下文 2 CGContextRef context = UIGraphicsGetCurrentContext(); 3 4 // 2. 構(gòu)建路徑 5 // 2.1 設(shè)置上下文路徑起點(diǎn) 6 CGContextMoveToPoint(context, 85, 85); 7 // 2.2 增加路徑內(nèi)容…… 8 CGContextAddLineToPoint(context, 150, 150); 9 CGContextAddLineToPoint(context, 250, 50); 10 // 3. 保存上下文狀態(tài) 11 // 4. 設(shè)置上下文狀態(tài) 12 // 4.1 設(shè)置邊線顏色 13 CGContextSetRGBStrokeColor(context, 1, 0, 0, 1); 14 // 4.2 設(shè)置線寬 15 CGContextSetLineWidth(context, 10); 16 // 4.3 設(shè)置線段連接樣式 17 CGContextSetLineJoin(context, kCGLineJoinRound); 18 // 4.4 設(shè)置線段收尾樣式 19 CGContextSetLineCap(context, kCGLineCapRound); 20 // 4.5 設(shè)置虛線樣式 21 // 4. 繪制路徑 22 CGContextDrawPath(context, kCGPathStroke);?
圓弧:
// 1. 獲取一個與視圖相關(guān)聯(lián)的上下文CGContextRef context = UIGraphicsGetCurrentContext();[[UIColor redColor]set];// 2. 添加弧線// 2.1 上下文// 2.2 中心點(diǎn)坐標(biāo)// 2.3 半徑// 2.4 開始角度,結(jié)束角度,角度的單位是“弧度”CGContextAddArc(context, 160, 230, 100, M_PI, -M_PI_2, 0);// 繪制路徑CGContextStrokePath(context);曲線
二次與三次Bezier曲線是代數(shù)曲線,可以指定任意的曲線形狀。曲線上的點(diǎn)通過一個應(yīng)用于起始、終點(diǎn)及一個或多個控制點(diǎn)的多項(xiàng)式計(jì)算得出。這種方式定義的形狀是向量圖的基礎(chǔ)。這個公式比將位數(shù)組更容易存儲,并且曲線可以在任何分辨下重新創(chuàng)建。
圖3-6顯示了一些路徑的曲線。每條路徑包含一條隨機(jī)生成的曲線;一些是填充的,另一些是描邊的。
?
我們使用函數(shù)CGContextAddCurveToPoint將Bezier曲線連接到當(dāng)前點(diǎn),并傳遞控制點(diǎn)和端點(diǎn)作為參數(shù),如圖3-7所示。兩個控制點(diǎn)的位置決定了曲線的形狀。如果兩個控制點(diǎn)都在兩個端點(diǎn)上面,則曲線向上凸起。如果兩個控制點(diǎn)都在兩個端點(diǎn)下面,則曲線向下凹。如果第二個控制點(diǎn)比第一個控制點(diǎn)離得當(dāng)前點(diǎn)近,則曲線自交叉,創(chuàng)建了一個回路。
我們也可以調(diào)用函數(shù)CGContextAddQuadCurveToPoint來創(chuàng)建Bezier,并傳遞端點(diǎn)及一個控制點(diǎn),如圖3-8所示??刂泣c(diǎn)決定了曲線彎曲的方向。由于只使用一個控制點(diǎn),所以無法創(chuàng)建出如三次Bezier曲線一樣多的曲線。例如我們無法創(chuàng)建出交叉的曲線
1 /*畫貝塞爾曲線*/ 2 //二次曲線 3 CGContextMoveToPoint(context, 120, 300);//設(shè)置Path的起點(diǎn) 4 CGContextAddQuadCurveToPoint(context,190, 310, 120, 390);//設(shè)置貝塞爾曲線的控制點(diǎn)坐標(biāo)和終點(diǎn)坐標(biāo) 5 CGContextStrokePath(context); 6 //三次曲線函數(shù) 7 CGContextMoveToPoint(context, 200, 300);//設(shè)置Path的起點(diǎn) 8 CGContextAddCurveToPoint(context,250, 280, 250, 400, 280, 300);//設(shè)置貝塞爾曲線的控制點(diǎn)坐標(biāo)和控制點(diǎn)坐標(biāo)終點(diǎn)坐標(biāo) 9 CGContextStrokePath(context);?
閉合路徑
我們可以調(diào)用函數(shù)CGContextClosePath來閉合曲線。該函數(shù)用一條直接來連接當(dāng)前點(diǎn)與起始點(diǎn),以使路徑閉合。起始與終點(diǎn)重合的直線、弧和曲線并不自動閉合路徑,我們必須調(diào)用CGContextClosePath來閉合路徑。
Quartz的一些函數(shù)將路徑的子路徑看成是閉合的。這些函數(shù)顯示地添加一條直線來閉合 子路徑,如同調(diào)用了CGContextClosePath函數(shù)。
在閉合一條子路徑后,如果程序再添加直線、弧或曲線到路徑,Quartz將在閉合的子路徑的起點(diǎn)開始創(chuàng)建一個子路徑。
橢圓
橢圓是一種特殊的圓。橢圓是通過定義兩個焦點(diǎn),在平面內(nèi)所有與這兩個焦點(diǎn)的距離之和相等的點(diǎn)所構(gòu)成的圖形。圖3-9顯示了一些獨(dú)立的路徑。每個路徑都包含一個隨機(jī)生成的橢圓;一些進(jìn)行了填充,另一邊進(jìn)行了描邊。
我們可以調(diào)用CGContextAddEllipseInRect函數(shù)來添加一個橢圓到當(dāng)前路徑。我們提供一個矩形來定義一個橢圓。Quartz利用一系列的Bezier曲線來模擬橢圓。橢圓的中心就是矩形的中心。如果矩形的寬與高相等,則橢圓變成了圓,且圓的半徑為矩形寬度的一半。如果矩形的寬與高不相等,則定義了橢圓的長軸與短軸。
添加到路徑中的橢圓開始于一個move-to操作,結(jié)束于一個close-subpath操作,所有的移動方向都是順時針。
矩形
我們可以調(diào)用CGContextAddRect來添加一個矩形到當(dāng)前路徑中,并提供一個CGRect結(jié)構(gòu)體(包含矩形的原點(diǎn)及大小)作為參數(shù)。
添加到路徑的矩形開始于一個move-to操作,結(jié)束于一個close-subpath操作,所有的移動方向都是順時針。
我們也可能調(diào)用CGContextAddRects函數(shù)來添加一系列的矩形到當(dāng)前路徑,并傳遞一個CGRect結(jié)構(gòu)體的數(shù)組。圖3-10顯示了一些獨(dú)立的路徑。每個路徑包含一個隨機(jī)生成的矩形;一些進(jìn)行了填充,另一邊進(jìn)行了描邊。
?四、創(chuàng)建路徑
當(dāng)我們需要在一個圖形上下文中構(gòu)建一個路徑時,我們需要調(diào)用CGContextBeginPath來標(biāo)記Quartz。然后,我們調(diào)用函數(shù)CGContextMovePoint來設(shè)置每一個圖形或子路徑的起始點(diǎn)。在構(gòu)建起始點(diǎn)后,我們可以添加直線、弧、曲線。記住如下規(guī)則:
- 在開始繪制路徑前,調(diào)用函數(shù)CGContextBeginPath;
- 直線、弧、曲線開始于當(dāng)前點(diǎn)。空路徑?jīng)]有當(dāng)前點(diǎn);我們必須調(diào)用CGContextMoveToPoint來設(shè)置第一個子路徑的起始點(diǎn),或者調(diào)用一個便利函數(shù)來隱式地完成該任務(wù)。
- 如果要閉合當(dāng)前子路徑,調(diào)用函數(shù)CGContextClosePath。隨后路徑將開始一個新的子路徑,即使我們不顯示設(shè)置一個新的起始點(diǎn)。
- 當(dāng)繪制弧時,Quartz將在當(dāng)前點(diǎn)與弧的起始點(diǎn)間繪制一條直線。
- 添加橢圓和矩形的Quartz程序?qū)⒃诼窂街刑砑有碌拈]合子路徑。
- 我們必須調(diào)用繪制函數(shù)來填充或者描邊一條路徑,因?yàn)閯?chuàng)建路徑時并不會繪制路徑。
在繪制路徑后,將清空圖形上下文。我們也許想保留路徑,特別是在繪制復(fù)雜場景時,我們需要反復(fù)使用?;诖?#xff0c;Quartz提供了兩個數(shù)據(jù)類型來創(chuàng)建可復(fù)用路徑—CGPathRef和CGMutablePathRef。我們可以調(diào)用函數(shù)CGPathCreateMutable來創(chuàng)建可變的CGPath對象,并可向該對象添加直線、弧、曲線和矩形。Quartz提供了一個類似于操作圖形上下文的CGPath的函數(shù)集合。這些路徑函數(shù)操作CGPath對象,而不是圖形上下文。這些函數(shù)包括:
- CGPathCreateMutable,取代CGContextBeginPath
- CGPathMoveToPoint,取代CGContextMoveToPoint
- CGPathAddLineToPoint,取代CGContexAddLineToPoint
- CGPathAddCurveToPoint,取代CGContexAddCurveToPoint
- CGPathAddEllipseInRect,取代CGContexAddEllipseInRect
- CGPathAddArc,取代CGContexAddArc
- CGPathAddRect,取代CGContexAddRect
- CGPathCloseSubpath,取代CGContexClosePath
如果想要添加一個路徑到圖形上下文,可以調(diào)用CGContextAddPath。路徑將保留在圖形上下文中,直到Quartz繪制它。我們可以調(diào)用CGContextAddPath再次添加路徑。
?五、繪制路徑
我們可以繪制填充或描邊的路徑。描邊(Stroke)是繪制路徑的邊框。填充是繪制路徑包含的區(qū)域。Quartz提供了函數(shù)來填充或描邊路徑。描邊線的屬性(寬度、顏色等),填充色及Quartz用于計(jì)算填充區(qū)域的方法都是圖形狀態(tài)的一部分。
影響描邊的屬性
我們可以使用表3-1中的屬性來決定如何對路徑進(jìn)行描邊操作。這邊屬性是圖形上下文的一部分,這意味著我們設(shè)置的值將會影響到后續(xù)的描邊操作,直到我們個性這些值。
linewidth是線的總寬度,單位是用戶空間單元。
linejoin屬性指定如何繪制線段間的聯(lián)接點(diǎn)。Quartz支持表3-2中描述的聯(lián)接樣式。
Table 3-2?直線聯(lián)接樣式
linecap指定如何繪制直線的端點(diǎn)。Quartz支持表3-3所示的線帽類型。默認(rèn)的是butt cap。
?
閉合路徑將起始點(diǎn)看作是一個聯(lián)接點(diǎn);起始點(diǎn)同樣也使用選定的直線連接方法進(jìn)行渲染。如果通過添加一條連接到起始點(diǎn)的直線來閉合路徑,則路徑的兩個端點(diǎn)都使用選定的線帽類型來繪制。
Linedash pattern(虛線模式)允許我們沿著描邊繪制虛線。我們通過在CGContextSetLineDash結(jié)構(gòu)體中指定虛線數(shù)組和虛線相位來控制虛線的大小及位置。
CGContextSetLineDash結(jié)構(gòu)如下:
?
1 void CGContextSetLineDash ( 2 CGContextRef ctx, 3 float phase, 4 const float lengths[], 5 size_t count, 6 );?其中l(wèi)engths屬性指定了虛線段的長度,該值是在繪制片斷與未繪制片斷之間交替。phase屬性指定虛線模式的起始點(diǎn)。圖3-11顯示了虛線模式:
路徑描邊的函數(shù)
Quartz提供了表3-4中的函數(shù)來描邊當(dāng)前路徑。其中一些是描邊矩形及橢圓的便捷函數(shù)。
表3-4 描邊路徑函數(shù)
?
函數(shù)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);當(dāng)我們調(diào)用CGContextStrokeLineSegments時,我們通過點(diǎn)數(shù)組來指定線段,并組織成點(diǎn)對的形式。每一對是由線段的起始點(diǎn)與終止點(diǎn)組成。例如,數(shù)組的第一個點(diǎn)指定了第一條直線的起始點(diǎn),第二個點(diǎn)是第一條直線的終點(diǎn),第三個點(diǎn)是第二條直線的起始點(diǎn),依此類推。
六、填充路徑
當(dāng)我們填充當(dāng)前路徑時,Quartz將路徑包含的每個子路徑都看作是閉合的。然后,使用這些閉合路徑并計(jì)算填充的像素。?Quartz有兩種方式來計(jì)算填充區(qū)域。橢圓和矩形這樣的路徑其區(qū)域都很明顯。但是如果路徑是由幾個重疊的部分組成或者路徑包含多個子路徑(如圖3-12所示),我們則有兩種規(guī)則來定義填充區(qū)域。
默認(rèn)的規(guī)則是非零纏繞數(shù)規(guī)則(nonzero windingnumber rule)。為了確定一個點(diǎn)是否需要繪制,我們從該點(diǎn)開始繪制一條直線穿過繪圖的邊界。從0開始計(jì)數(shù),每次路徑片斷從左到右穿過直線是,計(jì)數(shù)加1;而從右到左穿過直線時,計(jì)數(shù)減1。如果結(jié)果為0,則不繪制該點(diǎn),否則繪制。路徑片斷繪制的方向會影響到結(jié)果。圖3-13顯示了使用非纏繞數(shù)規(guī)則對內(nèi)圓和外圓進(jìn)行填充的結(jié)果。當(dāng)兩個圓繪制方向相同時,兩個圓都被填充。如果方向相反,則內(nèi)圓不填充。
我們也可以使用偶數(shù)-奇數(shù)規(guī)則。為了確定一個點(diǎn)是否被繪制,我們從該點(diǎn)開始繪制一條直線穿過繪圖的邊界。計(jì)算穿過該直線的路徑片斷的數(shù)目。如果是奇數(shù),則繪制該點(diǎn),如果是偶數(shù),則不繪制該點(diǎn)。路徑片斷繪制的方向不影響結(jié)果。如圖3-12所示,無論兩個圓的繪制方向是什么,填充結(jié)果都是一樣的。
Quartz提供了表3-5中的函數(shù)來填充當(dāng)前路徑。其中一些是填充矩形及橢圓的便捷函數(shù)。
表3-5 填充路徑的函數(shù)
?好,說了這么多下面上代碼
1 - (void)drawLine1 2 { 3 // 1. 獲取一個與視圖相關(guān)聯(lián)的上下文 4 CGContextRef context = UIGraphicsGetCurrentContext(); 5 6 // 2. 創(chuàng)建一個可變路徑 7 // 2.1 創(chuàng)建路徑 8 CGMutablePathRef path = CGPathCreateMutable(); 9 // 2.2 設(shè)置路徑起點(diǎn) 10 CGPathMoveToPoint(path, NULL, 50, 50); 11 // 2.3 增加路徑內(nèi)容…… 12 CGPathAddLineToPoint(path, NULL, 150, 150); 13 CGPathAddLineToPoint(path, NULL, 50, 150); 14 // CGPathAddLineToPoint(path, NULL, 50, 50); 15 // 閉合路徑,關(guān)閉路徑,閉合路徑是收尾相連的 16 CGPathCloseSubpath(path); 17 18 // 3. 將路徑添加到上下文; 19 CGContextAddPath(context, path); 20 21 // 4. 設(shè)置上下文狀態(tài) 22 // 4.1 設(shè)置邊線顏色 23 // 顏色數(shù)值 = RGB數(shù)值 / 255 24 CGContextSetRGBStrokeColor(context, 255.0 / 255.0, 0.0, 0.0 , 1.0); 25 // 4.2 設(shè)置填充顏色 26 CGContextSetRGBFillColor(context, 0.0, 0.0, 128.0 / 255.0, 1.0); 27 // 4.3 設(shè)置線寬 28 CGContextSetLineWidth(context, 5); 29 // 4.4 設(shè)置線段連接樣式 30 CGContextSetLineJoin(context, kCGLineJoinBevel); 31 // 4.5 設(shè)置線段首尾樣式 32 CGContextSetLineCap(context, kCGLineCapRound); 33 // 4.6 設(shè)置虛線樣式 34 // lengths 設(shè)置公有幾條虛線,每條虛線的長度 35 // count 指的是lengths數(shù)組的長度 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. 釋放路徑,不同對象對應(yīng)著不同的release方法 45 CGPathRelease(path); 46 }?七、裁剪路徑
當(dāng)前裁剪區(qū)域是從路徑中創(chuàng)建,作為一個遮罩,從而允許遮住我們不想繪制的部分。例如,我們有一個很大的圖片,但只需要顯示其中一小部分,則可以設(shè)置裁減區(qū)域來顯示我們想顯示的部分。
當(dāng)我們繪制的時候,Quartz只渲染裁剪區(qū)域里面的東西。裁剪區(qū)域內(nèi)的閉合路徑是可見的;而在區(qū)域外的部分是不可見的。
當(dāng)圖形上下文初始創(chuàng)建時,裁減區(qū)域包含上下文所有的可繪制區(qū)域(例如,PDF上下文的media box)。我們可以通過設(shè)置當(dāng)前路徑來改變裁剪區(qū)域,然后使用裁減函數(shù)來取代繪制函數(shù)。裁剪函數(shù)與當(dāng)前已有的裁剪區(qū)域求交集以獲得路徑的填充區(qū)域。因此,我們可以求交取得裁減區(qū)域,縮小圖片的可視區(qū)域,但是不能擴(kuò)展裁減區(qū)域。
裁減區(qū)域是圖形狀態(tài)的一部分。為了恢復(fù)先前的裁減區(qū)域,我們可以在裁減前保存圖形狀態(tài),并在裁減繪制后恢復(fù)圖形狀態(tài)。
?
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 裁減圖形上下文的函數(shù)
?
轉(zhuǎn)載于:https://www.cnblogs.com/tmacforever/p/4159163.html
總結(jié)
以上是生活随笔為你收集整理的Quartz-2D绘图之路径(Paths)详解的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 毕业生必须知道:干部身份、三方协议、派遣
- 下一篇: javascript优化--13模式1(