Quartz 2D(一)概念、圖形上下文、路徑

Quartz 2D是二維圖形繪製引擎,可以實現N多圖形圖像的操作功能,如基本路徑的繪製、透明度、描影、繪製陰影、透明層、顏色管理、反鋸齒、PDF文檔生成和PDF元數據訪問。

畫布Page

在圖像操作過程中使用了繪畫者模型,繪製過程是將繪製層鋪到畫布上,這個畫布稱爲Page。類似於Photoshop中,將每個圖層疊加放在畫布上,形成最後的圖像。

圖形上下文Graphics Context

圖形上下文(Graphics Context)是一個數據類型CGContextRef,它存放了Quartz 2D繪製的圖形輸出信息,可以看做是圖形到設備輸出的介質工具。Quartz 2D繪製的圖形可以放到多種設備上,比如:PDF文件、顯示器窗口、bitmap(位圖)、view的Layer層等等,這就需要不同的Graphics Context來完成輸出到不同設備上的工作。這好比人要上高速需要汽車,去海上需要船,去天空需要飛機,這裏人相當於圖形,高速路、海、天空相當於設備,而汽車、船、飛機就是Graphics Context

Graphics Context的幾種類型

1
2
3
4
5
1. Bitmap Graphics Context
2. PDF Graphics Context
3. Window Graphics Context
4. Layer Context
5. Post Graphics Context

Quartz 2D的數據類型

Quartz 2D API屬於Code Graphics框架,所以Quartz 2D的數據類型是以CG開頭的。有以下數據類型:

1
2
3
4
5
6
7
8
9
10
11
12
1. CGPathRef:用於向量圖,可創建路徑,並進行填充或描畫(stroke)
2. CGImageRef:用於表示bitmap圖像和基於採樣數據的bitmap圖像遮罩。
3. CGLayerRef:用於表示可用於重複繪製(如背景)和幕後(offscreen)繪製的繪畫層
4. CGPatternRef:用於重繪圖
5. CGShadingRef、CGGradientRef:用於繪製漸變
6. CGFunctionRef:用於定義回調函數,該函數包含一個隨機的浮點值參數。當爲陰影創建漸變時使用該類型
7. CGColorRef, CGColorSpaceRef:用於告訴Quartz如何解釋顏色
8. CGImageSourceRef,CGImageDestinationRef:用於在Quartz中移入移出數據
9. CGFontRef:用於繪製文本
10. CGPDFDictionaryRef, CGPDFObjectRef, CGPDFPageRef, CGPDFStream, CGPDFStringRef, and CGPDFArrayRef:用於訪問PDF的元數據
11. CGPDFScannerRef, CGPDFContentStreamRef:用於解析PDF元數據
12. CGPSConverterRef:用於將PostScript轉化成PDF。在iOS中不能使用。

圖形狀態

Quartz通過修改圖形狀態來修改繪製結果,圖形狀態直接決定了圖形的最終渲染結果。圖形狀態包含用於繪製程序的參數,繪製參數改變了,圖形自然就變了。比如修改了填充色值,圖形顏色就變了。

iOS中使用Graphics Context繪製圖形

在iOS中要想用Quartz 2D在屏幕上繪圖,需要自定義一個UIView,在UIView的- (void)drawRect:(CGRect)rect方法中實現繪圖操作,這個方法會在UIView顯示在屏幕上和需要被刷新的時候調用。創建上下文的方法如下:

1
2
3
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
}

在上下文中繪製圖形:

1
2
3
4
5
6
7
8
9
10
11
12
- (void)drawRect:(CGRect)rect {
//獲取圖形上下文
CGContextRef context = UIGraphicsGetCurrentContext();
//設置紅色透明度0.5的填充色
CGContextSetRGBFillColor(context, 1, 0, 0, 0.5);
//填充一個矩形frame爲(10, 20, 100, 40)
CGContextFillRect(context, CGRectMake(10, 20, 100, 40));
//設置藍色透明度0.5的填充色
CGContextSetRGBFillColor(context, 0, 0, 1, 0.5);
//填充一個矩形frame爲(10, 20, 40, 100)
CGContextFillRect(context, CGRectMake(10, 20, 40, 100));
}

顯示結果顯示結果

注意:設置填充色和填充矩形的方法順序不能顛倒,不然填充色填充不到想填充的矩形中。得不到填充色填充的會是黑色。

路徑

路徑可以構建出多種圖形,可以是點、直線、弧線、不規則線、規則或不規則形狀,可以對閉合路徑進行填充行程面。使用路徑繪製出想要的圖形,有兩步:創建路徑和繪製路徑。創建出點、線等路徑,使用函數CGContextDrawPath繪製路徑。

