1. 程式人生 > >iOS繪圖教程

iOS繪圖教程

Core Graphics Framework是一套基於C的API框架,使用了Quartz作為繪圖引擎。它提供了低級別、輕量級、高保真度的2D渲染。該框架可以用於基於路徑的繪圖、變換、顏色管理、脫屏渲染,模板、漸變、

本文是《Programming iOS5》中Drawing一章的翻譯,考慮到主題完整性,翻譯版本中加入了一些書中未涉及到的內容。希望本文能夠對你有所幫助。

  本文由海水的味道翻譯整理,轉載請註明譯者和出處,請勿用於商業用途!

      Core Graphics Framework是一套基於C的API框架,使用了Quartz作為繪圖引擎。它提供了低級別、輕量級、高保真度的2D渲染。該框架可以用於基於路徑的繪圖、變換、顏色管理、脫屏渲染,模板、漸變、遮蔽、影象資料管理、影象的建立、遮罩以及PDF文件的建立、顯示和分析。為了從感官上對這些概念做一個入門的認識,你可以執行一下官方的

example code

     iOS支援兩套圖形API族:Core Graphics/QuartZ 2D 和OpenGL ES。OpenGL ES是跨平臺的圖形API,屬於OpenGL的一個簡化版本。QuartZ 2D是蘋果公司開發的一套API,它是Core Graphics Framework的一部分。需要注意的是:OpenGL ES是應用程式程式設計介面,該介面描述了方法、結構、函式應具有的行為以及應該如何被使用的語義。也就是說它只定義了一套規範,具體的實現由裝置製造商根據規範去做。而往往很多人對介面和實現存在誤解。舉一個不恰當的比喻:上發條的時鐘和裝電池的時鐘都有相同的可視行為,但兩者的內部實現

截然不同。因為製造商可以自由的實現Open GL ES,所以不同系統實現的OpenGL ES也存在著巨大的效能差異。

       Core Graphics API所有的操作都在上下文中進行。所以在繪圖之前需要獲取該上下文並傳入執行渲染的函式內。如果你正在渲染一副在記憶體中的圖片,此時就需要傳入圖片所屬的上下文。獲得一個圖形上下文是我們完成繪圖任務的第一步,你可以將圖形上下文理解為一塊畫布。如果你沒有得到這塊畫布,那麼你就無法完成任何繪圖操作。有許多方式獲得一個圖形上下文,這裡我介紹兩種最為常用的獲取方法。

  第一種方法就是建立一個圖片型別的上下文。呼叫UIGraphicsBeginImageContextWithOptions

