iOS開發系列–打造自己的“美圖秀秀”

發表於2016-03-09

概述

在iOS中可以很容易的開發出絢麗的介面效果,一方面得益於成功系統的設計,另一方面得益於它強大的開發框架。今天我們將圍繞iOS中兩大圖形、影像繪圖框架進行介紹:Quartz 2D繪製2D圖形和Core Image中強大的濾鏡功能。

Quartz 2D

在iOS中常用的繪圖框架就是Quartz 2D,Quartz 2D是Core Graphics框架的一部分,是一個強大的二維影像繪製引擎。Quartz 2D在UIKit中也有很好的封裝和整合,我們日常開發時所用到的UIKit中的元件都是由Core Graphics進行繪製的。不僅如此,當我們引入UIKit框架時系統會自動引入Core Graphics框架,並且為了方便開發者使用在UIKit內部還對一些常用的繪圖API進行了封裝。

在iOS中繪圖一般分為以下幾個步驟:

1.獲取繪圖上下文

2.建立並設定路徑

3.將路徑新增到上下文

4.設定上下文狀態

5.繪製路徑

6.釋放路徑

圖形上下文CGContextRef代表圖形輸出裝置(也就是繪製的位置),包含了繪製圖形的一些裝置資訊,Quartz 2D中的所有物件最終都必須繪製到圖形上下文。這樣一來,我們在繪製圖形時就不必關心具體的裝置資訊,統一了程式碼編寫方式(在Quartz 2D中的繪圖上下文可以是點陣圖Bitmap、PDF、視窗Window、層Layer、列印物件Printer)。

基本圖形繪製

在UIKit中預設已經為我們準備好了一個圖形上下文物件,在UI控制元件的drawRect:方法(這個方法在loadView、viewDidLoad方法後執行)中我們可以通過UIKit封裝函式UIGraphicsGetCurrentContext()方法獲得這個圖形上下文(注意在其他UI控制元件方法中無法取得這個物件),然後我們只要按照繪圖步驟一步步執行即可。下面自定義一個KCView繼承自UIView,重寫drawRect:方法繪製兩條直線說明上面繪圖的步驟:

KCView.m

 

在檢視控制器建立KCView並新增到根檢視中:

執行效果如下:

DrawPath

簡化繪圖方式

上面的繪圖方式未免顯得有些麻煩,其實Core Graphics 內部對建立物件新增到上下文這兩步操作進行了封裝,可以一步完成。另外前面也說過UIKit內部其實封裝了一些以“UI”開頭的方法幫助大家進行圖形繪製。就拿前面的例子來說我們改進一些繪製方法:

 

上面的操作相比前面的方法應該說已經簡化了不少,除了路徑之外其他矩形、橢圓等都有對應的建立方法。另外上面我們也演示了封閉路徑的方法,大家可以執行看一下效果。

其他圖形繪製

相信大家瞭解了上面的繪製步驟其他圖形繪製並不麻煩,下面以一個例子簡單演示一下其他圖形的繪製,包括文字和影像的繪製。

繪製矩形

在下面的方法中還可以看到UIKit對繪圖方法的封裝,使用起來更加簡單。

執行效果:

DrawRect

繪製橢圓

執行效果:

DrawEllipse

繪製弧形

執行效果:

DrawArc

繪製貝塞爾曲線

要繪製規則圖形在iOS中相當簡單,但是不規則圖形怎麼繪製呢?此時就要利用路徑。前面我們繪製了直線,它和曲線繪製都屬於路徑繪製。和直線繪製相比曲線繪製就要複雜一些,但是路徑作為高階動畫的基礎又是我們必須掌握的,因此這裡我們就一起來熟悉一下曲線繪製。在Quartz 2D中曲線繪製分為兩種:二次貝塞爾曲線和三次貝塞爾曲線。二次曲線只有一個控制點,而三次曲線有兩個控制點,如下圖所示:

Bezier

當然,在iOS中兩種曲線分別對應兩種方法:

CGContextAddQuadCurveToPoint(CGContextRef c, CGFloat cpx, CGFloat cpy, CGFloat x, CGFloat y);

