Quartz 2D是一個(gè)二維圖形繪制引擎,支持iOS環(huán)境和Mac OS X環(huán)境。我們可以使用Quartz 2D API來(lái)實(shí)現(xiàn)許多功能,如基本路徑的繪制、透明度、描影、繪制陰影、透明層、顏色管理、反鋸齒、PDF文檔生成和PDF元數(shù)據(jù)訪(fǎng)問(wèn)。
繪制原理
Quartz 2D在圖像繪制中使用了繪畫(huà)者模型。在繪畫(huà)者模型中,每個(gè)連續(xù)的繪制操作都是將一個(gè)繪制層放置于一個(gè)畫(huà)布(canvas),我們可以理解為圖像的繪制即為在畫(huà)布上繪畫(huà),而繪畫(huà)操作的順序同時(shí)影響著我們繪制的結(jié)果,如圖:
圖形上下文(Graphics Context)
一個(gè)Graphics Context表示一個(gè)繪制目標(biāo)。它包含完成繪制任務(wù)所需的一些繪制參數(shù)和設(shè)備相關(guān)信息。Graphics Context定義了基本的繪制屬性,如顏色、裁減區(qū)域、線(xiàn)條寬度和字體信息、混合模式等。
Quartz 2D 坐標(biāo)系
Quartz 2D中默認(rèn)的坐標(biāo)系統(tǒng)為:左下角為坐標(biāo)系統(tǒng)原點(diǎn)(0,0),屏幕水平方向?yàn)閤軸,沿著x軸從左到右為正方向;屏幕垂直方向?yàn)閥軸,沿著y軸從下到上為正方向。在實(shí)際應(yīng)用中,不同的Graphics Context可能使用了不同的坐標(biāo)系統(tǒng),它們?cè)赒uartz 2D默認(rèn)的坐標(biāo)系統(tǒng)基礎(chǔ)之上做了調(diào)整,來(lái)適應(yīng)不同的場(chǎng)景。
iOS中的視圖繪制
在iOS應(yīng)用程序中,如果需要在屏幕上進(jìn)行視圖繪制,需要?jiǎng)?chuàng)建一個(gè)UIView對(duì)象,UIView是定義為在屏幕上的一塊矩形區(qū)域,用于管理這塊區(qū)域所呈現(xiàn)的內(nèi)容,而在這個(gè)矩形區(qū)域內(nèi),我們可以通過(guò)重寫(xiě)drawRect:方法來(lái)自定義一些需要顯示的內(nèi)容。drawRect:方法在視圖顯示在屏幕上及它的內(nèi)容需要更新時(shí)被系統(tǒng)自動(dòng)調(diào)用,我們手動(dòng)調(diào)用是無(wú)效的,系統(tǒng)提供了兩個(gè)方法讓我們進(jìn)行間接調(diào)用drawRect:來(lái)達(dá)到重繪視圖的目的:
// 方法一:重新繪制這個(gè)view
- (void)setNeedsDisplay;
// 方法二:重新繪制view的某個(gè)區(qū)域
- (void)setNeedsDisplayInRect:(CGRect)rect;
獲取Graphics Context
在調(diào)用自定義的drawRect:后,視圖對(duì)象自動(dòng)配置繪圖環(huán)境以便代碼能立即執(zhí)行繪圖操作。作為配置的一部分,視圖對(duì)象將為當(dāng)前的繪圖環(huán)境創(chuàng)建一個(gè)Graphics Context。我們可以在drawRect:中使用代碼獲取這個(gè)context:
CGContextRef context = UIGraphicsGetCurrentContext();
這里需要注意的是,這個(gè)context的坐標(biāo)系是默認(rèn)原點(diǎn)位于左上角,y軸正方向?yàn)橄蛳?。這是因?yàn)閁IKit使用的默認(rèn)的坐標(biāo)系統(tǒng)與Quartz 2D默認(rèn)的坐標(biāo)系統(tǒng)不同,在UIKit中,默認(rèn)原點(diǎn)位于左上角,y軸正方向?yàn)橄蛳?。所以UIView通過(guò)修改Quartz的Graphics Context的CTM(ps:一種仿射矩陣,通過(guò)平移(translation)、旋轉(zhuǎn)(rotation)、縮放(scale)操作可以將點(diǎn)從一個(gè)坐標(biāo)空間映射到另外一個(gè)坐標(biāo)空間)以使其與UIKit的坐標(biāo)系匹配。
創(chuàng)建與繪制路徑(Path)
路徑我們可以理解為我們手中的畫(huà)筆繪制出來(lái)的一個(gè)或者多個(gè)形狀(子路徑),每一個(gè)形狀可以是線(xiàn)、圓、矩形、星形等簡(jiǎn)單的形狀,也可以是一些更復(fù)雜的自定義形狀。如下圖顯示了一些路徑。左上角的直線(xiàn)可以是虛線(xiàn);直線(xiàn)也可以是實(shí)線(xiàn)。上邊中間的路徑是由多條曲線(xiàn)組成的開(kāi)放路徑;右上角的同心圓填充了顏色,但沒(méi)有描邊;左下角的加利福尼亞州是閉合路徑,由許多曲線(xiàn)和直線(xiàn)構(gòu)成,且對(duì)路徑進(jìn)行填充和描邊。
-
創(chuàng)建路徑:
路徑的創(chuàng)建和繪制是兩個(gè)獨(dú)立的工作,我們可以手動(dòng)創(chuàng)建一個(gè)路徑,也可以使用一些便利的函數(shù)幫我們隱式的創(chuàng)建一些路徑,我們獲取到的Quartz Context其實(shí)默認(rèn)會(huì)在內(nèi)部創(chuàng)建一個(gè)path用來(lái)保存繪圖信息。我們以構(gòu)建一條直線(xiàn)線(xiàn)段路徑為例:
- (void)drawRect:(CGRect)rect {
// 1.獲取Quartz Context
CGContextRef context = UIGraphicsGetCurrentContext();
// 2.開(kāi)始構(gòu)建路徑,設(shè)置一個(gè)起點(diǎn)的坐標(biāo),繪制系統(tǒng)會(huì)追蹤至該點(diǎn)進(jìn)行繪制
// 參數(shù)一:關(guān)聯(lián)的繪制目標(biāo)
// 參數(shù)二:起點(diǎn)的x坐標(biāo)
// 參數(shù)三:起點(diǎn)的y坐標(biāo)
CGContextMoveToPoint(context, 100, 100);
// 3.將當(dāng)前點(diǎn)與一個(gè)點(diǎn)(這里我們舉例為(200,100))之間構(gòu)建一條直線(xiàn)
// 參數(shù)一:關(guān)聯(lián)的繪制目標(biāo)
// 參數(shù)二:該端點(diǎn)的x坐標(biāo)
// 參數(shù)三:該端點(diǎn)的y坐標(biāo)
CGContextAddLineToPoint(context, 200, 100);
}
讓我們共同分析一下上面的代碼:
第一步,我們?cè)赿rawRect:方法中獲取Quartz Context;
第二步,我們準(zhǔn)備開(kāi)始構(gòu)建路徑,在構(gòu)建路徑時(shí)我們使用了CGContextMoveToPoint()函數(shù)來(lái)確定繪制的一個(gè)起始點(diǎn),context會(huì)將該點(diǎn)信息存儲(chǔ)至默認(rèn)生成的path中;
第三步,我們使用了CGContextAddLineToPoint()函數(shù)將兩點(diǎn)之間建立直線(xiàn)關(guān)系,同樣context會(huì)將該點(diǎn)信息也存儲(chǔ)至默認(rèn)生成的path中,注意此時(shí),我們只是構(gòu)建了一個(gè)直線(xiàn)路徑,并沒(méi)有繪制,所以運(yùn)行后不會(huì)有效果。
但是上述代碼的可讀性并不友好,而且在繪制路徑后,系統(tǒng)將清空Quartz Context,我們可能想保留路徑,特別是在繪制一些比較復(fù)雜場(chǎng)景時(shí),我們需要反復(fù)使用,所以我們?cè)趯?shí)際開(kāi)發(fā)中,通常會(huì)手動(dòng)創(chuàng)建一個(gè)path對(duì)象,而且這個(gè)path對(duì)象通常是可變的,使用的函數(shù)為CGPathCreateMutable(),數(shù)據(jù)類(lèi)型為:CGMutablePathRef,然后我們可以向該對(duì)象添加直線(xiàn)、弧、曲線(xiàn)和矩形等。Quartz提供了一個(gè)類(lèi)似于操作圖形上下文的CGPath的函數(shù)集合。這些路徑函數(shù)直接操作CGPath對(duì)象,而不是Quartz Context。這些函數(shù)包括:
CGPathCreateMutable,取代CGContextBeginPath
CGPathMoveToPoint,取代CGContextMoveToPoint
CGPathAddLineToPoint,取代CGContexAddLineToPoint
CGPathAddCurveToPoint,取代CGContexAddCurveToPoint
CGPathAddEllipseInRect,取代CGContexAddEllipseInRect
CGPathAddArc,取代CGContexAddArc
CGPathAddRect,取代CGContexAddRect
CGPathCloseSubpath,取代CGContexClosePath
如果想要添加一個(gè)路徑或者多個(gè)路徑到Quartz Context,可以調(diào)用CGContextAddPath。路徑將保留在Quartz Context中,直到Quartz繪制它。
-
繪制路徑:
構(gòu)建路徑后我們可以給它描邊(Stroke)或者填充(Fill)。
描邊:繪制路徑的邊框;
填充:填充是繪制路徑包含的區(qū)域。
Quartz提供了關(guān)于描邊和填充的函數(shù),我們可以設(shè)置描邊線(xiàn)的屬性,如寬度、顏色等,也可以設(shè)置填充的顏色以及填充的方式。
基本圖形繪制
-
線(xiàn)段繪制:
一條最基本的直線(xiàn)線(xiàn)段需要兩個(gè)端點(diǎn),一個(gè)起始點(diǎn)一個(gè)結(jié)束點(diǎn),下面的代碼案例繪制了一條P1(100, 100) 至P2(200, 100)之間的直線(xiàn)線(xiàn)段;
CGContextRef context = UIGraphicsGetCurrentContext();
CGMutablePathRef linePath = CGPathCreateMutable();
// 設(shè)置一個(gè)起點(diǎn)的坐標(biāo),繪制系統(tǒng)會(huì)追蹤至該點(diǎn)進(jìn)行繪制
CGPathMoveToPoint(linePath, NULL, 100, 100);
// 將當(dāng)前點(diǎn)與一個(gè)點(diǎn)(這里我們舉例為(200,100))之間構(gòu)建一條直線(xiàn)
CGPathAddLineToPoint(linePath, NULL, 200, 100);
CGContextAddPath(context, linePath);
// 設(shè)置描邊的顏色(兩種方式)
// 方法一:
// CGContextSetRGBStrokeColor(context, 211.f / 255.f, 106.f / 255.f, 119.f / 255.f, 1.0);
// 方法二:
[[UIColor redColor] setStroke];
// 設(shè)置描邊的寬度
CGContextSetLineWidth(context, 10);
// 繪制路徑-僅描邊
CGContextStrokePath(context);
// 注意內(nèi)存管理
CGPathRelease(linePath);
-
多條連續(xù)線(xiàn)段繪制:
如果繪制多個(gè)且連續(xù)的線(xiàn)段,只需多次調(diào)用CGPathAddLineToPoint()函數(shù)連接更多的點(diǎn)即可,當(dāng)然也有更便利的方式提供給我們:
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextRef context = UIGraphicsGetCurrentContext();
CGMutablePathRef linesPath = CGPathCreateMutable();
CGPoint pointArray[] = {CGPointMake(100, 100), CGPointMake(200, 100), CGPointMake(200, 200), CGPointMake(100, 200)};
// 通過(guò)數(shù)組繪制一條多端點(diǎn)的線(xiàn)
// 參數(shù)一:關(guān)聯(lián)的繪制路徑
// 參數(shù)二:仿射變化
// 參數(shù)三:數(shù)組
// 參數(shù)四:數(shù)組元素個(gè)數(shù)
CGPathAddLines(linesPath, NULL, pointArray, sizeof(pointArray) / sizeof(CGPoint));
CGContextAddPath(context, linesPath);
// 設(shè)置填充的顏色
// 方法一:
CGContextSetRGBFillColor(context, 211.f / 255.f, 106.f / 255.f, 119.f / 255.f, 1.0);
// 方法二:
[[UIColor redColor] setFill];
// 閉合繪制路徑,會(huì)從當(dāng)前點(diǎn)繪制至起點(diǎn)
CGContextClosePath(context);
// 繪制路徑-僅描邊
// CGContextStrokePath(context);
// 繪制路徑-僅填充
// CGContextFillPath(context);
// 繪制路徑-既有描邊也有填充
CGContextDrawPath(context, kCGPathFillStroke);
CGPathRelease(linesPath);
-
弧線(xiàn)繪制:
弧指的是圓弧段。我們指定一個(gè)圓心,半徑和放射角(以弧度為單位)。放射角為2*PI時(shí),創(chuàng)建的是一個(gè)圓。
CGContextRef context = UIGraphicsGetCurrentContext();
CGMutablePathRef arcPath = CGPathCreateMutable();
// 繪制一條弧
// 參數(shù)一:關(guān)聯(lián)的繪制路徑
// 參數(shù)二:仿射變化
// 參數(shù)三:原點(diǎn)的坐標(biāo)x
// 參數(shù)四:原點(diǎn)的坐標(biāo)y
// 參數(shù)五:半徑
// 參數(shù)六:起始弧度
// 參數(shù)七:結(jié)束弧度
// 參數(shù)八:繪制方向(0為順時(shí)針,1為逆時(shí)針)
CGPathAddArc(arcPath, NULL, 100, 100, 50, 0, 135 * (M_PI / 180), 1);
CGContextAddPath(context, arcPath);
CGContextStrokePath(context);
CGPathRelease(arcPath);
-
貝賽爾(Bezier)曲線(xiàn)繪制:
Bezier曲線(xiàn)是應(yīng)用于二維圖形的曲線(xiàn)。曲線(xiàn)由頂點(diǎn)和控制點(diǎn)組成,通過(guò)改變控制點(diǎn)坐標(biāo)可以改變曲線(xiàn)的形狀。
一次Bezier曲線(xiàn)是由P0至P1的連續(xù)點(diǎn),描述的一條線(xiàn)段:
二次Bezier曲線(xiàn)是 P0至P1 的連續(xù)點(diǎn)Q0和P1至P2 的連續(xù)點(diǎn)Q1 組成的線(xiàn)段上的連續(xù)點(diǎn)B(t),描述一條拋物線(xiàn):
三次Bezier曲線(xiàn):
繪制一條三次Bezier曲線(xiàn):
CGContextRef context = UIGraphicsGetCurrentContext();
CGMutablePathRef curvePath = CGPathCreateMutable();
CGPathMoveToPoint(curvePath, NULL, 20, 100);
// 繪制貝賽爾曲線(xiàn)
// 參數(shù)一:關(guān)聯(lián)的繪制路徑
// 參數(shù)二:仿射變化
// 參數(shù)三:控制點(diǎn)1的x坐標(biāo)
// 參數(shù)四:控制點(diǎn)1的y坐標(biāo)
// 參數(shù)五:控制點(diǎn)2的x坐標(biāo)
// 參數(shù)六:控制點(diǎn)2的y坐標(biāo)
// 參數(shù)七:終點(diǎn)的x坐標(biāo)
// 參數(shù)八:終點(diǎn)的y坐標(biāo)
CGPathAddCurveToPoint(curvePath, NULL, 50, 50, 80, 100, 130, 100);
CGContextAddPath(context, curvePath);
CGContextStrokePath(context);
CGPathRelease(curvePath);
-
矩形繪制:
我們可以調(diào)用CGPathAddRect或者CGContextAddRect來(lái)添加一個(gè)矩形到當(dāng)前路徑中,并提供一個(gè)CGRect結(jié)構(gòu)體(包含矩形的原點(diǎn)及大小)作為參數(shù)。
添加到路徑的矩形開(kāi)始于一個(gè)move-to-point操作,結(jié)束于一個(gè)close-subpath操作,所有的移動(dòng)方向都是順時(shí)針。
我們也可能調(diào)用CGPathAddRects或者CGContextAddRects函數(shù)來(lái)添加一系列的矩形到當(dāng)前路徑,并傳遞一個(gè)CGRect結(jié)構(gòu)體的數(shù)組。
CGContextRef context = UIGraphicsGetCurrentContext();
CGMutablePathRef rectPath = CGPathCreateMutable();
// 繪制矩形
// 參數(shù)一:關(guān)聯(lián)的繪制路徑
// 參數(shù)二:仿射變化
// 參數(shù)二:繪制位置及大小
CGPathAddRect(rectPath, NULL, CGRectMake(100, 100, 100, 100));
CGContextAddPath(context, rectPath);
CGContextStrokePath(context);
CGPathRelease(rectPath);
-
橢圓繪制:
橢圓是一種特殊的圓。橢圓是通過(guò)定義兩個(gè)焦點(diǎn),在平面內(nèi)所有與這兩個(gè)焦點(diǎn)的距離之和相等的點(diǎn)所構(gòu)成的圖形。
CGContextRef context = UIGraphicsGetCurrentContext();
CGMutablePathRef ellipsePath = CGPathCreateMutable();
// 繪制橢圓
// 參數(shù)一:關(guān)聯(lián)的繪制路徑
// 參數(shù)二:仿射變化
// 參數(shù)三:繪制的位置及大小
CGPathAddEllipseInRect(ellipsePath, NULL, CGRectMake(100, 100, 200, 200));
CGContextAddPath(context, ellipsePath);
[[UIColor redColor] setFill];
CGContextDrawPath(context, kCGPathFillStroke);
CGPathRelease(ellipsePath);