iOS開發(fā)-Quartz 2D基礎(chǔ)篇

Quartz 2D是一個(gè)二維圖形繪制引擎,支持iOS環(huán)境和Mac OS X環(huán)境。我們可以使用Quartz 2D API來實(shí)現(xiàn)許多功能,如基本路徑的繪制、透明度、描影、繪制陰影、透明層、顏色管理、反鋸齒、PDF文檔生成和PDF元數(shù)據(jù)訪問。

繪制原理

Quartz 2D在圖像繪制中使用了繪畫者模型。在繪畫者模型中,每個(gè)連續(xù)的繪制操作都是將一個(gè)繪制層放置于一個(gè)畫布(canvas),我們可以理解為圖像的繪制即為在畫布上繪畫,而繪畫操作的順序同時(shí)影響著我們繪制的結(jié)果,如圖:

圖形上下文(Graphics Context)

一個(gè)Graphics Context表示一個(gè)繪制目標(biāo)。它包含完成繪制任務(wù)所需的一些繪制參數(shù)和設(shè)備相關(guān)信息。Graphics Context定義了基本的繪制屬性,如顏色、裁減區(qū)域、線條寬度和字體信息、混合模式等。

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)整,來適應(yī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),我們可以通過重寫drawRect:方法來自定義一些需要顯示的內(nèi)容。drawRect:方法在視圖顯示在屏幕上及它的內(nèi)容需要更新時(shí)被系統(tǒng)自動(dòng)調(diào)用,我們手動(dòng)調(diào)用是無效的,系統(tǒng)提供了兩個(gè)方法讓我們進(jìn)行間接調(diào)用drawRect:來達(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通過修改Quartz的Graphics Context的CTM(ps:一種仿射矩陣,通過平移(translation)、旋轉(zhuǎn)(rotation)、縮放(scale)操作可以將點(diǎn)從一個(gè)坐標(biāo)空間映射到另外一個(gè)坐標(biāo)空間)以使其與UIKit的坐標(biāo)系匹配。

創(chuàng)建與繪制路徑(Path)

路徑我們可以理解為我們手中的畫筆繪制出來的一個(gè)或者多個(gè)形狀(子路徑),每一個(gè)形狀可以是線、圓、矩形、星形等簡單的形狀,也可以是一些更復(fù)雜的自定義形狀。如下圖顯示了一些路徑。左上角的直線可以是虛線;直線也可以是實(shí)線。上邊中間的路徑是由多條曲線組成的開放路徑;右上角的同心圓填充了顏色,但沒有描邊;左下角的加利福尼亞州是閉合路徑,由許多曲線和直線構(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用來保存繪圖信息。我們以構(gòu)建一條直線線段路徑為例:
    - (void)drawRect:(CGRect)rect {
    
    // 1.獲取Quartz Context
    CGContextRef context = UIGraphicsGetCurrentContext();
    // 2.開始構(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)建一條直線
    // 參數(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)備開始構(gòu)建路徑,在構(gòu)建路徑時(shí)我們使用了CGContextMoveToPoint()函數(shù)來確定繪制的一個(gè)起始點(diǎn),context會(huì)將該點(diǎn)信息存儲(chǔ)至默認(rèn)生成的path中;
第三步,我們使用了CGContextAddLineToPoint()函數(shù)將兩點(diǎn)之間建立直線關(guān)系,同樣context會(huì)將該點(diǎn)信息也存儲(chǔ)至默認(rèn)生成的path中,注意此時(shí),我們只是構(gòu)建了一個(gè)直線路徑,并沒有繪制,所以運(yùn)行后不會(huì)有效果。
但是上述代碼的可讀性并不友好,而且在繪制路徑后,系統(tǒng)將清空Quartz Context,我們可能想保留路徑,特別是在繪制一些比較復(fù)雜場景時(shí),我們需要反復(fù)使用,所以我們?cè)趯?shí)際開發(fā)中,通常會(huì)手動(dòng)創(chuàng)建一個(gè)path對(duì)象,而且這個(gè)path對(duì)象通常是可變的,使用的函數(shù)為CGPathCreateMutable(),數(shù)據(jù)類型為:CGMutablePathRef,然后我們可以向該對(duì)象添加直線、弧、曲線和矩形等。Quartz提供了一個(gè)類似于操作圖形上下文的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è)置描邊線的屬性,如寬度、顏色等,也可以設(shè)置填充的顏色以及填充的方式。

基本圖形繪制

  • 線段繪制:
    一條最基本的直線線段需要兩個(gè)端點(diǎn),一個(gè)起始點(diǎn)一個(gè)結(jié)束點(diǎn),下面的代碼案例繪制了一條P1(100, 100) 至P2(200, 100)之間的直線線段;
    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)建一條直線
    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ù)線段繪制:
    如果繪制多個(gè)且連續(xù)的線段,只需多次調(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)};
    // 通過數(shù)組繪制一條多端點(diǎ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);
  • 弧線繪制:
    弧指的是圓弧段。我們指定一個(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)曲線繪制:
    Bezier曲線是應(yīng)用于二維圖形的曲線。曲線由頂點(diǎn)和控制點(diǎn)組成,通過改變控制點(diǎn)坐標(biāo)可以改變曲線的形狀。
    一次Bezier曲線是由P0至P1的連續(xù)點(diǎn),描述的一條線段:

二次Bezier曲線是 P0至P1 的連續(xù)點(diǎn)Q0和P1至P2 的連續(xù)點(diǎn)Q1 組成的線段上的連續(xù)點(diǎn)B(t),描述一條拋物線:

三次Bezier曲線:

繪制一條三次Bezier曲線:

CGContextRef context = UIGraphicsGetCurrentContext();
CGMutablePathRef curvePath = CGPathCreateMutable();
CGPathMoveToPoint(curvePath, NULL, 20, 100);
// 繪制貝賽爾曲線
// 參數(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來添加一個(gè)矩形到當(dāng)前路徑中,并提供一個(gè)CGRect結(jié)構(gòu)體(包含矩形的原點(diǎn)及大小)作為參數(shù)。
    添加到路徑的矩形開始于一個(gè)move-to-point操作,結(jié)束于一個(gè)close-subpath操作,所有的移動(dòng)方向都是順時(shí)針。
    我們也可能調(diào)用CGPathAddRects或者CGContextAddRects函數(shù)來添加一系列的矩形到當(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);
  • 橢圓繪制:
    橢圓是一種特殊的圓。橢圓是通過定義兩個(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);

參考:《Quartz 2D Programming Guide》

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容