導語:Core Graphics是iOS和Mac OS X的2D繪圖引擎,本文簡單介紹Core Graphics繪圖相關概念
一、概述
1、iOS的繪圖系統
iOS主要的繪圖系統有UIKit、Core Graphics、Core Animation、Core Image、OpenGL ES.結構圖如下:
iOS的繪圖系統 | 主要作用 | API縮寫前綴 |
---|---|---|
UIKit | 使用頻率最高,高級別的OC圖形接口,它能夠訪問繪圖、動畫、字體、圖片等內容。 | 縮寫前綴為UI |
Core Animation | 使用頻率較高,提供了強大的2D和3D動畫 | 縮寫前綴為CA |
Core Graphics | 使用頻率中,基于C實現的iOS和Mac OS X的2D繪圖引擎, | 縮寫前綴為CG |
Core Image | 使用頻率低,圖片的濾鏡處理,比如高斯模糊、銳化等 | 縮寫前綴為CL |
OpenGL ES | 使用頻率低,OpenGL針對嵌入式設備的簡化版本,用以繪制高性能的2D和3D圖形 | 縮寫前綴為GL |
說明:圖像的濾鏡效果建議使用優秀的第三方庫 GPUImage,iOS支持兩套圖形API族:Core Graphics 和OpenGL ES,它們都是基于C的API框架。
2、視圖繪制 VS 視圖布局
視圖繪制:調用UIView中的drawRect方法。如果一個視圖調用setNeedsDisplay(或setNeedsDisplayInRect:)方法,它就被標記為重新繪制,并且會在下一次繪圖周期中調用drawRect方法重新繪制。
調用setNeedsDisplay或者setNeedsDisplayInRect:方法,只是告訴系統該視圖需要重新繪制了,真正的繪制發生在下一個繪制周期,所以可以一次性對多個視圖調用setNeedsDisplay/setNeedsDisplayInRect,等到它們在下一個周期被調用drawRect方法更新視圖。
即時不使用setNeedsDisplay/setNeedsDisplayInRect:方法,如果當遮擋你的視圖的其他視圖被移動或刪除操作的時,設置視圖的hidden屬性,改變視圖的顯示狀態,視圖滾出屏幕,然后再重新回到屏幕上。等等都會觸發視圖重新繪制。
-
視圖布局:調用UIView中的layoutSubviews方法。如果視圖中的子視圖布局發生變化,需要重新排列,UIKit會自動調用setNeedsLayout方法,也就是對于發生變化的視圖逐層次調用layoutSubviews方法。比如frame發生變化、滾動視圖等。
說明:因為繪制的工作大部分是使用CPU完成的,處理速度不如GPU, 盡量避免繪制,多使用布局。視圖在第一次創建的時候,繪制和布局的都會調用的。如果子視圖因為一些條件的改變,造成布局的改變,這個時候,系統會自動調用layoutSubviews方法。
3、UIKit相關繪圖API
-
填充、 描邊和路徑
UIRectFill(CGRect rect); //填充矩形函數 UIRectFrame(CGRect rect); //矩形描邊函數 UIBezierPath //繪制常見路徑類,但是對于線段、漸變、陰影、反鋸齒等高級特性支持還需要使用Core Graphics
-
UIImage類中繪制圖像主要的方法
- (void) drawAtPoint:(CGPoint)point; //設置繪制定點。 - (void) drawInRect:(CGRect)rect; //圖片繪制在指定的矩形里。 - (void) drawAsPatternInRect:(CGRect)rect; //在指定的矩形里平鋪繪制圖片,如果圖片大小超出了指定的矩形,形式上與-drawAtPoint:方法 // 類似,如果圖片大小小于指定的矩形,就會有平鋪的效果。
-
NSString類中繪制文本主要的方法:
- (void) drawAtPoint:(CGPoint)point withAttributes:(NSDictionary *)attrs;文本在指定點繪制; - (void) drawInRect:(CGRect)rect withAttributes:(NSDictionary *)attrs;文本在指定的矩形里繪制;
二、Core Graphics繪圖
Core Graphics中的繪圖操作都是在在一個上下文中進行,在繪圖之前必須獲取該上下文,這是繪圖任務的第一步。
1、概述
Core Graphics的圖形上下文是CGContextRef對象,相當于一塊畫布,以堆棧形式存放,只有在棧頂的圖形上下文上繪制才有效;圖形上下文負責存儲繪畫狀態(如畫筆顏色、線條粗細等)和繪制內容所處的內存空間。獲取上下文的方式有:
iOS有PDF圖形上下文、圖片處理的圖形上下文等,而通過UIGraphicsBeginImageContextWithOptions函數或在UIView的drawRect:方法中,使用 UIGraphicsGetCurrentContext函數獲取的是,當前圖片處理的圖形上下文。
Core Graphics中帶有 Ref 后綴的類,其 實例對象 可能有指向其他 Core Graphics “對象”的強引用指針,但是不能被ARC管理,所以創建了這些對象,使用完之后記得手動釋放,否則會有內存泄漏的問題。
內存管理規則:凡使用名稱中帶有create 或者 copy 的函數創建了一個 Core Graphics “對象”,就必須調用對應的Release函數并傳入該對象的指針,將其釋放。
2、UIKit封裝的圖形上下文操作函數
1)UIGraphicsBeginImageContextWithOptions(CGSize size, BOOL opaque, CGFloat scale)
功能:將舊的上下文入棧、為新上下文分配內存、創建新的上下文、翻轉坐標系統,并將其設置為當前上下文。(簡記:創建了一個圖形處理的上下文,并將其設置為當前上下文)
參數說明:size是新創建的位圖的尺寸。opaque代表透明通道的開關,指定所生成圖片的背景是否為不透明,YES表示不透明,圖片背景是黑色,NO表示透明;scale表明縮放比例,傳入0表示讓圖片的縮放因子等于屏幕的分辨率。
2)UIGraphicsBeginImageContext(CGSize size)
- 創建一個基于位圖的上下文,并將其設置為當前上下文。效果相當于UIGraphicsBeginImageContextWithOptions(size,NO,1.0)
3)UIGraphicsGetImageFromCurrentImageContext(void)
- 從當前上下文中獲取一個UIImage對象,一般用于獲取最終繪制結果。
4)UIGraphicsEndImageContext(void)
- 關閉圖形上下文,一般在調用UIGraphicsGetImageFromCurrentImageContext之后調用。
5)UIGraphicsGetCurrentContext(void)
- 獲得當前的圖形上下文,可以在UIGraphicsBeginImageContextWithOptions/UIGraphicsBeginImageContext或在UIView的drawRect:使用,以獲取當前的圖形上下文。
3、繪圖狀態切換 和 上下文切換
繪圖中,有繪圖狀態切換 和 上下文切換,對應兩組不同函數,要注意區分使用。
1) 繪圖狀態切換
在創建Core Graphics圖形上下文時,圖形上下文中持有一個繪圖狀態堆棧,這時的繪圖狀態堆棧是空的,通過CGContextSaveGState與CGContextRestoreGState可以推入和彈出繪圖狀態,實現了繪圖狀態切換,但是沒有改變其圖形上下文。
CGContextSaveGState將當前圖形上下文的之前繪圖狀態(Graphics State )壓入繪圖狀態堆棧,而CGContextRestoreGState是將繪圖狀態堆棧頂部的狀態彈出,返回到之前的圖形狀態。
-
在CGContextSaveGState之前,繪圖狀態是A,在CGContextSaveGState和CGContextRestoreGState之間,可以改變繪制狀態成為B,實現不同的效果,調用了CGContextRestoreGState之后,繪圖狀態又回到了A。
//繪圖狀態A CGContextSaveGState(context) //繪圖狀態B CGContextRestoreGState(context) //繪圖狀態A
這種推入和彈出的方式是回到之前繪圖狀態的快速方法,避免逐個撤消所有的狀態修改;這也是將某些狀態(比如裁剪路徑)恢復到原有設置的唯一方式。
2) 上下文切換
通過UIGraphicsPushContext與UIGraphicsPopContext函數可以實現圖形上下文切換
UIGraphicsPushContext將圖形上下文壓入圖形上下文堆棧;當需要新建一個上下文做一些繪制工作的話,使用UIGraphicsPushContext保存當前的上下文,在新的繪制圖形上下文完成繪制工作后,使用UIGraphicsPopContext恢復會之前的圖形上下文。
-
在UIGraphicsPushContext之前,在繪制圖形X,在UIGraphicsPushContext和UIGraphicsPopContext之間繪制圖形Y,在UIGraphicsPopContext之后,又回到了繪制圖像X。
//繪制圖形X UIGraphicsPushContext(context) //繪制圖形Y UIGraphicsPopContext(context) //繪制圖形X
說明:這種推入和彈出的方式是切換上下文比較好的方式。
三、Core Graphics在項目中的使用場景
在項目中,為了滿足設計需要,利用Core Graphics完成一些繪制工作,如繪制圓角(Round Corner)、高斯模糊(Gaussian Blur) 和 陰影(Shadow)。這些繪制工作要考慮性能和內存消耗,盡可能將繪制內容緩存下來,避免重復繪制。
1、圓角(Round Corner)
在硬件產品和 軟件用戶界面的設計上,圓角被大量采用;一是因為圓角在視覺過程中更易被認知,二是因為圓角也同時使得信息更易被處理,總之,大家認為“圓角看起來很漂亮”。
在項目中常見圓角圖片。雖然iOS 9之后,對使用加載png圖片做了優化,不會觸發離屏渲染 ,但網絡圖片未必都是png格式,使用Core Graphics來處理圓角,是個不錯的選擇。
關于圓角處理,參考QSImageProcess項目,具體介紹可見iOS實錄17:網絡圖片的優化顯示
2、高斯模糊(Gaussian Blur)
模糊(Blur)可以理解成每一個像素都取周邊像素的平均值;如果簡單地平均,顯然不很合理,因為圖像都是連續的,越靠近的點關系越密切,越遠離的點關系越疏遠。理想的想法是,利用加權平均,距離越近的點權重越大,距離越遠的點權重越小。
高斯模糊算法其實是利用高斯分布(正態分布)來處理周邊像素權重的問題;高斯分布是一種鐘形曲線,越接近中心,取值越大,越遠離中心,取值越小。在計算平均值的時候,我們只需要將"中心點"作為原點,其他點按照其在正態曲線上的位置,分配權重,就可以得到一個加權平均值。
模糊半徑越大,圖像就越模糊。從數值角度看,就是數值越平滑。
雖然利用UIVisualEffectView和UIToolBar可以實現模糊效果,但是不夠靈活。如果設計需要更加復雜的效果,使用Core Graphics處理也是不錯的選擇。
3、陰影(Shadow)
大多數陰影效果設置,利用shadow****屬性都可以完成。除非設計有特別的陰影需求,才使用Core Graphics(謹記)。
-
網上很多博客中說:通過shadow****設置陰影,會觸發離屏渲染,這句話并不嚴謹。只有在不設置shadowPath(陰影路徑)的情況下,設置陰影效果(如shadowColor、shadowOpacity、shadowOffset和shadowRadius等),才會觸發。如下代碼是不會觸發離屏渲染的,
imageView.layer.shadowOpacity = 0.8; //陰影透明度 imageView.layer.shadowOffset = CGSizeMake(2, 2); ;// 陰影偏移量 imageView.layer.shadowRadius = 10; //陰影的圓角 imageView.layer.shadowColor = [UIColor grayColor].CGColor; // 陰影的顏色 UIBezierPath *path = [UIBezierPath bezierPathWithRect:imageView.bounds]; imageView.layer.shadowPath = path.CGPath; //陰影的路徑(避開離屏渲染的關鍵)
說明1:設置了shadowPath屬性,圖層在創建陰影的時候,不需要遍歷sublayer的alpha通道,而是直接使用指定的路徑作為陰影的輪廓,不發生離屏渲染。反之,CoreAnimation需要在每一幀繪制陰影的時候,遍歷所有sublayer的alpha通道,以精確的計算出陰影的輪廓,發生離屏渲染,消耗了性能。
說明2:設置shadowPath屬性后,當圖層的bounds產生變化后,需要更新shadowPath才能讓其適配新的bounds。
End
參考資料
iOS繪圖系統(一) UIKit與CoreGraphics
iOS繪圖框架CoreGraphics分析
CoreGraphics之CGContextSaveGState與UIGraphicsPushContext我是南華coder,一名北漂的初級iOS程序猿。iOS札記是我的一點學習筆記,不足之處,望批評指正。