函式就可獲得用來處理圖片的圖形上下文。利用該上下文,你就可以在其上進行繪圖,並生成圖片。呼叫UIGraphicsGetImageFromCurrentImageContext函式可從當前上下文中獲取一個UIImage物件。記住在你所有的繪圖操作後別忘了呼叫UIGraphicsEndImageContext函式關閉圖形上下文。

  第二種方法是利用cocoa為你生成的圖形上下文。當你子類化了一個UIView並實現了自己的drawRect:方法後,一旦drawRect:方法被呼叫,Cocoa就會為你建立一個圖形上下文,此時你對圖形上下文的所有繪圖操作都會顯示在UIView上。

  判斷一個上下文是否為當前圖形上下文需要注意的幾點:

  • UIGraphicsBeginImageContextWithOptions函式不僅僅是建立了一個適用於圖形操作的上下文,並且該上下文也屬於當前上下文。
  • drawRect方法被呼叫時,UIView的繪圖上下文屬於當前圖形上下文。
  • 回撥方法所持有的context:引數並不會讓任何上下文成為當前圖形上下文。此引數僅僅是對一個圖形上下文的引用罷了。

  作為初學者,很容易被UIKit和Core Graphics兩個支援繪圖的框架迷惑。

  UIKit

  像UIImage、NSString(繪製文字)、UIBezierPath(繪製形狀)、UIColor都知道如何繪製自己。這些類提供了功能有限但使用方便的方法來讓我們完成繪圖任務。一般情況下,UIKit就是我們所需要的。

  使用UiKit,你只能在當前上下文中繪圖,所以如果你當前處於UIGraphicsBeginImageContextWithOptions函式或drawRect:方法中,你就可以直接使用UIKit提供的方法進行繪圖。如果你持有一個context:引數,那麼使用UIKit提供的方法之前,必須將該上下文引數轉化為當前上下文。幸運的是,呼叫UIGraphicsPushContext函式可以方便的將context:引數轉化為當前上下文,記住最後別忘了呼叫UIGraphicsPopContext函式恢復上下文環境。

  Core Graphics

  這是一個繪圖專用的API族,它經常被稱為QuartZ或QuartZ 2D。Core Graphics是iOS上所有繪圖功能的基石,包括UIKit。

  使用Core Graphics之前需要指定一個用於繪圖的圖形上下文(CGContextRef),這個圖形上下文會在每個繪圖函式中都會被用到。如果你持有一個圖形上下文context:引數,那麼你等同於有了一個圖形上下文,這個上下文也許就是你需要用來繪圖的那個。如果你當前處於UIGraphicsBeginImageContextWithOptions函式drawRect:方法中,並沒有引用一個上下文。為了使用Core Graphics,你可以呼叫UIGraphicsGetCurrentContext函式獲得當前的圖形上下文。

  至此,我們有了兩大繪圖框架的支援以及三種獲得圖形上下文的方法(drawRect:、drawRect: inContext:、UIGraphicsBeginImageContextWithOptions)。那麼我們就有6種繪圖的形式。如果你有些困惑了,不用怕,我接下來將說明這6種情況。無需擔心還沒有具體的繪圖命令,你只需關注上下文如何被建立以及我們是在使用UIKit還是Core Graphics。

  第一種繪圖形式:在UIView的子類方法drawRect:中繪製一個藍色圓,使用UIKit在Cocoa為我們提供的當前上下文中完成繪圖任務。

- (void) drawRect: (CGRect) rect {

UIBezierPath* p = [UIBezierPathbezierPathWithOvalInRect:CGRectMake(0,0,100,100)];

[[UIColor blueColor] setFill];

[p fill];

}

    第二種繪圖形式:使用Core Graphics實現繪製藍色圓。

- (void) drawRect: (CGRect) rect {

CGContextRef con = UIGraphicsGetCurrentContext();

CGContextAddEllipseInRect(con, CGRectMake(0,0,100,100));

CGContextSetFillColorWithColor(con, [UIColor blueColor].CGColor);

CGContextFillPath(con);

}

       第三種繪圖形式:我將在UIView子類的drawLayer:inContext:方法中實現繪圖任務。drawLayer:inContext:方法是一個繪製圖層內容的代理方法。為了能夠呼叫drawLayer:inContext:方法,我們需要設定圖層的代理物件。但要注意,不應該將UIView物件設定為顯示層的委託物件,這是因為UIView物件已經是隱式層的代理物件,再將它設定為另一個層的委託物件就會出問題。輕量級的做法是:編寫負責繪圖形的代理類。在MyView.h檔案中宣告如下程式碼:

@interface MyLayerDelegate : NSObject

@end

        然後MyView.m檔案中實現介面程式碼:

@implementation MyLayerDelegate

- (void)drawLayer:(CALayer*)layer inContext:(CGContextRef)ctx {

  UIGraphicsPushContext(ctx);

  UIBezierPath* p = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0,0,100,100)];

  [[UIColor blueColor] setFill];

  [p fill];

  UIGraphicsPopContext();

}

@end

直接將代理類的實現程式碼放在MyView.m檔案的#import程式碼的下面,這樣感覺好像在使用私有類完成繪圖任務(雖然這不是私有類)。需要注意的是,我們所引用的上下文並不是當前上下文,所以為了能夠使用UIKit,我們需要將引用的上下文轉變成當前上下文。