CGContextAddCurveToPoint(context, CGFloat cp1x, CGFloat cp1y, CGFloat cp2x, CGFloat cp2y, CGFloat x, CGFloat y);
下面就演示一下這兩種曲線的繪製方法

執行效果:

BesizerEffice

 

 

備註:貝塞爾曲線是由法國數學家“貝塞爾”發現的,他發現:任何一條曲線都能夠由和它相切的直線的兩個端點來描述,這種曲線表示方式後來被廣泛應用到計算機中,稱為“貝塞爾曲線”。

文字繪製

除了繪製圖形還可以繪製文字內容。

 

執行效果:

DrawText

影像繪製

Quartz 2D還可以將影像繪製到圖形上下文。

 

執行效果:

DrawImage

繪製漸變填充

從前面的示例中我們可以看到如何設定填充顏色,事實上很多時候純色的填充並不能滿足我們的需求,例如有時候我們要繪製一些圖形可能需要設定一個漂亮的背景,這個時候我們可能就會選擇漸變填充方式。Quartz 2D的漸變方式分為兩種:

a.線性漸變線:漸變色以直線方式從開始位置逐漸向結束位置漸變

b.徑向漸變:以中心點為圓心從起始漸變色向四周輻射,直到終止漸變色

要做漸變則必須先設定從開始位置到結束位置的漸變顏色,做過photoshop的朋友相信對於漸變色設定並不陌生,只要在指定位置指定不同的顏色,剩下的事情交給系統處理即可,如下圖在起始位置、3/10位置、結束位置指定了三種顏色就形成由三種顏色組成的漸變色:

GradientColor

另外,在iOS中繪製漸變還需要注意一點就是指定顏色空間,所謂顏色空間就是不同顏色在不同的維度上取值最終組成一種顏色的過程。就拿RGB來說,如果將紅色、綠色、藍色看成是x、y、z軸座標系,那麼在三個座標上分別取0~255範圍內的不同值則可以組成各類顏色。當然,不同顏色空間的“座標系”也是不同的(也就是說顏色表示的方式是不同的),常用的顏色空間除了RGB還有CMYK(印刷業常用這種顏色模式)、Gray。

在使用Quartz 2D繪圖時我們的顏色除了使用常規的方法(如何前面CGContextSetRGBFillColor(CGContextRef context, CGFloat red, CGFloat green, CGFloat blue, CGFloat alpha)方法)設定RGB和透明度外,有時還會遇到顏色引數是一個陣列情況。如使用顏色空間填充時用到的CGContextSetFillColor(CGContextRef context, const CGFloat *components)方法,這個時候components陣列中具體是如何儲存顏色就要根據顏色空間而定,如果顏色空間使用RGB則陣列中的元素四個為一組,分別是red(紅)、green(綠)、blue(藍)、alpha(透明度);如果使用CMYK顏色空間,那麼陣列中的元素五個為一組,分別是cyan(青)、magenta(洋紅)、yellow(黃)、black(黑)、alpha(透明度)。

下面的程式碼分別演示了兩種漸變方式,具體漸變繪製函式引數程式碼中已經註釋的很清楚了:

 

執行效果:

LinearGradient     RadiaGradient

擴充套件–漸變填充

上面我們只是繪製漸變到圖形上下文,實際開發中有時候我們還需要填充對應的漸變色,例如現在繪製了一個矩形,如何填充成漸變色呢?在此可以利用漸變裁切來完成(當然利用層CALayer更加方便但這不在今天的話題討論範圍內),特別說明一下區域裁切並不僅僅適用於漸變填充,對於其他圖形繪製仍然適用,並且注意裁切只能限於矩形裁切。

 

執行效果:

ClipRectEffect

其他狀態設定

常用的圖形上下文狀態設定上面基本都用到了,我們不再一一解釋,這裡著重說一下疊加模式和填充模式,初學者對於這兩個狀態設定往往容易產生疑惑。

疊加模式

