UIView 上下文和layer之間的關係詳解

iOS繪圖教程(一)

 (2014-01-16 12:50:10)
  分類: ios繪圖教程
原文鏈接:http://www.cocoachina.com/applenews/devnews/2014/0115/7703.html
本文是《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上。
 
判斷一個上下文是否爲當前圖形上下文需要注意的幾點:
1.UIGraphicsBeginImageContextWithOptions函數不僅僅是創建了一個適用於圖形操作的上下文,並且該上下文也屬於當前上下文。
2.當drawRect方法被調用時,UIView的繪圖上下文屬於當前圖形上下文。
3.回調方法所持有的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爲我們提供的當前上下文中完成繪圖任務。
 

  1. - (void) drawRect: (CGRect) rect { 
  2.  
  3. UIBezierPath* p = [UIBezierPathbezierPathWithOvalInRect:CGRectMake(0,0,100,100)]; 
  4.  
  5. [[UIColor blueColor] setFill]; 
  6.  
  7. [p fill]; 
  8.  
 
第二種繪圖形式:使用Core Graphics實現繪製藍色圓。
 

  1. - (void) drawRect: (CGRect) rect { 
  2.  
  3. CGContextRef con = UIGraphicsGetCurrentContext(); 
  4.  
  5. CGContextAddEllipseInRect(con, CGRectMake(0,0,100,100)); 
  6.  
  7. CGContextSetFillColorWithColor(con, [UIColor blueColor].CGColor); 
  8.  
  9. CGContextFillPath(con); 
  10.  
 
第三種繪圖形式:我將在UIView子類的drawLayer:inContext:方法中實現繪圖任務。drawLayer:inContext:方法是一個繪製圖層內容的代理方法。爲了能夠調用drawLayer:inContext:方法,我們需要設定圖層的代理對象。但要注意,不應該將UIView對象設置爲顯示層的委託對象,這是因爲UIView對象已經是隱式層的代理對象,再將它設置爲另一個層的委託對象就會出問題。輕量級的做法是:編寫負責繪圖形的代理類。在MyView.h文件中聲明如下代碼:
 

  1. @interface MyLayerDelegate : NSObject 
  2.  
  3. @end 
 
然後MyView.m文件中實現接口代碼:
 

  1. @implementation MyLayerDelegate 
  2.  
  3. - (void)drawLayer:(CALayer*)layer inContext:(CGContextRef)ctx { 
  4.  
  5.   UIGraphicsPushContext(ctx); 
  6.  
  7.   UIBezierPath* p = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0,0,100,100)]; 
  8.  
  9.   [[UIColor blueColor] setFill]; 
  10.  
  11.   [p fill]; 
  12.  
  13.   UIGraphicsPopContext(); 
  14.  
  15.  
  16. @end 
 
直接將代理類的實現代碼放在MyView.m文件的#import代碼的下面,這樣感覺好像在使用私有類完成繪圖任務(雖然這不是私有類)。需要注意的是,我們所引用的上下文並不是當前上下文,所以爲了能夠使用UIKit,我們需要將引用的上下文轉變成當前上下文。
 
因爲圖層的代理是assign內存管理策略,那麼這裏就不能以局部變量的形式創建MyLayerDelegate實例對象賦值給圖層代理。這裏選擇在MyView.m中增加一個實例變量,因爲實例變量默認是strong:
 

  1. @interface MyView () { 
  2.  
  3. MyLayerDelegate* _layerDeleagete; 
  4.  
  5.  
  6. @end 
 
使用該圖層代理:
 

  1. MyView *myView = [[MyView alloc] initWithFrame: CGRectMake(0, 0, 320, 480)]; 
  2.  
  3. CALayer *myLayer = [CALayer layer]; 
  4.  
  5. _layerDelegate = [[MyLayerDelegate alloc] init]; 
  6.  
  7. myLayer.delegate = _layerDelegate; 
  8.  
  9. [myView.layer addSublayer:myLayer]; 
  10.  
  11. [myView setNeedsDisplay]; // 調用此方法,drawLayer: inContext:方法纔會被調用。 
 
第四種繪圖形式: 使用Core Graphics在drawLayer:inContext:方法中實現同樣操作,代碼如下:
 

  1. - (void)drawLayer:(CALayer*)lay inContext:(CGContextRef)con { 
  2.  
  3. CGContextAddEllipseInRect(con, CGRectMake(0,0,100,100)); 
  4.  
  5. CGContextSetFillColorWithColor(con, [UIColor blueColor].CGColor); 
  6.  
  7. CGContextFillPath(con); 
  8.  
 
最後,演示UIGraphicsBeginImageContextWithOptions的用法,並從上下文中生成一個UIImage對象。生成UIImage對象的代碼並不需要等待某些方法被調用後或在UIView的子類中才能去做。
 
第五種繪圖形式: 使用UIKit實現:
 

  1. UIGraphicsBeginImageContextWithOptions(CGSizeMake(100,100), NO, 0); 
  2.  
  3. UIBezierPath* p = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0,0,100,100)]; 
  4.  
  5. [[UIColor blueColor] setFill]; 
  6.  
  7. [p fill]; 
  8.  
  9. UIImage* im = UIGraphicsGetImageFromCurrentImageContext(); 
  10.  
  11. UIGraphicsEndImageContext(); 
 
解釋一下UIGraphicsBeginImageContextWithOptions函數參數的含義:第一個參數表示所要創建的圖片的尺寸;第二個參數用來指定所生成圖片的背景是否爲不透明,如上我們使用YES而不是NO,則我們得到的圖片背景將會是黑色,顯然這不是我想要的;第三個參數指定生成圖片的縮放因子,這個縮放因子與UIImage的scale屬性所指的含義是一致的。傳入0則表示讓圖片的縮放因子根據屏幕的分辨率而變化,所以我們得到的圖片不管是在單分辨率還是視網膜屏上看起來都會很好。
 
第六種繪圖形式: 使用Core Graphics實現:
 

  1. UIGraphicsBeginImageContextWithOptions(CGSizeMake(100,100), NO, 0); 
  2.  
  3. CGContextRef con = UIGraphicsGetCurrentContext(); 
  4.  
  5. CGContextAddEllipseInRect(con, CGRectMake(0,0,100,100)); 
  6.  
  7. CGContextSetFillColorWithColor(con, [UIColor blueColor].CGColor); 
  8.  
  9. CGContextFillPath(con); 
  10.  
  11. UIImage* im = UIGraphicsGetImageFromCurrentImageContext(); 
  12.  
  13. UIGraphicsEndImageContext(); 
 
 UIKit和Core Graphics可以在相同的圖形上下文中混合使用。在iOS 4.0之前,使用UIKit和UIGraphicsGetCurrentContext被認爲是線程不安全的。而在iOS4.0以後蘋果讓繪圖操作在第二個線程中執行解決了此問題。
 
UIImage常用的繪圖操作
 
一個UIImage對象提供了向當前上下文繪製自身的方法。我們現在已經知道如何獲取一個圖片類型的上下文並將它轉變成當前上下文。
 
平移操作:下面的代碼展示瞭如何將UIImage繪製在當前的上下文中。
 

  1. UIImage* mars = [UIImage imageNamed:@"Mars.png"]; 
  2.  
  3. CGSize sz = [mars size]; 
  4.  
  5. UIGraphicsBeginImageContextWithOptions(CGSizeMake(sz.width*2, sz.height), NO, 0); 
  6.  
  7. [mars drawAtPoint:CGPointMake(0,0)]; 
  8.  
  9. [mars drawAtPoint:CGPointMake(sz.width,0)]; 
  10.  
  11. UIImage* im = UIGraphicsGetImageFromCurrentImageContext(); 
  12.  
  13. UIGraphicsEndImageContext(); 
  14.  
  15. UIImageView* iv = [[UIImageView alloc] initWithImage:im]; 
  16.  
  17. [self.window.rootViewController.view addSubview: iv]; 
  18.  
  19.     iv.center = self.window.center; 

圖1 UIImage平移處理
 
縮放操作:下面代碼展示瞭如何對UIImage進行縮放操作:

  1. UIImage* mars = [UIImage imageNamed:@"Mars.png"]; 
  2.  
  3. CGSize sz = [mars size]; 
  4.  
  5. UIGraphicsBeginImageContextWithOptions(CGSizeMake(sz.width*2, sz.height*2), NO, 0); 
  6.  
  7. [mars drawInRect:CGRectMake(0,0,sz.width*2,sz.height*2)]; 
  8.  
  9. [mars drawInRect:CGRectMake(sz.width/2.0, sz.height/2.0, sz.width, sz.height) blendMode:kCGBlendModeMultiply alpha:1.0]; 
  10.  
  11. UIImage* im = UIGraphicsGetImageFromCurrentImageContext(); 
  12.  
  13. UIGraphicsEndImageContext(); 
 
 
圖2 UIImage縮放處理
 
UIImage沒有提供截取圖片指定區域的功能。但通過創建一個較小的圖形上下文並移動圖片到一個適當的圖形上下文座標系內,指定區域內的圖片就會被獲取。
 
裁剪操作:下面代碼展示瞭如何獲取圖片的右半邊:

  1. UIImage* mars = [UIImage imageNamed:@"Mars.png"]; 
  2.  
  3. CGSize sz = [mars size]; 
  4.  
  5. UIGraphicsBeginImageContextWithOptions(CGSizeMake(sz.width/2.0, sz.height), NO, 0); 
  6.  
  7. [mars drawAtPoint:CGPointMake(-sz.width/2.0, 0)]; 
  8.  
  9. UIImage* im = UIGraphicsGetImageFromCurrentImageContext(); 
  10.  
  11. UIGraphicsEndImageContext(); 
 
以上的代碼首先創建一個一半圖片寬度的圖形上下文,然後將圖片左上角原點移動到與圖形上下文負X座標對齊,從而讓圖片只有右半部分與圖形上下文相交。
 
圖3 UIImage裁剪原理
 
CGImage常用的繪圖操作
UIImage的Core Graphics版本是CGImage(具體類型是CGImageRef)。兩者可以直接相互轉化: 使用UIImage的CGImage屬性可以訪問Quartz圖片數據;將CGImage作爲UIImage方法imageWithCGImage:或initWithCGImage:的參數創建UIImage對象。
 
一個CGImage對象可以讓你獲取原始圖片中指定區域的圖片(也可以獲取指定區域外的圖片,UIImage卻辦不到)。
 
下面的代碼展示了將圖片拆分成兩半,並分別繪製在上下文的左右兩邊:

  1. UIImage* mars = [UIImage imageNamed:@"Mars.png"]; 
  2.  
  3. // 抽取圖片的左右半邊 
  4.  
  5. CGSize sz = [mars size]; 
  6.  
  7. CGImageRef marsLeft = CGImageCreateWithImageInRect([mars CGImage],CGRectMake(0,0,sz.width/2.0,sz.height)); 
  8.  
  9. CGImageRef marsRight = CGImageCreateWithImageInRect([mars CGImage],CGRectMake(sz.width/2.0,0,sz.width/2.0,sz.height)); 
  10.  
  11. // 將每一個CGImage繪製到圖形上下文中 
  12.  
  13. UIGraphicsBeginImageContextWithOptions(CGSizeMake(sz.width*1.5, sz.height), NO, 0); 
  14.  
  15. CGContextRef con = UIGraphicsGetCurrentContext(); 
  16.  
  17. CGContextDrawImage(con, CGRectMake(0,0,sz.width/2.0,sz.height), marsLeft); 
  18.  
  19. CGContextDrawImage(con, CGRectMake(sz.width,0,sz.width/2.0,sz.height), marsRight); 
  20.  
  21. UIImage* im = UIGraphicsGetImageFromCurrentImageContext(); 
  22.  
  23. UIGraphicsEndImageContext(); 
  24.  
  25. // 記得釋放內存,ARC在這裏無效 
  26.  
  27. CGImageRelease(marsLeft); 
  28.  
  29. CGImageRelease(marsRight); 
 
你也許發現繪出的圖是上下顛倒的!圖片的顛倒並不是因爲被旋轉了。當你創建了一個CGImage並使用CGContextDrawImage方法繪圖就會引起這種問題。這主要是因爲原始的本地座標系統(座標原點在左上角)與目標上下文(座標原點在左下角)不匹配。有很多方法可以修復這個問題,其中一種方法就是使用CGContextDrawImage方法先將CGImage繪製到UIImage上,然後獲取UIImage對應的CGImage,此時就得到了一個倒轉的CGImage。當再調用CGContextDrawImage方法,我們就將倒轉的圖片還原回來了。實現代碼如下:
 

  1. CGImageRef flip (CGImageRef im) { 
  2.  
  3. CGSize sz = CGSizeMake(CGImageGetWidth(im), CGImageGetHeight(im)); 
  4.  
  5. UIGraphicsBeginImageContextWithOptions(sz, NO, 0); 
  6.  
  7. CGContextDrawImage(UIGraphicsGetCurrentContext(), CGRectMake(0, 0, sz.width, sz.height), im); 
  8.  
  9. CGImageRef result = [UIGraphicsGetImageFromCurrentImageContext() CGImage]; 
  10.  
  11. UIGraphicsEndImageContext(); 
  12.  
  13. return result; 
  14.  
 
現在將之前的代碼修改如下:
 

  1. CGContextDrawImage(con, CGRectMake(0,0,sz.width/2.0,sz.height), flip(marsLeft)); 
  2.  
  3. 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的兩倍大。所以我們需要修改上面的代碼,讓其在單雙分辨率下都可以工作。代碼如下:
 

  1. UIImage* mars = [UIImage imageNamed:@"Mars.png"]; 
  2.  
  3. CGSize sz = [mars size]; 
  4.  
  5. // 轉換CGImage並使用對應的CGImage尺寸截取圖片的左右部分 
  6.  
  7. CGImageRef marsCG = [mars CGImage]; 
  8.  
  9. CGSize szCG = CGSizeMake(CGImageGetWidth(marsCG), CGImageGetHeight(marsCG)); 
  10.  
  11. CGImageRef marsLeft = CGImageCreateWithImageInRect(marsCG,CGRectMake(0,0,szCG.width/2.0,szCG.height)); 
  12.  
  13. CGImageRef marsRight = CGImageCreateWithImageInRect(marsCG, CGRectMake(szCG.width/2.0,0,szCG.width/2.0,szCG.height)); 
  14.  
  15. UIGraphicsBeginImageContextWithOptions(CGSizeMake(sz.width*1.5, sz.height), NO, 0); 
  16.  
  17. //剩下的和之前的代碼一樣,修復倒置問題 
  18.  
  19. CGContextRef con = UIGraphicsGetCurrentContext(); 
  20.  
  21. CGContextDrawImage(con, CGRectMake(0,0,sz.width/2.0,sz.height),flip(marsLeft)); 
  22.  
  23. CGContextDrawImage(con, CGRectMake(sz.width,0,sz.width/2.0,sz.height),flip(marsRight)); 
  24.  
  25. UIImage* im = UIGraphicsGetImageFromCurrentImageContext(); 
  26.  
  27. UIGraphicsEndImageContext(); 
  28.  
  29. CGImageRelease(marsLeft); 
  30.  
  31. CGImageRelease(marsRight); 
 
上面的代碼初看上去很繁雜,不過不用擔心,這裏還有另一種修復倒置問題的方案。相對於使用flip函數,你可以在繪圖之前將CGImage包裝進UIImage中,這樣做有兩大優點:
1.當UIImage繪圖時它會自動修復倒置問題
2.當你從CGImage轉化爲Uimage時,可調用imageWithCGImage:scale:orientation:方法生成CGImage作爲對縮放性的補償。
 
所以這是一個解決倒置和縮放問題的自包含方法。
 
代碼如下:

  1. UIImage* mars = [UIImage imageNamed:@"Mars.png"]; 
  2.  
  3. CGSize sz = [mars size]; 
  4.  
  5. CGImageRef marsCG = [mars CGImage]; 
  6.  
  7. CGSize szCG = CGSizeMake(CGImageGetWidth(marsCG), CGImageGetHeight(marsCG)); 
  8.  
  9. CGImageRef marsLeft = CGImageCreateWithImageInRect(marsCG, CGRectMake(0,0,szCG.width/2.0,szCG.height)); 
  10.  
  11. CGImageRef marsRight = CGImageCreateWithImageInRect(marsCG, CGRectMake(szCG.width/2.0,0,szCG.width/2.0,szCG.height)); 
  12.  
  13. UIGraphicsBeginImageContextWithOptions(CGSizeMake(sz.width*1.5, sz.height), NO, 0); 
  14.  
  15. [[UIImage imageWithCGImage:marsLeft scale:[mars scale] orientation:UIImageOrientationUp] drawAtPoint:CGPointMake(0,0)]; 
  16.  
  17. [[UIImage imageWithCGImage:marsRight scale:[mars scale] orientation:UIImageOrientationUp] drawAtPoint:CGPointMake(sz.width,0)]; 
  18.  
  19. UIImage* im = UIGraphicsGetImageFromCurrentImageContext(); 
  20.  
  21. UIGraphicsEndImageContext(); 
  22.  
  23. 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的作用是背景圖片樣本將被源圖片的黑色部分替換掉。
 
代碼如下:
 

  1. UIImage* moi = [UIImage imageNamed:@"Mars.jpeg"]; 
  2.  
  3. CIImage* moi2 = [[CIImage alloc] initWithCGImage:moi.CGImage]; 
  4.  
  5. CIFilter* grad = [CIFilter filterWithName:@"CIRadialGradient"]; 
  6.  
  7. CIVector* center = [CIVector vectorWithX:moi.size.width / 2.0 Y:moi.size.height / 2.0]; 
  8.  
  9. // 使用setValue:forKey:方法設置濾鏡屬性 
  10.  
  11. [grad setValue:center forKey:@"inputCenter"]; 
  12.  
  13. // 在指定濾鏡名時提供所有濾鏡鍵值對 
  14.  
  15. CIFilter* dark = [CIFilter filterWithName:@"CIDarkenBlendMode" keysAndValues:@"inputImage", grad.outputImage, @"inputBackgroundImage", moi2, nil]; 
  16.  
  17. CIContext* c = [CIContext contextWithOptions:nil]; 
  18.  
  19. CGImageRef moi3 = [c createCGImage:dark.outputImage fromRect:moi2.extent]; 
  20.  
  21. UIImage* moi4 = [UIImage imageWithCGImage:moi3 scale:moi.scale orientation:moi.imageOrientation]; 
  22.  
  23. 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))。你可能會發現drawRect:方法中的代碼在整個應用程序生命週期內只被調用了一次!事實上,將代碼移到drawRect:方法中是提高性能的普遍做法。這是因爲繪圖引擎直接對屏幕進行渲染相對於先是脫屏渲染然後再將像素拷貝到屏幕要來的高效。
 
當視圖的backgroundColor爲nil並且opaque屬性爲YES,視圖的背景顏色就會變成黑色。
 
發佈了34 篇原創文章 · 獲贊 9 · 訪問量 9萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章