1.繪制像素到屏幕上
軟件的組成
GPU :圖形處理單元。更多圖像的繪制
CPU:圖形高迸發計算而量身定做的處理單元。更對為繪制圖像提供計算、數據。
GPU Driver: 是直接和 GPU 交流的代碼塊。不同的GPU是不同的性能怪獸,但是驅動使他們在下一個層級上顯示的更為統一,典型的下一層級有 OpenGL/OpenGL ES.
OpenGL(Open Graphics Library): 是一個提供了 2D 和 3D 圖形渲染的 API。GPU 是一塊非常特殊的硬件,OpenGL 和 GPU 密切的工作以提高GPU的能力,并實現硬件加速渲染。對大多數人來說,OpenGL 看起來非常底層,但是當它在1992年第一次發布的時候(20多年前的事了)是第一個和圖形硬件(GPU)交流的標準化方式,這是一個重大的飛躍,程序員不再需要為每個GPU重寫他們的應用了。
不透明 VS 透明
當源紋理是完全不透明的時候,目標像素就等于源紋理。這可以省下 GPU 很大的工作量,這樣只需簡單的拷貝源紋理而不需要合成所有的像素值。但是沒有方法能告訴 GPU 紋理上的像素是透明還是不透明的。只有當你作為一名開發者知道你放什么到 CALayer 上了。這也是為什么 CALayer 有一個叫做 opaque 的屬性了。如果這個屬性為 YES,GPU 將不會做任何合成,而是簡單從這個層拷貝,不需要考慮它下方的任何東西(因為都被它遮擋住了)。這節省了 GPU 相當大的工作量。這也正是 Instruments 中 color blended layers 選項中所涉及的。(這在模擬器中的Debug菜單中也可用).它允許你看到哪一個 layers(紋理) 被標注為透明的,比如 GPU 正在為哪一個 layers 做合成。合成不透明的 layers 因為需要更少的數學計算而更廉價。
所以如果你知道你的 layer 是不透明的,最好確定設置它的 opaque 為 YES。如果你加載一個沒有 alpha 通道的圖片,并且將它顯示在 UIImageView 上,這將會自動發生。但是要記住如果一個圖片沒有 alpha 通道和一個圖片每個地方的 alpha 都是100%,這將會產生很大的不同。在后一種情況下,Core Animation 需要假定是否存在像素的 alpha 值不為100%。在 Finder 中,你可以使用 Get Info 并且檢查 More Info 部分。它將告訴你這張圖片是否擁有 alpha 通道。
像素對齊 VS 不重合在一起
到現在我們都在考慮像素完美重合在一起的 layers。當所有的像素是對齊的時候我們得到相對簡單的計算公式。每當 GPU 需要計算出屏幕上一個像素是什么顏色的時候,它只需要考慮在這個像素之上的所有 layer 中對應的單個像素,并把這些像素合并到一起。或者,如果最頂層的紋理是不透明的(即圖層樹的最底層),這時候 GPU 就可以簡單的拷貝它的像素到屏幕上。
當一個 layer 上所有的像素和屏幕上的像素完美的對應整齊,那這個 layer 就是像素對齊的。主要有兩個原因可能會造成不對齊。第一個便是滾動;當一個紋理上下滾動的時候,紋理的像素便不會和屏幕的像素排列對齊。另一個原因便是當紋理的起點不在一個像素的邊界上。
在這兩種情況下,GPU 需要再做額外的計算。它需要將源紋理上多個像素混合起來,生成一個用來合成的值。當所有的像素都是對齊的時候,GPU 只剩下很少的工作要做。
Core Animation 工具和模擬器有一個叫做 color misaligned images 的選項,當這些在你的 CALayer 實例中發生的時候,這個功能便可向你展示。
Masks
一個圖層可以有一個和它相關聯的 mask(蒙板),mask 是一個擁有 alpha 值的位圖,當像素要和它下面包含的像素合并之前都會把 mask 應用到圖層的像素上去。當你要設置一個圖層的圓角半徑時,你可以有效的在圖層上面設置一個 mask。但是也可以指定任意一個蒙板。比如,一個字母 A 形狀的 mask。最終只有在 mask 中顯示出來的(即圖層中的部分)才會被渲染出來。
圖片格式
當你在 iOS 或者 OS X 上處理圖片時,他們大多數為 JPEG 和 PNG。讓我們更進一步觀察。
JPEG
每個人都知道 JPEG。他是相機的產物。它代表這照片如何存儲在電腦上。甚至你嘛嘛都聽說過 JPEG。
一個很好的理由,很多人都認為 JPEG 文件僅是另一種像素數據的格式,就像我們剛剛談到的 RGB 像素布局那樣。這樣理解離真像真是差十萬八千里了。
將 JPEG 數據轉換成像素數據是一個非常復雜的過程,你通過一個周末的計劃都不能完成,甚至是一個非常漫長的周末(原文的意思好像就是為了表達這個過程非常復雜,不過老外的比喻總讓人拎不清)。對于每一個二維顏色,JPEG 使用一種基于離散余弦變換(簡稱 DCT 變換)的算法,將空間信息轉變到頻域.這個信息然后被量子化,排好序,并且用一種哈夫曼編碼的變種來壓縮。很多時候,首先數據會被從 RGB 轉換到二維 YCbCr,當解碼 JPEG 的時候,這一切都將變得可逆。
這也是為什么當你通過 JPEG 文件創建一個 UIImage 并且繪制到屏幕上時,將會有一個延時,因為 CPU 這時候忙于解壓這個 JPEG。如果你需要為每一個 tableviewcell 解壓 JPEG,那么你的滾動當然不會平滑(原來 tableviewcell 里面最要不要用 JPEG 的圖片)。
那究竟為什么我們還要用 JPEG 呢?答案就是 JPEG 可以非常非常好的壓縮圖片。一個通過 iPhone5 拍攝的,未經壓縮的圖片占用接近 24M。但是通過默認壓縮設置,你的照片通常只會在 2-3M 左右。JPEG 壓縮這么好是因為它是失真的,它去除了人眼很難察覺的信息,并且這樣做可以超出像 gzip 這樣壓縮算法的限制。但這僅僅在圖片上有效的,因為 JPEG 依賴于圖片上有很多人類不能察覺出的數據。如果你從一個基本顯示文本的網頁上截取一張圖,JPEG 將不會這么高效。壓縮效率將會變得低下,你甚至能看出來圖片已經壓縮變形了。
PNG
PNG讀作”ping”。和 JPEG 相反,它的壓縮對格式是無損的。當你將一張圖片保存為 PNG,并且打開它(或解壓),所有的像素數據會和最初一模一樣,因為這個限制,PNG 不能像 JPEG 一樣壓縮圖片,但是對于像程序中的原圖(如buttons,icons),它工作的非常好。更重要的是,解碼 PNG 數據比解碼 JPEG 簡單的多。
在現實世界中,事情從來沒有那么簡單,目前存在了大量不同的 PNG 格式。可以通過維基百科查看詳情。但是簡言之,PNG 支持壓縮帶或不帶 alpha 通道的顏色像素(RGB),這也是為什么它在程序原圖中表現良好的另一個原因。
With –drawRect:
如果你的視圖類實現了 -drawRect:,他們將像這樣工作:
當你調用 -setNeedsDisplay,UIKit 將會在這個視圖的圖層上調用 -setNeedsDisplay。這為圖層設置了一個標識,標記為 dirty(直譯是臟的意思,想不出用什么詞比較貼切,污染?),但還顯示原來的內容。它實際上沒做任何工作,所以多次調用 -setNeedsDisplay并不會造成性能損失。
下面,當渲染系統準備好,它會調用視圖圖層的-display方法.此時,圖層會裝配它的后備存儲。然后建立一個 Core Graphics 上下文(CGContextRef),將后備存儲對應內存中的數據恢復出來,繪圖會進入對應的內存區域,并使用 CGContextRef 繪制。
當你使用 UIKit 的繪制方法,例如: UIRectFill() 或者 -[UIBezierPath fill] 代替你的 -drawRect: 方法,他們將會使用這個上下文。使用方法是,UIKit 將后備存儲的 CGContextRef 推進他的 graphics context stack,也就是說,它會將那個上下文設置為當前的。因此 UIGraphicsGetCurrent() 將會返回那個對應的上下文。既然 UIKit 使用 UIGraphicsGetCurrent() 繪制方法,繪圖將會進入到圖層的后備存儲。如果你想直接使用 Core Graphics 方法,你可以自己調用 UIGraphicsGetCurrent() 得到相同的上下文,并且將這個上下文傳給 Core Graphics 方法。
從現在開始,圖層的后備存儲將會被不斷的渲染到屏幕上。直到下次再次調用視圖的 -setNeedsDisplay ,將會依次將圖層的后備存儲更新到視圖上。