使用Quartz 2D繪圖時後面繪製的影像會覆蓋前面的,預設情況下如果前面的被覆蓋後將看不到後面的內容,但是有時候這個結果並不是我們想要的,因此在Quartz 2D中提供了填充模式供開發者配置調整。由於填充模式類別特別多,因此下面以一個例子來說明:

執行效果:

BlendModeEffect

相信大家對比程式碼和顯示效果並不難發現每種疊加的效果。例子中只是使用UIKit的封裝方法進行疊加模式設定,更一般的方法當然是使用CGContextSetBlendMode(CGContextRef context, CGBlendMode mode)方法進行設定。

填充模式

前面的示例中已經演示過純色填充、漸變填充,而有時我們需要按一定的自定義樣式進行填充,這種方式有點類似於貼瓷磚的方式。我們知道如果家裡貼地板或瓷磚時,通常我們會先選擇一種瓷磚樣式,根據房間面積我們購買不同量的瓷磚。但是不管買多少,這些瓷磚的樣式都是一模一樣的。填充模式就是為了達到這種效果而產生的:我們只需要繪製一個瓷磚的樣式,然後讓程式自動呼叫這種樣式填充指定大小的區域。

Quartz 2D支援兩種填充模式:有顏色填充和無顏色填充。兩種模式使用起來區別很小,有顏色填充就是在繪製瓷磚時就指定顏色,在呼叫填充時就不用再指定瓷磚顏色;無顏色填充模式就是繪製瓷磚時不用指定任何顏色,在呼叫填充時再指定具體填充顏色。相比較無顏色填充模式而言,有顏色填充模式更加的靈活,推薦使用。

下面我們具體看一下如何按指定模式進行圖形填充:

1.在使用填充模式時首先要構建一個符合CGPatternDrawPatternCallback簽名的方法,這個方法專門用來建立“瓷磚”。注意:如果使用有顏色填充模式,需要設定填充色。例如我們定義一個方法drawTile繪製以下瓷磚(有顏色填充):

Tile

2.接著需要指定一個填充的顏色空間,這個顏色空間跟前面繪製漸變的顏色空間不太一樣,前面建立漸變使用的顏色空間是裝置無關的,我們需要基於這個顏色空間建立一個顏色空間專門用於填充(注意對於有顏色填充建立填充顏色空間引數為NULL,不用基於裝置無關的顏色空間建立)。

3.然後我們就可以使用CGPatternCreate方法建立一個填充模式,建立填充模式時需要注意其中的引數,在程式碼中已經做了一一解釋(這裡注意對於有顏色填充模式isColored設定為true,否則為false)。

4.最後呼叫CGContextSetFillPattern方法給圖形上下文指定填充模式(這個時候注意最後一個引數,如果是有顏色填充模式最後一個引數為透明度alpa的地址,對於無顏色填充模式最後一個引數是當前填充顏色空間的顏色陣列)。

5.繪製圖形,這裡我們繪製一個矩形。

6.釋放資源。

下面是具體程式碼(包含兩種填充模式程式碼,可以一一執行)

執行效果:

FillWithPattern

這裡強調一點,在drawTile回撥方法中不要使用UIKit封裝方法進行圖形繪製(例如UIRectFill等),由於這個方法由Core Graphics內部呼叫,而Core Graphics考慮到跨平臺問題,內部是不允許呼叫UIKit方法的。

上下文變換

我們知道在UIKit開發中UIView有一個transform屬性用於控制元件的形變,其實在繪圖中我們也經常用到圖形形變,這個時候可以藉助圖形上下文的形變方法來完成。在弄清形變之前我們要清楚圖形上下文的座標原點,因為無論是位移還是旋轉都是相對於座標原點進行的。其實Quartz 2D的座標系同UIKit並不一樣,它的座標原點在螢幕左下方,但是為了統一程式設計方式,UIKit對其進行了轉換,座標原點統一在螢幕左上角。注意在設定圖形上下文形變之前一定要注意儲存上下文的初始狀態,在使用完之後進行恢復。否則在處理多個圖形形變的時候很容易弄不清楚到底是基於怎樣的座標系進行繪圖,容易找不到原點(做過html5 canvas繪圖的朋友對這一點應該很熟悉,在html5中繪圖也經常進行狀態儲存和恢復)。下面通過一個圖片的變換演示一下圖形上下文的形變(其他圖形也是一樣的,就不再演示):

 