因為圖層的代理是assign記憶體管理策略,那麼這裡就不能以區域性變數的形式建立MyLayerDelegate例項物件賦值給圖層代理。這裡選擇在MyView.m中增加一個例項變數,因為例項變數預設是strong:

@interface MyView () {

MyLayerDelegate* _layerDeleagete;

}

@end

    使用該圖層代理:

MyView *myView = [[MyView alloc] initWithFrame: CGRectMake(0, 0, 320, 480)];

CALayer *myLayer = [CALayer layer];

_layerDelegate = [[MyLayerDelegate alloc] init];

myLayer.delegate = _layerDelegate;

[myView.layer addSublayer:myLayer];

[myView setNeedsDisplay]; //呼叫此方法,drawLayer: inContext:方法才會被呼叫。

 第四種繪圖形式: 使用Core Graphics在drawLayer:inContext:方法中實現同樣操作,程式碼如下:

- (void)drawLayer:(CALayer*)lay inContext:(CGContextRef)con {

CGContextAddEllipseInRect(con, CGRectMake(0,0,100,100));

CGContextSetFillColorWithColor(con, [UIColor blueColor].CGColor);

CGContextFillPath(con);

}

  最後,演示UIGraphicsBeginImageContextWithOptions的用法,並從上下文中生成一個UIImage物件。生成UIImage物件的程式碼可以在任何地方被使用,它沒有上述繪圖方法那樣的限制。

  第五種繪圖形式: 使用UIKit實現:

UIGraphicsBeginImageContextWithOptions(CGSizeMake(100,100), NO, 0);

UIBezierPath* p = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0,0,100,100)];

[[UIColor blueColor] setFill];

[p fill];

UIImage* im = UIGraphicsGetImageFromCurrentImageContext();

UIGraphicsEndImageContext();

  解釋一下UIGraphicsBeginImageContextWithOptions函式引數的含義:第一個引數表示所要建立的圖片的尺寸;第二個引數用來指定所生成圖片的背景是否為不透明,如上我們使用YES而不是NO,則我們得到的圖片背景將會是黑色,顯然這不是我想要的;第三個引數指定生成圖片的縮放因子,這個縮放因子與UIImage的scale屬性所指的含義是一致的。傳入0則表示讓圖片的縮放因子根據螢幕的解析度而變化,所以我們得到的圖片不管是在單解析度還是視網膜屏上看起來都會很好。

   第六種繪圖形式: 使用Core Graphics實現:

UIGraphicsBeginImageContextWithOptions(CGSizeMake(100,100), NO, 0);

CGContextRef con = UIGraphicsGetCurrentContext();

CGContextAddEllipseInRect(con, CGRectMake(0,0,100,100));

CGContextSetFillColorWithColor(con, [UIColor blueColor].CGColor);

CGContextFillPath(con);

UIImage* im = UIGraphicsGetImageFromCurrentImageContext();

UIGraphicsEndImageContext();

  UIKit和Core Graphics可以在相同的圖形上下文中混合使用。在iOS 4.0之前,使用UIKit和UIGraphicsGetCurrentContext被認為是執行緒不安全的。而在iOS4.0以後蘋果讓繪圖操作在第二個執行緒中執行解決了此問題。

  UIImage常用的繪圖操作

  一個UIImage物件提供了向當前上下文繪製自身的方法。我們現在已經知道如何獲取一個圖片型別的上下文並將它轉變成當前上下文。

  平移操作:下面的程式碼展示瞭如何將UIImage繪製在當前的上下文中。

UIImage* mars = [UIImage imageNamed:@"Mars.png"];

CGSize sz = [mars size];

UIGraphicsBeginImageContextWithOptions(CGSizeMake(sz.width*2, sz.height), NO, 0);

[mars drawAtPoint:CGPointMake(0,0)];

[mars drawAtPoint:CGPointMake(sz.width,0)];

UIImage* im = UIGraphicsGetImageFromCurrentImageContext();

UIGraphicsEndImageContext();

UIImageView* iv = [[UIImageView alloc] initWithImage:im];