點是依靠x、y值固定的位置,可作爲路徑的起始點位置。比如要畫一個線段,必須要有一個起點和一個終點,兩點確定一條線段。使用函數CGContextMoveToPoint來確定起始點,傳入圖形上下文和x,y座標點。

1
2
//確定一個座標爲(10,10)的點
CGContextMoveToPoint(context, 10, 10);

直線

有起始點,再指定一個終點位置就能確定一條直線了,使用函數CGContextAddLineToPoint來指定終點位置。

1
2
3
4
5
6
7
8
9
10
- (void)drawRect:(CGRect)rect {
//獲取圖形上下文
CGContextRef context = UIGraphicsGetCurrentContext();
//設置起點
CGContextMoveToPoint(context, 20, 20);
//起點延長至終點位置
CGContextAddLineToPoint(context, 50, 50);
//繪製路徑
CGContextDrawPath(context, kCGPathStroke);
}

可以使用CGContextAddLineToPoint函數指定多個位置點,線段會一個接一個畫下去,形成折線圖形。也可以使用CGContextAddLines函數一次性指定多個位置點,完成折線圖形,此時無需指定起始點,第一個點默認爲起始點(即使用CGContextMoveToPoint指定起始點也無效)。

1
2
3
4
5
6
7
8
//一個一個點繼續加
CGContextMoveToPoint(context, 20, 20);
CGContextAddLineToPoint(context, 50, 50);
CGContextAddLineToPoint(context, 30, 80);
//一次性指定多個點
CGPoint pos[3] = {CGPointMake(80, 20), CGPointMake(20, 50), CGPointMake(100, 200)};
CGContextAddLines(context, pos, 3);

由於CGContextAddLineToPoint函數必須得有CGContextMoveToPoint函數固定起始點,CGContextAddLines函數默認第一個點爲起始點,所以CGContextAddLines後面可以跟着CGContextAddLineToPoint繼續加點畫線,能連成一組折線,而CGContextMoveToPoint+CGContextAddLineToPoint畫線後面不能跟CGContextAddLines,會畫成兩組無關聯的折線。

圓弧

畫圓弧提供了兩個函數,一個是CGContextAddArc,依次指定圖形上下文圓心座標半徑起始弧度終止弧度畫線順時針(1)或逆時針(0)

1
2
3
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextAddArc(context, 100, 200, 30, 0, M_PI, 0);
CGContextDrawPath(context, kCGPathStroke);

第二個函數是CGContextAddArcToPoint,三點+半徑確定一個圓弧。CGContextMoveToPoint函數確定第一個點,CGContextAddArcToPoint添加第二個點、第三個點和還有半徑。原理:以第二點爲中心,分別向第一點、第三點延長兩條射線,射線夾角小於180°的一側,以指定半徑畫圓弧,圓弧與射線相切。

1
2
3
4
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextMoveToPoint(context, 100, 100);
CGContextAddArcToPoint(context, 100, 150, 150, 150, 50);
CGContextDrawPath(context, kCGPathStroke);

曲線

畫Bezier曲線也提供了兩個函數,函數CGContextAddCurveToPoint用於畫三次Bezier曲線,由一個起點、一個終點和兩個控制點構成一條三次Bezier曲線。當兩個控制點在起點和終點連線的同側時,曲線會只有一個拱向,在不同側時有兩個拱向。

1
2
3
4
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextMoveToPoint(context, 100, 100);
CGContextAddCurveToPoint(context, 150, 50, 200, 200, 300, 100);
CGContextDrawPath(context, kCGPathStroke);

畫二次Bezier曲線,使用函數CGContextAddQuadCurveToPoint。以當前點爲起始點,指定一個控制點一個終點,確定一條二次Bezier曲線。控制點決定了曲線拱的方向,該函數只能創建一個拱向的Bezier曲線,而且曲線不可能交叉。

1
2
3
4
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextMoveToPoint(context, 100, 100);
CGContextAddQuadCurveToPoint(context, 200, 200, 300, 100);
CGContextDrawPath(context, kCGPathStroke);

閉合路徑

Quartz 2D提供函數CGContextClosePath來閉合路徑。就是將終點和起點用直線連接起來,使整個路徑閉合。對於直線、弧、曲線等不能自動閉合的路徑,如果想完成路徑閉合必須調用該函數。