最終執行效果見第四幅截圖,下圖描繪出了整個程式的執行過程(移動->縮放->旋轉):

CGContentRefState

擴充套件–使用Core Graphics繪製影像

在前面基本繪圖部分,繪製影像時使用了UIKit中封裝的方法進行了影像繪製,我們不妨看一下使用Quartz 2D內建方法繪製是什麼效果。

執行效果:

DrawImage2Effect

看起來整個影像是倒過來的,原因正是前面說的:在Core Graphics中座標系的y軸正方向是向上的,座標原點在螢幕左下角,y軸方向剛好和UIKit中y軸方向相反。而使用UIKit進行繪圖之所以沒有問題是因為UIKit中進行了處理,事實上對於其他圖形即使使用Core Graphics繪製也沒有問題,因為UIKit統一了程式設計方式。但是使用Core Graphics中內建方法繪製影像是存在這種問題的,如何解決呢?

其實圖形上下文只要沿著x軸旋轉180度,然後向上平移適當的高度即可(但是注意不要沿著z軸旋轉,這樣得不到想要的結果)。可是通過前面介紹的CGContextRotateCTM方法只能通過沿著z軸旋轉,此時不妨使用另外一種方法,那就是在y軸方向縮放-1,同樣可以達到想要的效果:

 

檢視重新整理

在UIView的drawRect:中繪製的圖形會在控制元件顯示的時候呼叫(而且顯示時會重繪所有圖形),有時候我們希望繪製內容的顯示是實時的,此時我們就需要呼叫繪圖方法重新繪製,但是在iOS開發中不允許開發者直接呼叫drawRect:方法,重新整理繪製內容需要呼叫setNeedsDisplay方法。下面以一個調整字型大小的介面演示一下檢視的重新整理。

KCView.h

 

KCView.m

 

KCMainViewController.m

 

執行效果:

RefreshViewEffect

其他圖形上下文

前面我們也說過,Quartz 2D的圖形上下方除了可以繪製到層上還可以繪製到點陣圖、PDF等,這裡我們就介紹一下如何利用Quartz 2D繪製影像到點陣圖及PDF中。

上面的示例中一直都是在drawRect:方法中利用UIGraphicsGetCurrentContext()方法取得上下文,要得到點陣圖或者PDF的上下文可以利用UIGraphicsBeginImageContext(CGSize size)和UIGraphicsBeginPDFPageWithInfo(CGRect bounds, NSDictionary *pageInfo)方法。點陣圖圖形上下文和PDF圖形上下文UIKit是不會負責建立的,所以需要使用者手動建立,並且在使用完後關閉它。在使用UIKit中系統建立的圖形上下文的時候,我們只能在drawRect:方法中使用,由於這兩類圖形上下文是由我們手動建立的因此可以放到任何方法中呼叫。此外,這兩個方法開啟的圖形上下文並沒有返回值,如果我們要得到我們建立的圖形上下文只要在建立上下文之後、關閉之前呼叫UIGraphicsGetCurrentContext()方法,此時取得的上下文即是我們自己建立的圖形上下文。

繪製到點陣圖

下面利用點陣圖圖形上下文給一個圖片新增水印,在下面的程式中我們首先建立上下文,然後在上下文中繪製圖片、直線和文字,最後從當前點陣圖上下文中取得最終形成的新圖片顯示到介面。

 

執行效果:

ImageContextEffect

注意:上面這種方式繪製的影像除了可以顯示在介面上還可以呼叫對應方法進行儲存(程式碼註釋中已經包含儲存方法);除此之外這種方法相比在drawRect:方法中繪製圖形效率更高,它不用每次展示時都呼叫所有圖形繪製方法。

繪製到PDF

繪製到PDF則要啟用pdf圖形上下文,PDF圖形上下文的建立使用方式跟點陣圖圖形上下文是類似的,需要注意的一點就是繪製內容到PDF時需要建立分頁,每頁內容的開始都要呼叫一次IGraphicsBeginPDFPage();方法。下面的示例演示了文字繪製和圖片繪製(其他圖形繪製也是類似的):

 

