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

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);

參考:《Quartz 2D Programming Guide》

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,501評(píng)論 6 544
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,673評(píng)論 3 429
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事?!?“怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 178,610評(píng)論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 63,939評(píng)論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,668評(píng)論 6 412
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 56,004評(píng)論 1 329
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,001評(píng)論 3 449
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 43,173評(píng)論 0 290
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,705評(píng)論 1 336
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,426評(píng)論 3 359
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,656評(píng)論 1 374
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,139評(píng)論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,833評(píng)論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 35,247評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 36,580評(píng)論 1 295
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,371評(píng)論 3 400
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,621評(píng)論 2 380

推薦閱讀更多精彩內(nèi)容