[self.window.rootViewController.view addSubview: iv];

    iv.center = self.window.center;

圖1 UIImage平移處理

  縮放操作:下面程式碼展示瞭如何對UIImage進行縮放操作:

UIImage* mars = [UIImage imageNamed:@"Mars.png"];

CGSize sz = [mars size];

UIGraphicsBeginImageContextWithOptions(CGSizeMake(sz.width*2, sz.height*2), NO, 0);

[mars drawInRect:CGRectMake(0,0,sz.width*2,sz.height*2)];

[mars drawInRect:CGRectMake(sz.width/2.0, sz.height/2.0, sz.width, sz.height) blendMode:kCGBlendModeMultiply alpha:1.0];

UIImage* im = UIGraphicsGetImageFromCurrentImageContext();

UIGraphicsEndImageContext();

圖2 UIImage縮放處理

  UIImage沒有提供擷取圖片指定區域的功能。但通過建立一個較小的圖形上下文並移動圖片到一個適當的圖形上下文座標系內,指定區域內的圖片就會被獲取。

  裁剪操作:下面程式碼展示瞭如何獲取圖片的右半邊:

UIImage* mars = [UIImage imageNamed:@"Mars.png"];

CGSize sz = [mars size];

UIGraphicsBeginImageContextWithOptions(CGSizeMake(sz.width/2.0, sz.height), NO, 0);

[mars drawAtPoint:CGPointMake(-sz.width/2.0, 0)];

UIImage* im = UIGraphicsGetImageFromCurrentImageContext();

UIGraphicsEndImageContext();

  以上的程式碼首先建立一個一半圖片寬度的圖形上下文,然後將圖片左上角原點移動到與圖形上下文負X座標對齊,從而讓圖片只有右半部分與圖形上下文相交。

圖3 UIImage裁剪原理

  CGImage常用的繪圖操作

  UIImage的Core Graphics版本是CGImage(具體型別是CGImageRef)。兩者可以直接相互轉化: 使用UIImage的CGImage屬性可以訪問Quartz圖片資料;將CGImage作為UIImage方法imageWithCGImage:initWithCGImage:的引數建立UIImage物件。

  一個CGImage物件可以讓你獲取原始圖片中指定區域的圖片(也可以獲取指定區域外的圖片,UIImage卻辦不到)。

  下面的程式碼展示了將圖片拆分成兩半,並分別繪製在上下文的左右兩邊:

UIImage* mars = [UIImage imageNamed:@"Mars.png"];

//抽取圖片的左右半邊

CGSize sz = [mars size];

CGImageRef marsLeft = CGImageCreateWithImageInRect([mars CGImage],CGRectMake(0,0,sz.width/2.0,sz.height));

CGImageRef marsRight = CGImageCreateWithImageInRect([mars CGImage],CGRectMake(sz.width/2.0,0,sz.width/2.0,sz.height));

//將每一個CGImage繪製到圖形上下文中

UIGraphicsBeginImageContextWithOptions(CGSizeMake(sz.width*1.5, sz.height), NO, 0);

CGContextRef con = UIGraphicsGetCurrentContext();

CGContextDrawImage(con, CGRectMake(0,0,sz.width/2.0,sz.height), marsLeft);

CGContextDrawImage(con, CGRectMake(sz.width,0,sz.width/2.0,sz.height), marsRight);

UIImage* im = UIGraphicsGetImageFromCurrentImageContext();

UIGraphicsEndImageContext();

//記得釋放記憶體,ARC在這裡無效

CGImageRelease(marsLeft);

CGImageRelease(marsRight);

  你也許發現繪出的圖是上下顛倒的!圖片的顛倒並不是因為被旋轉了。當你建立了一個CGImage並使用CGContextDrawImage方法繪圖就會引起這種問題。這主要是因為原始的本地座標系統(座標原點在左上角)與目標上下文(座標原點在左下角)不匹配。有很多方法可以修復這個問題,其中一種方法就是使用CGContextDrawImage方法先將CGImage繪製到UIImage上,然後獲取UIImage對應的CGImage,此時就得到了一個倒轉的CGImage。當再呼叫CGContextDrawImage方法,我們就將倒轉的圖片還原回來了。實現程式碼如下:

CGImageRef flip (CGImageRef im) {

CGSize sz = CGSizeMake(CGImageGetWidth(im), CGImageGetHeight(im));

UIGraphicsBeginImageContextWithOptions(sz, NO, 0);

CGContextDrawImage(UIGraphicsGetCurrentContext(), CGRectMake(0, 0, sz.width, sz.height), im);

CGImageRef result = [UIGraphicsGetImageFromCurrentImageContext() CGImage];

UIGraphicsEndImageContext();

return result;

}

  現在將之前的程式碼修改如下:

CGContextDrawImage(con, CGRectMake(0,0,sz.width/2.0,sz.height), flip(marsLeft));

CGContextDrawImage(con, CGRectMake(sz.width,0,sz.width/2.0,sz.height), flip(marsRight));

  然而,這裡又出現了另外一個問題:在雙解析度的裝置上,如果我們的圖片檔案是高解析度(@2x)版本,上面的繪圖就是錯誤的。原因在於對於UIImage來說,在載入原始圖片時使用imageNamed:方法,它會自動根據所在裝置的解析度型別選擇圖片,並且UIImage通過設定用來適配的scale屬性補償圖片的兩倍尺寸。但是一個CGImage物件並沒有scale屬性,它不知道圖片檔案的尺寸是否為兩倍!所以當呼叫UIImage的CGImage方法,你不能假定所獲得的CGImage尺寸與原始UIImage是一樣的。在單解析度和雙解析度下,一個UIImage物件的size屬性值都是一樣的,但是雙解析度UIImage對應的CGImage是單解析度UIImage對應的CGImage的兩倍大。所以我們需要修改上面的程式碼,讓其在單雙解析度下都可以工作。程式碼如下:

UIImage* mars = [UIImage imageNamed:@"Mars.png"];

CGSize sz = [mars size];

//轉換CGImage並使用對應的CGImage尺寸擷取圖片的左右部分

CGImageRef marsCG = [mars CGImage];

CGSize szCG = CGSizeMake(CGImageGetWidth(marsCG), CGImageGetHeight(marsCG));

CGImageRef marsLeft = CGImageCreateWithImageInRect(marsCG,CGRectMake(0,0,szCG.width/2.0,szCG.height));

CGImageRef marsRight = CGImageCreateWithImageInRect(marsCG, CGRectMake(szCG.width/2.0,0,szCG.width/2.0,szCG.height));

UIGraphicsBeginImageContextWithOptions(CGSizeMake(sz.width*1.5, sz.height), NO, 0);

//剩下的和之前的程式碼一樣,修復倒置問題

CGContextRef con = UIGraphicsGetCurrentContext();

CGContextDrawImage(con, CGRectMake(0,0,sz.width/2.0,sz.height),flip(marsLeft));

CGContextDrawImage(con, CGRectMake(sz.width,0,sz.width/2.0,sz.height),flip(marsRight));

UIImage* im = UIGraphicsGetImageFromCurrentImageContext();

UIGraphicsEndImageContext();

CGImageRelease(marsLeft);

CGImageRelease(marsRight);

  上面的程式碼初看上去很繁雜,不過不用擔心,這裡還有另一種修復倒置問題的方案。相對於使用flip函式,你可以在繪圖之前將CGImage包裝進UIImage中,這樣做有兩大優點:

  • 當UIImage繪圖時它會自動修復倒置問題
  • 當你從CGImage轉化為Uimage時,可呼叫imageWithCGImage:scale:orientation:方法生成CGImage作為對縮放性的補償。

  所以這是一個解決倒置和縮放問題的自包含方法。

  程式碼如下:

UIImage* mars = [UIImage imageNamed:@"Mars.png"];

CGSize sz = [mars size];

CGImageRef marsCG = [mars CGImage];