調用了CGContextClosePath完成閉合路徑後,再調用Add...添加路徑的函數,添加直線、弧線、曲線等路徑,會從閉合路徑的起始點開始。如果沒有完成閉合路徑函數的調用,則從終點繼續開始新添加的路徑。

1
2
3
4
5
6
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextMoveToPoint(context, 100, 100);
CGContextAddCurveToPoint(context, 150, 50, 150, 200, 300, 100);
CGContextClosePath(context);
CGContextAddLineToPoint(context, 200, 500);
CGContextDrawPath(context, kCGPathStroke);

矩形

使用函數CGContextAddRect來畫矩形,函數中的rect中x,y是矩形左上角,寬高是矩形的寬高。

1
2
3
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextAddRect(context, CGRectMake(20, 20, 100, 40));
CGContextDrawPath(context, kCGPathStroke);

橢圓

使用函數CGContextAddEllipseInRect來畫橢圓,是以矩形來確定橢圓。座標參數意義和矩形一樣,以確定的矩形內切畫出的橢圓。矩形的圓心即是橢圓的圓心,矩形的寬高即是橢圓的長軸短軸,當矩形的寬高相等時,橢圓就是一個圓了。

1
2
3
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextAddEllipseInRect(context, CGRectMake(0, 0, 30, 50));
CGContextDrawPath(context, kCGPathStroke);

創建路徑

  • 在開始繪製路徑前,調用 CGContextBeginPath 或 UI。
  • 直線、弧、曲線開始於當前點。空路徑沒有當前點;我們必須調用CGContextMoveToPoint來設置第一個子路徑的起始點,或者調用一個便利函數來隱式地完成該任務。
  • 如果要閉合當前子路徑,調用函數 CGContextClosePath。隨後路徑將開始一個新的子路徑,即使我們不顯示設置一個新的起始點。
  • 當繪製弧時,Quartz 將在當前點與弧的起始點間繪製一條直線。
  • 添加橢圓和矩形的 Quartz 程序將在路徑中添加新的閉合子路徑。
  • 我們必須調用繪製函數來填充或者描邊一條路徑,因爲創建路徑時並不會繪製路徑。
  • Quartz 提供了兩個數據類型來創建可複用路徑 CGPathRef 和 CGMutablePathRef。
  • Quartz 提供了一個類似於操作圖形上下文的 CGPath 的函數集合。這些路徑函數操作 CGPath 對象,而不是圖形上下文。
1
2
3
4
5
6
7
8
1. CGPathCreateMutable 取代 CGContextBeginPath
2. CGPathMoveToPoint 取代 CGContextMoveToPoint
3. CGPathAddLineToPoint 取代 CGContexAddLineToPoint
4. CGPathAddCurveToPoint 取代 CGContexAddCurveToPoint
5. CGPathAddEllipseInRect 取代 CGContexAddEllipseInRect
6. CGPathAddArc 取代 CGContexAddArc
7. CGPathAddRect 取代 CGContexAddRect
8. CGPathCloseSubpath 取代 CGContexClosePath

如果想要添加一個路徑到圖形上下文,可以調用CGContextAddPath。路徑將保留在圖形上下文中,直到Quartz繪製它。我們可以調用CGContextAddPath再次添加路徑。

繪製路徑

通過圖形上下文加上一系列創建路徑的函數得到最終的路徑,還需要繪製路徑操作,才能使得路徑呈現。如上文中一直用到的繪製路徑的函數操作CGContextDrawPath(context, kCGPathStroke);

繪製路徑分爲描邊和填充,描邊是繪製路徑的邊框,填充是繪製路徑所包含的區域。

影響描邊的屬性

函數 解釋 備註
CGContextSetLineWidth 描邊路徑的寬度 單位是用戶空間單位
CGContextSetLineJoin 指定描邊的連接點樣式 枚舉類型:Miter(尖角),Round(圓角),Bevel(平角)
CGContextSetLineCap 指定直線端點樣式 枚舉類:Butt,Round,Square
CGContextSetMiterLimit 轉角的量級
CGContextSetLineDash 虛線模式 指定虛線數組和虛線相位控制虛線大小和位置
CGContextSetStrokeColorSpace 描邊顏色空間 封裝了顏色和顏色空間的CGColorRef
CGContextSetStrokeColor 設置描邊顏色
CGContextSetStrokePattern 筆觸模式

填充路徑

填充規則有兩種:非零纏繞數規則(nonzero winding number rule)、偶數-奇數規則(even-odd rule)。

默認的填充規則爲非零纏繞數規則。方法或枚舉帶有“EO”的爲偶數-奇數規則。

非零纏繞數的填充規則與繪製的方向有關、偶數-奇數規則則與方向無關。如圖。

