路徑定義一個或多個形狀或子路徑。 子路徑可以由直線,曲線或兩者組成。 它可以打開或關閉。 子路徑可以是簡單的形狀,例如線,圓,矩形或星形,或更復雜的形狀,例如山脈的輪廓或抽象涂鴉。 圖3-1顯示了您可以創建的一些路徑。 直線(在圖的左上方)是虛線; 線條也可以是實心的。 波形路徑(在中間頂部)由幾條曲線組成并且是開放路徑。 同心圓被填充,但不被描邊。 加利福尼亞州是一條封閉的路徑,由許多曲線和線組成,路徑既描邊(stroked)又填充。 星星說明了兩種填充路徑的選項,您將在本章后面閱讀。
在本章中,您將了解構成路徑的構建塊,如何描繪和繪制路徑以及影響路徑外觀的參數。
路徑創建和路徑繪畫
路徑創建和路徑繪制是單獨的任務。 首先創建一個路徑。 當你想渲染一個路徑時,你要求Quartz繪制它。 如圖3-1所示,您可以選擇劃出路徑,填充路徑,或者同時劃出和填充路徑。 您還可以使用路徑來限制路徑邊界內的其他對象的繪制,實際上是剪裁區域。
圖3-2顯示了已繪制并包含兩個子路徑的路徑。 左邊的子路徑是矩形,右邊的子路徑是由直線和曲線組成的抽象形狀。 每個子路徑都已填充,其輪廓已劃線。
圖3-3顯示了獨立繪制的多條路徑。 每個路徑包含隨機生成的曲線,其中一些填充和其他描邊。 繪圖通過剪裁區域限制為圓形區域。
構件塊
子路徑由線,弧和曲線構成。 Quartz還提供了方便的功能,通過單個函數調用來添加矩形和橢圓。 點也是路徑的基本構建塊,因為點定義了形狀的開始和結束位置。
Points
點是指定用戶空間中的位置的x和y坐標。 您可以調用函數CGContextMoveToPoint以指定新子路徑的起始位置。 Quartz跟蹤當前點,這是用于路徑構造的最后一個位置。 例如,如果調用函數CGContextMoveToPoint在(10,10)設置位置,則將當前點移動到(10,10)。 如果然后繪制水平線50個單位長,線上的最后一個點,即(60,10),變為當前點。 線,弧和曲線始終從當前點開始繪制。
大多數時候你通過傳遞給Quartz函數指定一個點,兩個浮點值來指定x和y坐標。 一些函數要求你傳遞一個CGPoint數據結構,它保存兩個浮點值。
Lines
線由其端點定義。 它的始點始終假定為當前點,因此在創建線時,只指定其端點。 您可以使用函數CGContextAddLineToPoint將單個行追加到子路徑。
您可以通過調用函數CGContextAddLines將一系列連接的行添加到路徑。 你傳遞這個函數的點數組。 第一點必須是第一行的起點; 其余點是端點。 Quartz在第一個點開始新的子路徑,并將直線段連接到每個端點。
Arcs
圓弧是圓弧段。 Quartz提供了兩個創建弧線的函數。 函數CGContextAddArc從圓圈創建一個曲線段。 指定圓的中心,半徑和徑向角(以弧度表示)。 您可以通過指定2 pi的徑向角度創建一個完整的圓。 圖3-4顯示了獨立繪制的多個路徑。 每個路徑包含隨機生成的圓; 一些填充和其他描邊。
函數CGContextAddArcToPoint是理想的,當你想要圓角的矩形。 Quartz使用您提供的端點創建兩條切線。 您還提供了Quartz切割圓弧的圓的半徑。 弧的中心點是兩個半徑的交點,每個半徑垂直于兩條切線中的一條。 圓弧的每個端點是切線之一上的切點,如圖3-5所示。 圓的紅色部分是實際繪制的。
如果當前路徑已經包含子路徑,Quartz將從當前點到弧的起點添加一條直線段。 如果當前路徑為空,Quartz將在弧的起點處創建一個新的子路徑,而不添加初始直線段。
Curves
二次和三次貝塞爾曲線是可以指定任何數量的有趣曲線形狀的代數曲線。 通過對起點和終點以及一個或多個控制點應用多項式公式來計算這些曲線上的點。 以這種方式定義的形狀是矢量圖形的基礎。 公式比位數組更緊湊地存儲,并且具有可以以任何分辨率重新創建曲線的優點。
圖3-6顯示了通過獨立繪制多個路徑創建的各種曲線。 每個路徑包含隨機生成的曲線; 一些填充和其他描邊。
提供二次和三次貝塞爾曲線的多項式公式以及關于如何從公式生成曲線的細節,在描述計算機圖形的許多數學文本和在線資源中討論。 這里不討論這些細節。
您使用函數CGContextAddCurveToPoint從當前點附加一個三次貝塞爾曲線,使用控制點和您指定的端點。 圖3-7顯示了從圖中所示的當前點,控制點和終點產生的三次貝塞爾曲線。 兩個控制點的位置確定曲線的幾何形狀。 如果控制點都在起點和終點之上,則曲線向上拱起。 如果控制點都在起點和終點以下,則曲線向下彎曲。
您可以通過調用函數CGContextAddQuadCurveToPoint,并指定控制點和端點,從當前點追加二次Bézier曲線。 圖3-8顯示了使用相同的端點但是使用不同的控制點產生的兩條曲線。 控制點確定曲線彎曲的方向。 使用二次Bézier曲線創建盡可能多的有趣形狀是不可能的,因為二次曲線只使用一個控制點。 例如,不能使用單個控制點創建交叉。
Closing a Subpath
要關閉當前子路徑,應用程序應調用CGContextClosePath。 此函數將從當前點到子路徑的起始點添加一個線段,并關閉子路徑。 在子路徑起點處結束的線,弧和曲線實際上不關閉子路徑。 您必須顯式調用CGContextClosePath以關閉子路徑。
一些Quartz函數將路徑的子路徑視為由應用程序關閉。 這些命令處理每個子路徑,就像您的應用程序調用CGContextClosePath關閉它,隱式地添加一個線段到子路徑的起始點。
關閉子路徑后,如果您的應用程序進行額外的調用以向路徑添加線,弧或曲線,Quartz將從您剛關閉的子路徑的起點開始新的子路徑。
Ellipses
橢圓本質上是一個被擠壓的圓。 通過定義兩個聚焦點,然后繪制所有位于一定距離處的點,從而將從橢圓上的任何點到一個焦點的距離與從該相同點到另一個焦點的距離相加總是相同的值來創建一個 。 圖3-9顯示了獨立繪制的多條路徑。 每個路徑包含隨機生成的橢圓; 一些填充和其他描邊。
您可以通過調用函數CGContextAddEllipseInRect將橢圓添加到當前路徑。 您提供了一個定義橢圓邊界的矩形。 Quartz使用一系列Bézier曲線近似橢圓。 橢圓的中心是矩形的中心。 如果矩形的寬度和高度相等(即,正方形),則橢圓是圓形的,半徑等于矩形的寬度(或高度)的一半。 如果矩形的寬度和高度不相等,則它們定義橢圓的長軸和短軸。
添加到路徑的橢圓以移動到操作開始,并以閉合子路徑操作結束,所有移動以順時針方向定向。
Rectangles
您可以通過調用函數CGContextAddRect將矩形添加到當前路徑。 您提供了一個包含矩形的原點及其寬度和高度的CGRect結構。
添加到路徑的矩形以移動到操作開始,并以閉合子路徑操作結束,所有移動以逆時針方向定向。
您可以通過調用函數CGContextAddRects并提供一個CGRect結構數組來向當前路徑添加許多矩形。 圖3-10顯示了獨立繪制的多條路徑。 每個路徑包含一個隨機生成的矩形; 一些填充和其他描邊。
創建路徑
當你想在圖形上下文中構造一個路徑時,你通過調用函數CGContextBeginPath來發送Quartz信號。 接下來,通過調用函數CGContextMoveToPoint,為路徑中的第一個形狀或子路徑設置起點。 建立第一個點后,您可以向路徑中添加線,弧和曲線,并記住以下幾點:
- 在開始新路徑之前,請調用函數CGContextBeginPath。
- 從當前點開始繪制線,弧和曲線。 空路徑沒有當前點; 您必須調用CGContextMoveToPoint以設置第一個子路徑的起點或調用一個為您隱式執行此操作的便利函數。
- 當要關閉某個路徑中的當前子路徑時,請調用函數CGContextClosePath將段連接到子路徑的起始點。 后續路徑調用開始一個新的子路徑,即使您沒有顯式設置新的起點。
- 當繪制弧時,Quartz在當前點和弧的起點之間繪制一條線。
- 添加橢圓和矩形的Quartz例程向路徑中添加一個新的封閉子路徑。
- 您必須調用繪制函數來填充或描邊路徑,因為創建路徑不會繪制路徑。 有關詳細信息,請參閱繪制路徑。
繪制路徑后,將從圖形上下文中刷新。 你可能不想失去你的路徑這么容易,特別是如果它描繪了一個復雜的場景,你想一次又一次使用。 因此,Quartz為創建可重用路徑提供了兩種數據類型:CGPathRef和CGMutablePathRef。 您可以調用函數CGPathCreateMutable創建一個可變的CGPath對象,您可以添加線,弧,曲線和矩形。 Quartz提供了一組與“構件塊”中討論的功能并行的CGPath函數。 路徑函數對CGPath對象而不是圖形上下文進行操作。 這些功能是:
- CGPathCreateMutable,它替換了CGContextBeginPath
- CGPathMoveToPoint,它替換CGContextMoveToPoint
- CGPathAddLineToPoint,它替換CGContextAddLineToPoint
- CGPathAddCurveToPoint,它替換CGContextAddCurveToPoint
- CGPathAddEllipseInRect,它替換CGContextAddEllipseInRect
- CGPathAddArc,它替換CGContextAddArc
- CGPathAddRect,它將替換CGContextAddRect
- CGPathCloseSubpath,它替換CGContextClosePath
有關路徑函數的完整列表,請參閱Quartz 2D Reference Collection。
當您想將路徑附加到圖形上下文時,可以調用函數CGContextAddPath。 路徑停留在圖形上下文中,直到Quartz繪制它。 您可以通過調用CGContextAddPath再次添加路徑。
注:您可以通過調用函數CGContextReplacePathWithStrokedPath,將圖形上下文中的路徑替換為路徑的描邊版本。
繪畫路徑
您可以通過描邊或填充或兩者來繪制當前路徑。描邊畫一條跨越路徑的線。 填充繪制路徑中包含的區域。 Quartz有一些函數可以讓你描繪一個路徑,填充一個路徑,或者描邊和填充一個路徑。 描邊線(寬度,顏色等),填充顏色和Quartz用于計算填充區域的方法的特性都是圖形狀態的一部分(請參見圖形狀態)。
Parameters That Affect Stroking
您可以通過修改表3-1中列出的參數來影響路徑的描邊。 這些參數是圖形狀態的一部分,這意味著為參數設置的值將影響所有后續的描邊,直到將參數設置為另一個值。
線寬是線的總寬度,以用戶空間的單位表示。 線跨過路徑,在任一側具有一半的總寬度。
線連接指定Quartz如何繪制連接的線段之間的連接。 Quartz支持表3-2中描述的線連接樣式。 默認樣式為miter join。
線條帽指定CGContextStrokePath用于繪制線的端點的方法。 Quartz支持表3-3中描述的線帽樣式。 默認樣式為對接帽。
閉合子路徑將開始點視為連接的線段之間的連接點; 使用所選的線連接方法渲染起點。 相反,如果通過添加連接到起點的線段來關閉路徑,則使用所選的line-cap方法繪制路徑的兩端。
線圖案允許您沿著描邊路徑繪制分段線。 通過將虛線數組和虛線階段指定為CGContextSetLineDash的參數,可以控制沿線的虛線段的大小和位置:
void CGContextSetLineDash (
CGContextRef ctx,
CGFloat phase,
const CGFloat lengths[],
size_t count
);
length參數的元素指定了破折號的寬度,在線的著色和未涂漆段之間交替。 phase參數指定破折號模式的起點。 圖3-11顯示了一些行破折號模式。
筆畫顏色空間確定了Quartz對筆觸顏色值的解釋方式。 您還可以指定封裝顏色和顏色空間的Quartz顏色(CGColorRef數據類型)。 有關設置顏色空間和顏色的詳細信息,請參閱顏色和顏色空間。
Functions for Stroking a Path
Quartz提供了表3-4中顯示的用于描畫當前路徑的函數。 一些是用于描邊矩形或橢圓的便利函數。
函數CGContextStrokeLineSegments等價于以下代碼:
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時,您將線段指定為一個點數組,組織為對。 每對包括線段的起點,后面是線段的終點。 例如,陣列中的第一點指定第一行的開始位置,第二點指定第一行的結束位置,第三點指定第二行的開始位置,等等。
Filling a Path
當你填充當前路徑時,Quartz的行為就像路徑中包含的每個子路徑都被關閉一樣。然后使用這些閉合的子路徑并計算要填充的像素。有兩種方法可以計算填充面積。簡單路徑(如橢圓形和矩形)具有明確定義的區域。但是,如果路徑由重疊段組成,或者路徑包含多個子路徑,例如圖3-12中所示的同心圓,則有兩個規則可用于確定填充區域。
默認填充規則稱為非零匝數規則。要確定是否應該繪制特定點,請從該點開始,繪制超出圖形邊界的線。從計數0開始,每當路徑段從左到右穿過該線時將計數加1,并且每當路徑段從右到左穿過該線時減1。如果結果為0,則該點不被繪制。否則,點被繪。繪制路徑段的方向會影響結果。圖3-12顯示了使用非零匝數規則填充的兩組內圓和外圓。當在相同方向繪制每個圓時,填充兩個圓。當在相反方向繪制圓時,內圓不被填充。
您可以選擇使用偶數規則。要確定是否應該繪制特定點,請從該點開始,繪制超出圖形邊界的線。計算線路交叉的路徑段數。如果結果是奇數,點被繪。如果結果是偶數,點不繪。繪制路徑段的方向不會影響結果。如圖3-12所示,每個圓圈繪制的方向無關緊要,填充將始終如圖所示。
Quartz提供了表3-5中顯示的用于填充當前路徑的函數。 一些是用于描邊矩形或橢圓的便利函數。
Setting Blend Modes
混合模式指定Quartz如何在背景上應用涂料。 Quartz默認使用正常混合模式,它使用以下公式將前景繪制與背景繪制相結合:
result = (alpha * foreground) + (1 - alpha) * background
顏色和顏色空間提供了顏色的alpha分量的詳細討論,它指定了顏色的不透明度。對于本節中的示例,您可以假設顏色完全不透明(alpha值= 1.0)。對于不透明顏色,當使用正常混合模式繪制時,在背景上繪制的任何內容都會遮蔽背景。
您可以通過調用函數CGContextSetBlendMode設置混合模式以實現各種效果,傳遞適當的混合模式常量。請記住,混合模式是圖形狀態的一部分。如果在更改混合模式之前使用函數CGContextSaveGState,則調用函數CGContextRestoreGState會將混合模式重置為正常。
本節的其余部分顯示在圖3-14所示的矩形上繪制如圖3-13所示的矩形的結果。在每種情況下(圖3-15到圖3-30),使用正常混合模式繪制背景矩形。然后通過用適當的常數調用函數CGContextSetBlendMode來改變混合模式。最后,繪制前景矩形。
注:您也可以使用混合模式來合成兩個圖像,或者在已經繪制到圖形上下文的任何內容上合成圖像。 將混合模式與圖像配合使用可提供有關如何使用混合模式合成圖像的信息,并顯示將混合模式應用于兩個圖像的結果。
Normal Blend Mode
因為正常混合模式是默認混合模式,所以使用常量kCGBlendModeNormal調用函數CGContextSetBlendMode僅在使用其他混合模式常量之后將混合模式重置為默認模式。 圖3-15顯示了使用正常混合模式繪制圖3-13和圖3-14的結果。
Multiply Blend Mode
乘法混合模式指定將前景圖像樣本與背景圖像樣本相乘。 所得到的顏色至少與兩種貢獻的樣品顏色中的任一種一樣暗。 圖3-16顯示了使用乘法混合模式繪制圖3-13和圖3-14的結果。 要使用此混合模式,請使用常量kCGBlendModeMultiply調用函數CGContextSetBlendMode。
Screen Blend Mode
屏幕混合模式指定將前景圖像樣本的倒數乘以背景圖像樣本的倒數。 所得到的顏色至少與兩種貢獻的樣品顏色中的任一種一樣輕。 圖3-17顯示了使用屏幕混合模式繪制圖3-13和圖3-14的結果。 要使用此混合模式,請使用常量kCGBlendModeScreen調用函數CGContextSetBlendMode。
Overlay Blend Mode
覆蓋混合模式指定根據背景顏色,將前景圖像樣本與背景圖像樣本相乘或篩選。 背景顏色與前景顏色混合以反映背景的亮度或暗度。 圖3-18顯示了使用覆蓋混合模式繪制圖3-13和圖3-14的結果。 要使用此混合模式,請使用常量kCGBlendModeOverlay調用函數CGContextSetBlendMode。
Darken Blend Mode
指定通過選擇較暗的樣本(來自前景圖像或背景)創建合成圖像樣本。 背景圖像樣本被更暗的任何前景圖像樣本替換。 否則,背景圖像樣本保持不變。 圖3-19顯示了使用暗混合模式繪制圖3-13和圖3-14的結果。 要使用此混合模式,請使用常量kCGBlendModeDarken調用函數CGContextSetBlendMode。
Lighten Blend Mode
指定通過選擇較亮的樣本(從前景或背景)創建復合圖像樣本。 結果是背景圖像樣本被更輕的任何前景圖像樣本替換。 否則,背景圖像樣本保持不變。 圖3-20顯示了使用lighten混合模式繪制圖3-13和圖3-14的結果。 要使用此混合模式,請使用常量kCGBlendModeLighten調用函數CGContextSetBlendMode。
Color Dodge Blend Mode
指定使背景圖像樣本變亮以反映前景圖像樣本。 指定黑色的前景圖像樣本值不會產生變化。 圖3-21顯示了使用顏色閃避混合模式繪制圖3-13和圖3-14的結果。 要使用此混合模式,請使用常量kCGBlendModeColorDodge調用函數CGContextSetBlendMode。
Color Burn Blend Mode
指定使背景圖像樣本變暗以反映前景圖像樣本。 指定白色的前景圖像樣本值不會產生變化。 圖3-22顯示了使用彩色混合模式繪制圖3-13和圖3-14的結果。 要使用此混合模式,請使用常量kCGBlendModeColorBurn調用函數CGContextSetBlendMode。
Soft Light Blend Mode
指定根據前景圖像樣本顏色使顏色變深或變亮。 如果前景圖像樣本顏色比50%灰色更亮,則背景被減輕,類似于躲避。 如果前景圖像樣本顏色比50%灰色深,則背景變暗,類似于燃燒。 如果前景圖像樣本顏色等于50%灰色,則背景不會改變。 等于純黑色或純白色的圖像樣本會產生較暗或較亮的區域,但不會產生純黑色或白色。 整體效果類似于通過在前景圖像上發射漫射聚光燈所實現的效果。 使用此向場景添加高光。 圖3-23顯示了使用柔光混合模式繪制圖3-13和圖3-14的結果。 要使用此混合模式,請使用常量kCGBlendModeSoftLight調用函數CGContextSetBlendMode。
Hard Light Blend Mode
指定乘法或屏幕顏色,具體取決于前景圖像樣本顏色。 如果前景圖像樣本顏色比50%灰色更亮,則背景被減輕,類似于篩選。 如果前景圖像樣本顏色比50%灰色深,則背景變暗,類似于乘法。 如果前景圖像樣本顏色等于50%灰色,則前景圖像不改變。 等于純黑色或純白色的圖像樣本導致純黑色或白色。 整體效果類似于通過在前景圖像上照射嚴酷的聚光燈所實現的效果。 使用此向場景添加高光。 圖3-24顯示了使用硬光混合模式繪制圖3-13和圖3-14的結果。 要使用此混合模式,請使用常量kCGBlendModeHardLight調用函數CGContextSetBlendMode。
Difference Blend Mode
指定從背景圖像樣本顏色中減去前景圖像樣本顏色,或反之,根據哪個樣本具有較大的亮度值。 黑色的前景圖像樣本值不變; 白色反轉背景顏色值。 圖3-25顯示了使用差分混合模式繪制圖3-13和圖3-14的結果。 要使用此混合模式,請使用常量kCGBlendModeDifference調用函數CGContextSetBlendMode。
Exclusion Blend Mode
指定與kCGBlendModeDifference產生的效果類似的效果,但對比度較低。 黑色的前景圖像樣本值不會產生變化; 白色反轉背景顏色值。 圖3-26顯示了使用排除混合模式繪制圖3-13和圖3-14的結果。 要使用此混合模式,請使用常量kCGBlendModeExclusion調用函數CGContextSetBlendMode。
Hue Blend Mode
指定使用背景的亮度和飽和度值與前景圖像的色調。 圖3-27顯示了使用色調混合模式繪制圖3-13和圖3-14的結果。 要使用此混合模式,請使用常量kCGBlendModeHue調用函數CGContextSetBlendMode。
Saturation Blend Mode
指定使用背景的亮度和色調值與前景圖像的飽和度。 沒有飽和度的背景區域(即,純灰色區域)不產生變化。 圖3-28顯示了使用飽和混合模式繪制圖3-13和圖3-14的結果。 要使用此混合模式,請使用常量kCGBlendModeSaturation調用函數CGContextSetBlendMode。
Color Blend Mode
指定使用背景的亮度值與前景圖像的色相和飽和度值。 此模式保留圖像中的灰度級。 您可以使用此模式對單色圖像進行著色或對彩色圖像進行著色。 圖3-29顯示了使用顏色混合模式繪制圖3-13的圖3-14的結果。 要使用此混合模式,請使用常量kCGBlendModeColor調用函數CGContextSetBlendMode。
Luminosity Blend Mode
指定使用背景的色相和飽和度與前景圖像的亮度。 此模式創建的效果與kCGBlendModeColor創建的效果相反。 圖3-30顯示了使用光度混合模式繪制圖3-13和圖3-14的結果。 要使用此混合模式,請使用常量kCGBlendModeLuminosity調用函數CGContextSetBlendMode。
剪切路徑
當前剪裁區域是從用作掩碼的路徑創建的,允許您阻止不想繪制的頁面部分。例如,如果您有一個非常大的位圖圖像,并且只想顯示它的一小部分,您可以設置剪切區域,只顯示您要顯示的部分。
當你繪畫時,Quartz只在剪貼區域內繪畫。在剪切區域的閉合子路徑內發生的繪制是可見的;在剪切區域的閉合子路徑外發生的繪制不是。
當最初創建圖形上下文時,剪切區域包括上下文的所有可繪制區域(例如,PDF上下文的媒體框)。通過設置當前路徑然后使用裁剪函數代替繪圖函數來更改裁剪區域。剪切函數使當前路徑的填充區域與現有剪切區域相交。因此,您可以交叉裁剪區域,縮小圖片的可見區域,但不能增加裁剪區域的面積。
剪切區域是圖形狀態的一部分。要將剪裁區域恢復到先前的狀態,您可以在剪裁之前保存圖形狀態,并在完成剪切繪制后恢復圖形狀態。
清單3-1顯示了一個以圓形形狀設置剪切區域的代碼片段。此代碼導致繪圖被剪切,類似于圖3-3所示。 (有關另一個示例,請參閱“漸變”一章中的“剪輯上下文”。)
// 設置圓形剪裁區域
CGContextBeginPath (context);
CGContextAddArc (context, w/2, h/2, ((w>h) ? h : w)/2, 0, 2*PI, 0);
CGContextClosePath (context);
CGContextClip (context);