CGSize szCG = CGSizeMake(CGImageGetWidth(marsCG), CGImageGetHeight(marsCG));

CGImageRef marsLeft = CGImageCreateWithImageInRect(marsCG, CGRectMake(0,0,szCG.width/2.0,szCG.height));

CGImageRef marsRight = CGImageCreateWithImageInRect(marsCG, CGRectMake(szCG.width/2.0,0,szCG.width/2.0,szCG.height));

UIGraphicsBeginImageContextWithOptions(CGSizeMake(sz.width*1.5, sz.height), NO, 0);

[[UIImage imageWithCGImage:marsLeft scale:[mars scale] orientation:UIImageOrientationUp] drawAtPoint:CGPointMake(0,0)];

[[UIImage imageWithCGImage:marsRightscale:[mars scale]orientation:UIImageOrientationUp]drawAtPoint:CGPointMake(sz.width,0)];

UIImage* im = UIGraphicsGetImageFromCurrentImageContext();

UIGraphicsEndImageContext();

CGImageRelease(marsLeft); CGImageRelease(marsRight); 

 還有另一種解決倒置問題的方案是在繪製CGImage之前,對上下文應用變換操作,有效地倒置上下文的內部座標系統。這裡先不做討論。

 為什麼會發生倒置問題

 究其原因是因為Core Graphics源於Mac OS X系統,在Mac OS X中,座標原點在左下方並且正y座標是朝上的,而在iOS中,原點座標是在左上方並且正y座標是朝下的。在大多數情況下,這不會出現任何問題,因為圖形上下文的座標系統是會自動調節補償的。但是建立和繪製一個CGImage物件時就會暴露出倒置問題。 

  CIFilter與CIImage

  CIFilter與CIImage是iOS 5新引入的,雖然它們已在MAX OS X系統中存在多年。字首“CI”表示Core Image,這是一種使用數學濾鏡變換圖片的技術。但是你不要去幻想iOS提供了像Photoshop軟體那樣強大的濾鏡功能。使用Core Image之前你需要將CoreImage.framework框架匯入到你的target之中。

  所謂濾鏡指的是CIFilter類,濾鏡可被分為以下幾類:

  模板與漸變類

  這兩類濾鏡建立的CIImage可以和其他的CIImage進行合併,比如一種單色,一個棋盤,條紋,亦或是漸變。

  合成類 

  此類濾鏡可以將一張圖片與另外的圖片合併,合成濾鏡模式常見於圖形處理軟體Photoshop中。

  色彩類

  此濾鏡調整、修改圖片的色彩。因此你可以改變一張圖片的飽和度、色度、亮度、對比度、伽馬、白點、曝光度、陰影、高亮等屬性。

  幾何變換類

  此類濾鏡可對圖片執行基本的幾何變換,比如縮放、旋轉、裁剪。

    CIFilter使用起來非常的簡單。CIFilter看上去就像一個由鍵值組成的字典。它生成一個CIImage物件作為其輸出。一般地,一個濾鏡有一個或多個輸入,而對於部分濾鏡,生成的圖片是基於其他型別的引數值。CIFilter物件是一個集合,可使用鍵值對進行檢索。通過提供濾鏡的字串名稱建立一個濾鏡,如果想知道有哪些濾鏡,可以查詢蘋果的Core Image Filter Reference文件,或是呼叫CIFilter的類方法filterNamesInCategories:,引數值為nil。每一個濾鏡擁有一小部分用來確定其行為的鍵值。如果你想修改某一個鍵(比如亮度鍵)對應的值,你可以呼叫setValue:forKey:方法或當你指定一個濾鏡名時提供所有鍵值對。

     需要處理的圖片必須是CIImage型別,呼叫initWithCGImage:方法可獲得CIImage。因為CGImage又是作為濾鏡的輸出,因此濾鏡之間可被連線在一起(將濾鏡的輸出作為initWithCGImage:方法的輸入引數)

   當你構建一個濾鏡鏈時,並沒有做複雜的運算。只有當整個濾鏡鏈需要輸出一個CGImage時,密集型計算才會發生。呼叫contextWithOptions:createCGImage: fromRect:方法建立CIContext。與以往不同的地方是CIImage沒有frame與bounds屬性;只有extent屬性。你將非常頻繁的使用這個屬性作為createCGImage: fromRect:方法的第二個引數。

   接下來我將演示Core Image的使用。首先建立一個徑向漸變的濾鏡,該濾鏡是從白到黑的漸變方式,白色區域的半徑預設是100。接著將其與一張使用CIDarkenBlendMode濾鏡的圖片合成。CIDarkenBlendMode的作用是背景圖片樣本將被源圖片的黑色部分替換掉。

