圖片是如何顯示的
在講解如何選擇圖片格式之前,我感覺有必要先了解下,圖片是如何展示的。如果我們要展示一張圖片,一般步驟是這樣的:
/// Assets.xcassets中的圖片,不需要字尾
let image = UIImage(named: "icon")
let imageView = UIImageView(frame: rect)
imageView.image = image
view.addSubview(imageView)
複製程式碼
執行程式,我們就可以在指定位置看到這個icon。看似簡單的程式碼背後隱藏了很多細節工作。一張圖片的展示,從程式碼執行到展示出來大致經歷了這些步驟:
1. 載入圖片
-
從磁碟中載入一張圖片;
-
然後將生成的
UIImage
賦值給UIImageView
; -
接著一個隱式的
CATransaction
捕獲到了UIImageView
圖層樹的變化; -
分配記憶體緩衝區用於管理檔案 IO 和解壓縮操作,將檔案資料從磁碟讀到記憶體中;
2. 圖片解碼(解壓)
- 將壓縮的圖片資料解碼成未壓縮的點陣圖形式,這是一個非常耗時的 CPU 操作,預設在主執行緒進行;
3. 圖片渲染
-
Core Animation
中CALayer
使用解壓(解碼)的點陣圖資料渲染UIImageView
的圖層; -
CPU計算好圖片的Frame,對圖片解壓之後,就會交給GPU來做圖片渲染渲染流程;
-
GPU獲取獲取圖片的座標,將座標交給頂點著色器(頂點計算),將圖片光柵化(獲取圖片對應螢幕上的畫素點),片元著色器計算(計算每個畫素點的最終顯示的顏色值);
-
從幀快取區中渲染到螢幕上;
這其中有個關鍵步驟是圖片解碼。那為什麼要解碼呢,這是因為我們平常使用的圖片一般為了節約空間都會經過一些壓縮演算法進行封裝,而使用時螢幕要精確的渲染到每個畫素點,這就需要把壓縮的圖片解碼展開,便於系統處理。
名詞解釋
有損vs無損
有失真壓縮:指在壓縮檔案大小的過程中,損失了一部分圖片的資訊,也即降低了圖片的質量,並且這種損失是不可逆的,我們不可能從有一個有失真壓縮過的圖片中恢復出全來的圖片。常見的有失真壓縮手段,是按照一定的演算法將臨近的畫素點進行合併。
無失真壓縮:只在壓縮檔案大小的過程中,圖片的質量沒有任何損耗。我們任何時候都可以從無失真壓縮過的圖片中恢復出原來的資訊。
索引色vs直接色
索引色:用一個數字來代表(索引)一種顏色,在儲存圖片的時候,儲存一個數字的組合,同時儲存數字到圖片顏色的對映。這種方式只能儲存有限種顏色,通常是256種顏色,對應到計算機系統中,使用一個位元組的數字來索引一種顏色。
直接色:使用四個數字來代表一種顏色,這四個數字分別代表這個顏色中紅色、綠色、藍色以及透明度。現在流行的顯示裝置可以在這四個維度分別支援256種變化,所以直接色可以表示2的32次方種顏色。當然並非所有的直接色都支援這麼多種,為壓縮空間使用,有可能只有表達紅、綠、藍的三個數字,每個數字也可能不支援256種變化之多。
點陣圖vs向量圖
點陣圖:也叫做點陣圖,畫素圖。構成點陣圖的最小單位是象素,點陣圖就是由象素陣列的排列來實現其顯示效果的,每個象素有自己的顏色資訊,在對點陣圖影像進行編輯操作的時候,可操作的物件是每個象素,我們可以改變影像的色相、飽和度、明度,從而改變影像的顯示效果。點陣圖縮放會失真,用最近非常流行的沙畫來比喻最恰當不過,當你從遠處看的時候,畫面細膩多彩,但是當你靠的非常近的時候,你就能看到組成畫面的每粒沙子以及每個沙粒的顏色。
向量圖:也叫做向量圖。向量圖並不紀錄畫面上每一點的資訊,而是紀錄了元素形狀及顏色的演算法,當你開啟一張向量圖的時候,軟體對圖形象對應的函式進行運算,將運算結果[圖形的形狀和顏色]顯示給你看。無論顯示畫面是大還是小,畫面上的物件對應的演算法是不變的,所以,即使對畫面進行倍數相當大的縮放,其顯示效果仍然相同(不失真)。
幾種格式的對比
一張圖片,如果我們將它的每一個畫素及其對應的顏色都儲存起來(BMP格式),是會很大的。為了減小圖片佔用的儲存空間,派生出了各種不同壓縮演算法所代表的圖片格式。常見的圖片格式有png、jpeg、heic、gif、webp,svg等。
PNG
PNG有兩種型別:PNG-8和PNG-24。
-
PNG-8是無損的、索引色、點陣圖。它支援透明度的調節。
-
PNG-24是無損的、直接色、點陣圖。因為使用直接色,顏色範圍更大,也佔用更大的空間。他的目標是替代JPEG,但一般而言,PNG-24的檔案大小是JPEG的五倍之多,而顯示效果則通常只能獲得一點點提升。所以如果不是對圖片質量要求特別高,不建議使用PNG-24
PNG是蘋果推薦的圖片格式,而且這些圖片會被一個叫pngcrush的開源工具優化,這樣iOS裝置就能在顯示時更快的解壓和渲染圖片。該工具位於目錄:
/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/usr/bin
複製程式碼
JPEG
JPEG是有損的、採用直接色的、點陣圖壓縮方式。 JEPG目標是在不影響人類可分辨的圖片質量的前提下,儘可能的壓縮檔案大小。一般都是用於相機圖片的格式。但因為有損,會導致圖片失真。iOS可以通過以下方式壓縮圖片:
// 壓縮比範圍從0到1
func jpegData(compressionQuality: CGFloat) -> Data?
複製程式碼
題外話 一般來說,相同的圖片採用JPEG的壓縮方式會比png得到更小的尺寸,但也有例外。
在網上查了資料說是JEPG更適合處理帶有很多雜色的風景圖,而對於使用數位板等電子繪製的純色卡通系風格圖片,JEPG的壓縮方式會適得其反,導致體積更大。
HEIC
HEIC是HEIF(High Efficiency Image Format 高效率影像檔案格式)的一種,在iOS 11更新後,iPhone 7及其後硬體,在拍攝照片時的預設影像儲存格式。與JPG相比,它佔用的空間更小,畫質更加無損。HEIC的目的就是作為JPEG的繼任者,以後或許會成為一種趨勢。目前可以想到的在開發中的應用是,對於一些需要下載的大圖可以轉成HEIC格式,供客戶端使用。但是當前卻很少應用,大概率是考慮到圖片相容問題吧。
題外話
一個有趣的現象,我用相機(iPhoneXR)拍攝一張照片,通過AirDrop傳到電腦,顯示為HEIC格式。當我在拍照時選擇系統自帶的任意一種濾鏡,圖片格式就變成了JPEG。這是為什麼?
pdf圖片通常是向量的,它的匯入方式有些特殊。我們需要在Assets.xcassets
檔案,建立一個New Image Set
,然後將該檔案的Scales
設定為Single Scale
,拖入1x尺寸的pdf檔案即可:
使用時我們可以把它當做普通圖片對待:
let image = UIImage(named: "sunset")
複製程式碼
在執行期間Xcode會根據螢幕的比例因子生成對應尺寸的png影像。比如匯入一張100x100的pdf圖片,在2x和3x的機型裡面會生成對應的200x200,300x300的png(可以在Assets.car中找到)。所以pdf只不過是Xcode處理圖片的中間狀態,下載到手機的應用包裡面是沒有這張pdf的。
這種處理方式有一個好處就是,當蘋果以後釋出一款4x螢幕的手機時,使用pdf處理的圖片會自適應生成對應的4x資源,不需要再手動匯入。但相比優點,pdf作為圖片資源的缺點更多。
首先是尺寸上,因為是自動生成對應的png,並沒有任何優化和壓縮,而且我們也並不能在這中間做什麼。對比相同尺寸經過ImageOptim壓縮過的png,在大小上後者會是前者的1/2,甚至1/4。
另外pdf對陰影和漸變的處理會存在失真的情況:
左邊是png,右邊是pdf。在一些漸變和光影的影像部分可以看出明顯的失真。
更多關於pdf和png的差別,可以看這篇:Why I don't use PDFs for iOS assets: bjango.com/articles/id…
SVG/SF Symbol
SVG是一種無損的向量圖,是眾多向量圖中的一種,它的特點是使用XML來描述圖片。使用XML的優點是,任何時候你都可以把它當做一個文字檔案來對待,也就是說,你可以非常方便的修改SVG圖片,你所需要的只需要一個文字編輯器。
在iOS13之前應用中直接使用SVG的場景非常少,但從iOS13開始,蘋果推出了SF Symbol,一種svg格式的向量符號集。而且蘋果還提供了多於1500多種icon模板,我們可以在這裡下載檢視。
我們可以從中選擇適合自己的icon,選中之後,從File > Export Custom Symbol Templete中匯出svg格式圖片集,然後拖到Xcode的Assets.xcassets
。
SF Symbol有9種粗細的調節——從ultralight到black——每一種都相當於San Francisco
系統字型的重量(weight)。(SF Symbol中的SF是San Francisco
(舊金山)的縮寫)。這種對應使您能夠在符號和相鄰文字之間實現精確的權重匹配,同時支援不同大小和上下文的靈活性。
當然如果這些圖示都不能滿足需求,我們還可以自定義SF圖示,然後通過SF Symbol App進行驗證和匯出。操作細節可以看這裡:Creating Custom Symbol Images for Your App。
SF Symbol使用起來也很簡單:
let configuration = UIImage.SymbolConfiguration.init(scale: .small)
imageView.image = UIImage(systemName: "alarm", withConfiguration: configuration)
複製程式碼
SF Symbol可以一次性解決相同icon,不同尺寸,不同粗細的問題,它讓我們處理圖片像處理字型一樣方便。可以想象這就是應用圖示的未來。
當看到SF Symbol僅支援iOS13+,watchOS6+,我又不得不退回到現實,png也挺好的。
題外話
我在測試SF Symbol圖示時,從生成的應用包中檢視圖片,會得到這樣的結果:
程式碼中的我將圖片設定為100x100,僅有這一處地方使用。跟pdf類似我們找不到svg原始檔,這好理解,svg只是中間狀態,我們最終使用的還是png,但為什麼會有多個小尺寸的png影像呢?
如何選擇圖片格式
我們平常開發時,使用最多的就是png了,甚至可能是不加考慮的全部使用png。其實這樣是不好的,我們應該充分發揮不同格式圖片的優點,從相容性、空間佔用、展示效果三方面考量選取最佳格式。
關於圖片格式的選擇,蘋果的Human Interface Guidelines有以下說法:
一般情況下,使用PNG圖片,因為PNG支援透明性,而且是無損的,壓縮工件不會模糊重要的細節或改變顏色。對於需要陰影、紋理和高光效果的複雜藝術品來說,這是一個不錯的選擇。使用8位的PNG圖形,不需要完全24位的顏色。8位PNG可以在不降低影像質量的情況下減小檔案大小。精緻的應用圖示最好使用png。
對於照片應該使用JPEG格式,它的壓縮演算法通常比無損格式產生更小的尺寸,而且很難在照片中辨別出來。應該嘗試優化JPEG檔案,在大小和質量之間找到平衡。大多數JPEG檔案可以被壓縮而不會導致影像明顯的退化。即使是少量的壓縮也可以節省大量的磁碟空間。
使用PDF處理字形和其他需要高解析度縮放的平面向量圖形。
最終可以做以下總結。
圖片格式 | 適用範圍 | 注意事項 |
---|---|---|
png | 應用icon,介面icon,卡通風格的背景圖 | 匯入專案前可以使用ImageOptim進行壓縮 |
jpeg | 尺寸較大的風景圖,照片 | 不支援透明度;因為可以調節壓縮比,可以在大小和質量之間尋找最佳平衡。 |
字形,高解析度的向量圖 | 存在展開尺寸較大,光效失真的情況 | |
svg(sf symbol) | 指示性icon | 僅支援iOS13及以上,系統sf符號是有版權的,使用時要注意應用範圍和蘋果要求 |