iOS 札記2:Core Graphics小記

導語:Core Graphics是iOS和Mac OS X的2D繪圖引擎,本文簡單介紹Core Graphics繪圖相關概念

一、概述

1、iOS的繪圖系統

iOS主要的繪圖系統有UIKit、Core Graphics、Core Animation、Core Image、OpenGL ES.結構圖如下:

iOS的繪圖系統結構.png
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圖形上下文時,圖形上下文中持有一個繪圖狀態堆棧,這時的繪圖狀態堆棧是空的,通過CGContextSaveGStateCGContextRestoreGState可以推入和彈出繪圖狀態,實現了繪圖狀態切換,但是沒有改變其圖形上下文。

  • CGContextSaveGState當前圖形上下文的之前繪圖狀態(Graphics State )壓入繪圖狀態堆棧,而CGContextRestoreGState是將繪圖狀態堆棧頂部的狀態彈出,返回到之前的圖形狀態。

  • CGContextSaveGState之前,繪圖狀態是A,在CGContextSaveGStateCGContextRestoreGState之間,可以改變繪制狀態成為B,實現不同的效果,調用了CGContextRestoreGState之后,繪圖狀態又回到了A。

    //繪圖狀態A
    CGContextSaveGState(context)
    //繪圖狀態B
    CGContextRestoreGState(context)
    //繪圖狀態A
    
  • 這種推入和彈出的方式是回到之前繪圖狀態的快速方法,避免逐個撤消所有的狀態修改;這也是將某些狀態(比如裁剪路徑)恢復到原有設置的唯一方式。

2) 上下文切換
  • 通過UIGraphicsPushContext與UIGraphicsPopContext函數可以實現圖形上下文切換

  • UIGraphicsPushContext將圖形上下文壓入圖形上下文堆棧;當需要新建一個上下文做一些繪制工作的話,使用UIGraphicsPushContext保存當前的上下文,在新的繪制圖形上下文完成繪制工作后,使用UIGraphicsPopContext恢復會之前的圖形上下文。

  • UIGraphicsPushContext之前,在繪制圖形X,在UIGraphicsPushContextUIGraphicsPopContext之間繪制圖形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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容