程式碼如下:

UIImage* moi = [UIImage imageNamed:@"Mars.jpeg"];

CIImage* moi2 = [[CIImage alloc] initWithCGImage:moi.CGImage];

CIFilter* grad = [CIFilter filterWithName:@"CIRadialGradient"];

CIVector* center = [CIVector vectorWithX:moi.size.width / 2.0 Y:moi.size.height / 2.0];

//使用setValueforKey:方法設定濾鏡屬性

[grad setValue:center forKey:@"inputCenter"];

//在指定濾鏡名時提供所有濾鏡鍵值對

CIFilter* dark = [CIFilter filterWithName:@"CIDarkenBlendMode" keysAndValues:@"inputImage", grad.outputImage, @"inputBackgroundImage", moi2, nil];

CIContext* c = [CIContext contextWithOptions:nil];

CGImageRef moi3 = [c createCGImage:dark.outputImage fromRect:moi2.extent];

UIImage* moi4 = [UIImage imageWithCGImage:moi3 scale:moi.scale orientation:moi.imageOrientation];

CGImageRelease(moi3);

  圖4 圖片合成快照 

  這個例子可能沒有什麼吸引人的地方,因為所有一切都可以使用Core Graphics完成。除了Core Image是使用GPU處理,可能有點吸引人。Core Graphics也可以做到徑向漸變並使用混合模式合成圖片。但Core Image要簡單得多,特別是當你有多個圖片輸入想重用一個濾鏡鏈時。並且Core Image的顏色調整功能比Core Graphics更加強大。對了,Core Image還能實現自動人臉識別哦!

  繪製一個UIView

  繪製一個UIVIew最靈活的方式就是由它自己完成繪製。實際上你不是繪製一個UIView,你只是子類化了UIView並賦予子類繪製自己的能力。當一個UIVIew需要執行繪圖操作的時,drawRect:方法就會被呼叫。覆蓋此方法讓你獲得繪圖操作的機會。當drawRect:方法被呼叫,當前圖形上下文也被設定為屬於檢視的圖形上下文。你可以使用Core Graphics或UIKit提供的方法將圖形畫到該上下文中。

  你不應該手動呼叫drawRect:方法!如果你想呼叫drawRect:方法更新檢視,只需傳送setNeedsDisplay方法。這將使得drawRect:方法會在下一個適當的時間呼叫。當然,不要覆蓋drawRect:方法除非你知道這樣做絕對合法。比方說,在UIImageView子類中覆蓋drawRect:方法是不合法的,你將得不到你繪製的圖形。

       在UIView子類的drawRect:方法中無需呼叫super,因為本身UIView的drawRect:方法是空的。為了提高一些繪圖效能,你可以呼叫setNeedsDisplayInRect方法重新繪製檢視的子區域,而檢視的其他部分依然保持不變。

       一般情況下,你不應該過早的進行優化。繪圖程式碼可能看上去非常的繁瑣,但它們是非常快的。並且iOS繪圖系統自身也是非常高效,它不會頻繁呼叫drawRect:方法,除非迫不得已(或呼叫了setNeedsDisplay方法)。一旦一個檢視已由自己繪製完成,那麼繪製的結果會被快取下來留待重用,而不是每次重頭再來。(蘋果公司將快取繪圖稱為檢視的點陣圖儲存回填(bitmap backing store))。你可能