非零纏繞數規則和偶數奇數規則非零纏繞數規則和偶數奇數規則

函數 解釋
CGContextEOFillPath 使用偶數奇數規則填充當前路徑
CGContextFillPath 使用非零纏繞數規則填充當前路徑
CGContextFillRect 快速填充一個矩形
CGContextFillRects 快速填充N個矩形
CGContextFillEllipseInRect 快速填充一個橢圓形
CGContextDrawPath 填充或描邊路徑

CGContextDrawPath函數的使用:

  • CGContextDrawPath(context, kCGPathFill) 填充路徑。
  • CGContextDrawPath(context, kCGPathEOFill) 使用奇偶規則填充路徑。
  • CGContextDrawPath(context, kCGPathStroke) 描邊路徑。
  • CGContextDrawPath(context, kCGPathFillStroke) 填充並描邊路徑。
  • CGContextDrawPath(context, kCGPathEOFillStroke) 使用奇偶規則填充並描邊路徑。

混合模式

混合模式是Quartz將繪圖繪製到背景上的方式,其實就是前景圖和背景圖怎麼混合疊加。疊加公式如下:

1
result = (alpha * foreground) + (1 - alpha) *background

Quartz默認使用普通混合模式,也就是kCGBlendModeNormal。下面舉例幾個混合模式的樣式,案例樣式的代碼見下一節裁剪路徑

kCGBlendModeNormal kCGBlendModeMultiply kCGBlendModeScreen
普通模式普通模式 正片疊底正片疊底 屏幕混合屏幕混合

16種混合模式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
kCGBlendModeNormal, //普通
kCGBlendModeMultiply, //正片疊底
kCGBlendModeScreen, //屏幕
kCGBlendModeOverlay, //疊加
kCGBlendModeDarken, //暗化
kCGBlendModeLighten, //亮化
kCGBlendModeColorDodge, //色彩減淡
kCGBlendModeColorBurn, //色彩加深
kCGBlendModeSoftLight, //柔光
kCGBlendModeHardLight, //強光
kCGBlendModeDifference, //差值
kCGBlendModeExclusion, //排除
kCGBlendModeHue, //色相
kCGBlendModeSaturation, //飽和度
kCGBlendModeColor, //顏色
kCGBlendModeLuminosity, //亮度

裁剪路徑

裁剪是一個遮罩,會遮住不允許繪製的地方。裁剪區域是一個閉合路徑,Quartz只會渲染裁剪區域裏面的東西,外面的東西不渲染。通過函數CGContextClip裁剪,具體的裁剪代碼和執行結果如下,注意代碼位置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
CGContextRef context = UIGraphicsGetCurrentContext();
/** 裁剪 */
CGContextBeginPath(context);
CGContextAddArc(context, 200, 200, 150, 0, 2 * M_PI, 0);
CGContextClosePath(context);
CGContextClip(context);
CGContextSetRGBFillColor(context, 0.5, 0.2, 1, 1);
CGContextFillRect(context, CGRectMake(100, 50, 50, 300));
CGContextSetRGBFillColor(context, 0.8, 0.3, 0.1, 1);
CGContextFillRect(context, CGRectMake(150, 50, 50, 300));
CGContextSetRGBFillColor(context, 0.5, 0.1, 0.4, 1);
CGContextFillRect(context, CGRectMake(200, 50, 50, 300));
CGContextSetRGBFillColor(context, 0, 0.5, 0.1, 1);
CGContextFillRect(context, CGRectMake(250, 50, 50, 300));
//設置混合模式,自行替換上述16中混合模式查看效果
CGContextSetBlendMode(context, kCGBlendModeMultiply);
CGContextSetRGBFillColor(context, 0.5, 0.2, 1, 1);
CGContextFillRect(context, CGRectMake(20, 100, 350, 50));
CGContextSetRGBFillColor(context, 0.8, 0.3, 0.1, 1);
CGContextFillRect(context, CGRectMake(20, 150, 350, 50));
CGContextSetRGBFillColor(context, 0.5, 0.1, 0.4, 1);
CGContextFillRect(context, CGRectMake(20, 200, 350, 50));
CGContextSetRGBFillColor(context, 0, 0.5, 0.1, 1);
CGContextFillRect(context, CGRectMake(20, 250, 350, 50));

執行結果:

裁剪路徑和混合模式的執行結果裁剪路徑和混合模式的執行結果


參考文章:南峯子翻譯的Quartz2D編程指南

轉自: http://markmiao.com/2016/12/13/Quartz2D1/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章