生成的pdf文件:

DrawToPDF

 

知識補充

1.Core Graphics是基於C語言的一套框架,開發時無法像使用Obj-C一樣呼叫;

2.在Quartz 2D中凡是使用帶有“Create”或者“Copy”關鍵字方法建立的物件,在使用後一定要使用對應的方法釋放(由於這個框架基於C語言編寫無法自動釋放記憶體);

3.Quartz 2D是跨平臺的,因此其中的方法中不能使用UIKit中的物件(UIKit只有iOS可用),例如用到的顏色只能用CGColorRef而不能用UIColor,但是UIKit中提供了對應的轉換方法;

4.在C語言中列舉一般以“k”開頭,由於Quartz 2D基於C語言開發,所以它也不例外(引數中很多列舉都是k開頭的);

5.由於Quartz 2D是Core Graphics的一部分,所以API多數以CG開頭;

6.在使用Quartz 2D繪圖API中所有以“Ref”結尾物件,在宣告時都不必宣告為指標型別;

7.在使用Quartz 2D繪圖API時,凡是“UI”開頭的相關繪圖函式,都是UIKit對Core Graphics的封裝(主要為了簡化繪圖操作);

 

Core Image

利用Quartz 2D我們可以繪製各類圖形、影像,功能確實強大。用過photoshop的朋友都知道,使用photoshop可以製作各種濾鏡特效,那麼在iOS中能否實現濾鏡呢?在iOS5.0之前這些演算法基本全部要靠程式設計師程式設計實現,實現過程相當複雜。從iOS5.0開始蘋果官方已經提供了Core Image框架來幫助開發者進行特效製作。

先來看一下濾鏡使用過程中常用的基類物件:

CIContext:影像上下文,用於管理整個圖片處理過程,不同的圖形上下文將利用不同的影像處理硬體進行影像處理(在iOS中可以通過不同的方式建立影像上下文,例如可以建立基於CPU的影像上下方、建立基於GPU的影像上下方以及建立OpenGL優化過的影像上下文)。

CIFilter:影像處理濾鏡,每種濾鏡有不同的引數設定。

CIImage:Core Image框架中的影像型別,主要用於輸入和輸出影像。

在使用濾鏡之前我們先要弄清平臺主要支援哪些濾鏡,以及這些濾鏡的方法和引數如何設定,此時不妨使用下面的方法進行列印檢視:

 

在iOS7中列印會發現有127中濾鏡,如果我們把每種濾鏡都介紹一遍恐怕用幾章內容也很難介紹詳細,事實上也沒有這個必要。這些濾鏡使用方法是類似的,只是引數設定有所區別。在iOS文件中可以搜尋“core image filter reference”一節的內容,裡面有每種濾鏡的詳細介紹和圖片使用效果。

使用Core Image框架建立濾鏡效果一般分為以下幾步:

1.建立影像上下文CIContext

2.建立濾鏡CIFilter

3.建立過濾原圖片CIImage

4.呼叫CIFilter的setValue: forKey:方法為濾鏡指定源圖片

5.設定濾鏡引數【可選】

6.取得輸出圖片顯示或儲存

大家都知道在美圖秀秀中有一個“增強”功能,利用它可以調整照片的飽和度、亮度、對比度,其實在Core Image中也有這樣一款濾鏡,下面就以顏色濾鏡來演示一下Core Image中濾鏡的使用。

 

執行效果:

CoreImageEffect

再次給大家強調一下:

  • 在上面的程式碼中除了使用了基於GPU的影像上下文(推薦方式),也建立了其他影像上下文,儘管已經被註釋大家還是需要熟悉。
  • Core Image允許你一次給影像或視訊幀疊加多種效果,同時Core Image還能保證強大的處理效率。
  • 和在使用Core Graphics繪圖一樣,UIKit中也封裝了一些方法直接轉換為Core Image中的物件,例如UIImage物件可以直接呼叫CIImage屬性轉換為CIImage型別。

相關文章