版本記錄
版本號 | 時間 |
---|---|
V1.0 | 2019.10.20 星期日 |
前言
App中很多時候都需要進行圖像處理,包括各種縮放、濾鏡和壓縮等處理,好的圖像處理不僅可以提高App的性能,也會給用戶帶來耳目一新的感覺,這里重新開了一個專題,專門講述對圖像的各種處理。感興趣的可以看下面幾篇文章。
1. 圖像處理相關(一) —— 圖像深度相關處理簡單示例(一)
開始
首先看下主要內容:
主要內容:在此
HEIC
圖像壓縮教程中,您將學習如何將圖像轉換為HEIC
和JPEG
格式,并比較它們的效率以獲得最佳性能。這里是翻譯原文地址
接著看下寫作環境
Swift 5, iOS 13, Xcode 11
在當今的現代世界中,照片和視頻通常會占用移動設備的大部分磁盤空間。 由于Apple繼續在iPhone的攝像頭上投入時間和金錢,因此使用iOS設備的人們將繼續保持這種情況。 高質量的照片意味著更大的圖像數據。 4K攝像機占用這么多空間是有原因的!
要存儲大量圖像數據,通過增加硬件存儲大小,您可以做很多事情。 為了幫助最小化數據占用空間,發明了各種數據壓縮算法。 數據壓縮算法很多,沒有一種適合所有解決方案的算法。 對于圖像,Apple已采用HEIC圖像壓縮。 您將在本教程中了解有關此壓縮的所有信息。
1. Formatting and HEIC Image Compression
JPEG
一詞通常用于描述圖片的文件類型。盡管文件的擴展名.jpg
或.jpeg
可能會引起誤解,但JPEG
實際上是一種壓縮格式。通過JPEG
壓縮創建的最常見文件類型為JFIF
或EXIFF
。
HEIF(High Efficiency Image File Format)
是一種新的圖像文件格式,在許多方面都比其JPEG
更好。該格式由MPEG
在2013年開發,聲稱保存的數據量是JPEG
的兩倍,并支持多種類型的圖像數據,包括:
Items
Sequences
Derivations
Metadata
Auxiliary image items
這些數據類型使HEIF
比JPEG
可以存儲的單個圖像的數據更具靈活性。這使得非常實用的用例(例如存儲圖像的編輯)非常有效。您還可以存儲最新iPhone上記錄的圖像深度數據。
MPEG
規范中定義了一些文件擴展名。對于他們的HEIF
文件,Apple決定使用.heic
擴展名,即High Image Image Container
。他們的選擇表明使用了HEVC
編解碼器,但是Apple的設備也可以讀取其他一些編解碼器壓縮的文件。
首先,打開已經下載的項目。
該項目是一個簡單的示例應用程序,顯示兩個圖像視圖以及一個用于調整JPEG
和HEIC
圖像壓縮級別的滑塊。 在每個圖像視圖旁邊是幾個標簽,用于顯示有關所選圖像的信息,這些標簽目前全部都不起任何作用。
該應用程序的目的是通過顯示圖像壓縮所需的時間以及HEIC
文件的大小來顯示使用HEIC
和JPEG
的優點。 它還顯示了如何使用share sheet
共享HEIC
文件。
Saving As HEIC
在啟動程序項目打開的情況下,構建并運行以查看實際應用程序的用戶界面。
在開始壓縮圖像之前,您需要能夠選擇圖像。 杰里米·托馬斯Jeremy Thomas在Unsplash上使用的默認圖片效果不錯,但最好看看它在您自己的內容上的效果。
在MainViewController.swift
內部,將以下內容添加到文件的底部:
extension MainViewController: UIImagePickerControllerDelegate,
UINavigationControllerDelegate {
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
// 1
picker.dismiss(animated: true)
}
func imagePickerController(
_ picker: UIImagePickerController,
didFinishPickingMediaWithInfo
info: [UIImagePickerController.InfoKey : Any]
) {
picker.dismiss(animated: true)
// 2
guard let image = info[.originalImage] as? UIImage else {
return
}
// 3
originalImage = image
updateImages()
}
}
這是UIImagePickerControllerDelegate
的簡單實現。 在這里:
- 1) 當按下取消按鈕時,關閉選擇器。
- 2) 從選擇器中獲取原始圖像,以在此應用程序中獲得最佳效果。
- 3) 存儲此圖像并更新圖像視圖。
目前,updateImages()
不執行任何操作。 接下來,將這些行添加到空的addButtonPressed()
中:
let picker = UIImagePickerController()
picker.delegate = self
present(picker, animated: true)
這提供了一個圖像選擇器,使用戶有機會選擇自己的圖像。 但是,您仍然需要更新圖像視圖以使此工作正常。
用以下內容替換compressJPGImage(with :)
和compressHEICImage(with :)
的空實現:
private func compressJPGImage(with quality: CGFloat) {
jpgImageView.image = originalImage
}
private func compressHEICImage(with quality: CGFloat) {
heicImageView.image = originalImage
}
現在,兩個圖像視圖都將顯示選定的圖像。 選定的圖像是臨時的,但會驗證圖像選擇器是否正常工作。
現在,構建并運行該應用程序。 選擇一個圖像以查看它是否同時出現在兩個圖像視圖中
選擇圖像后,您可以繼續使用壓縮滑塊。 它什么也沒做,但是最終它可以改變每種類型的壓縮強度。
在模擬器上壓縮圖像比在設備上壓縮圖像慢得多。 要解決此問題,您需要一個條件來確定如何讀取滑塊的值。
首先在originalImage
上方的MainViewController.swift
頂部添加以下內容:
private var previousQuality: Float = 0
此屬性將存儲最后一個滑塊值,您稍后將使用該值來根據滑塊的值限制更新次數。
接下來,在MainViewController.swift
的Actions
部分的末尾添加以下兩個方法:
@objc private func sliderEndedTouch() {
updateImages()
}
@objc private func sliderDidChange() {
let diff = abs(compressionSlider.value - previousQuality)
guard diff > 0.1 else {
return
}
previousQuality = compressionSlider.value
updateImages()
}
這兩種方法都會更新屏幕上的圖像。 唯一的區別是,底部方法根據滑塊值的明顯變化來限制更新次數。
在viewDidLoad()
的底部添加以下內容:
#if targetEnvironment(simulator)
compressionSlider.addTarget(
self,
action: #selector(sliderEndedTouch),
for: [.touchUpInside, .touchUpOutside]
)
#else
compressionSlider.addTarget(
self,
action: #selector(sliderDidChange),
for: .valueChanged
)
#endif
這會根據當前開發環境將目標操作注冊到滑塊,從而提高了在沒有物理設備的情況下開發應用程序的質量。 這種設置很有用,因為雖然模擬器可以加快開發速度,但有時性能并不理想。
有了這些功能,終于可以開始壓縮這些圖像了。
在MainViewController.swift
的頂部添加以下屬性:
private let compressionQueue = OperationQueue()
操作隊列(operation queue)
是減輕繁重工作負擔的一種方法,可確保應用程序的其余部分響應。 使用隊列還提供了取消任何活動的壓縮任務的能力。 對于此示例,在開始新任務之前取消當前任務是有意義的。
在updateImages()
內對resetLabels()
的調用之后,添加以下行:
compressionQueue.cancelAllOperations()
在添加新任務之前,這將取消當前隊列中的所有操作。 如果沒有此步驟,您可能會在視圖中設置錯誤的壓縮質量的圖像。
接下來,將compressJPGImage(with :)
的內容替換為以下內容:
// 1
jpgImageView.image = nil
jpgActivityIndicator.startAnimating()
// 2
compressionQueue.addOperation {
// 3
guard let data = self.originalImage.jpegData(compressionQuality: quality) else {
return
}
// 4
DispatchQueue.main.async {
self.jpgImageView.image = UIImage(data: data)
// TODO: Add image size here...
// TODO: Add compression time here...
// TODO: Disable share button here...
UIView.animate(withDuration: 0.3) {
self.jpgActivityIndicator.stopAnimating()
}
}
}
使用上面的代碼,您:
- 1) 刪除舊圖像并啟動活動指示器
(activity indicator)
。 - 2) 將壓縮任務添加到已定義的操作隊列中。
- 3) 使用質量參數壓縮原始圖像并將其轉換為
Data
。 - 4) 從壓縮數據創建
UIImage
并更新主線程上的圖像視圖。 請記住,UI操作應始終在主線程上進行。 您即將在此方法中添加更多代碼。
就是使用JPEG
編解碼器壓縮圖像。 要添加HEIC
圖像壓縮,請將compressHEICImage(with :)
的內容替換為:
heicImageView.image = nil
heicActivityIndicator.startAnimating()
compressionQueue.addOperation {
do {
let data = try self.originalImage.heicData(compressionQuality: quality)
DispatchQueue.main.async {
self.heicImageView.image = UIImage(data: data)
// TODO: Add image size here...
// TODO: Add compression time here...
// TODO: Disable share button here...
UIView.animate(withDuration: 0.3) {
self.heicActivityIndicator.stopAnimating()
}
}
} catch {
print("Error creating HEIC data: \(error.localizedDescription)")
}
}
HEIC
圖像壓縮方法只有一個區別。 圖像數據在UIImage + Additions.swift
中的幫助器方法中壓縮,該方法目前為空。
打開UIImage + Additions.swift
,您會發現heicData(compressionQuality :)
的空實現。 在添加方法的內容之前,您需要自定義錯誤類型。
在擴展的頂部添加以下內容:
enum HEICError: Error {
case heicNotSupported
case cgImageMissing
case couldNotFinalize
}
此Error
枚舉包含幾種情況,以說明使用HEIC
壓縮圖像時可能出錯的情況。 并非所有的iOS設備都可以捕獲HEIC
內容,但是大多數運行iOS 11或更高版本的設備都可以讀取和編輯此內容。
將heicData(compressionQuality :)
的內容替換為:
// 1
let data = NSMutableData()
guard let imageDestination =
CGImageDestinationCreateWithData(
data, AVFileType.heic as CFString, 1, nil
)
else {
throw HEICError.heicNotSupported
}
// 2
guard let cgImage = self.cgImage else {
throw HEICError.cgImageMissing
}
// 3
let options: NSDictionary = [
kCGImageDestinationLossyCompressionQuality: compressionQuality
]
// 4
CGImageDestinationAddImage(imageDestination, cgImage, options)
guard CGImageDestinationFinalize(imageDestination) else {
throw HEICError.couldNotFinalize
}
return data as Data
是時候分解一下:
- 首先,您需要一個空的數據緩沖區。此外,您可以使用
CGImageDestinationCreateWithData(_:_:_:_ :)
為HEIC
編碼的內容創建目標。此方法是Image I / O
框架的一部分,用作一種容器,可以在添加圖像數據之前添加圖像數據并更新其屬性。如果這里有問題,則設備上沒有HEIC
。 - 您需要確保有要處理的圖像數據。
- 使用鍵
kCGImageDestinationLossyCompressionQuality
來應用傳遞給該方法的參數。您正在使用NSDictionary
類型,因為CoreGraphics
需要它。
最后,將圖像數據和選項一起應用到目標。 -
CGImageDestinationFinalize(_ :)
完成HEIC
圖像壓縮,如果成功,則返回true
。
Build
并運行。現在您應該看到圖像將根據滑塊的值而改變。底部的圖像應該花更長的時間顯示,因為HEIC圖像壓縮涉及更多的壓縮過程,因為它可以節省更多磁盤空間。
Measuring Time
現在,您可能會認為整個HEIC
事情并不十分令人印象深刻。 目前唯一清楚的是使用HEIC
壓縮圖像的速度很慢。 好吧,接下來您將看到HEIC
文件小了多少。
該項目中包含一個名為Data + Additions.swift
的幫助文件,該文件包含一個計算屬性,可以漂亮地打印pretty-prints Data
對象的大小。 此屬性使用Foundation
框架的便捷ByteCountFormatter
格式化字節大小。
在MainViewController.swift
中,替換TODO: Add image size here…
在compressJPGImage(with :)
內為:
self.jpgSizeLabel.text = data.prettySize
像JPEG
方法一樣,替換TODO: Add image size here…
在compressHEICImage(with :)
內為:
self.heicSizeLabel.text = data.prettySize
這將更新size label
以反映每張圖像的尺寸。
Build
并運行。 您應該立即看到使用HEIC可以節省多少空間,這將非常有用。
在HEIC
和JPEG
之間進行選擇時,要考慮的最后一個元素是時間。 壓縮所需的時間是要考慮的關鍵數據。 如果您的應用程序需要速度大于空間,那么HEIC
可能不是您的最佳選擇。
在MainViewController.swift
的頂部添加以下內容:
private let numberFormatter = NumberFormatter()
格式化程序有助于使數字更具可讀性。 此格式化程序將使讀取精確的時間間隔更加容易。
在viewDidLoad()
的底部,在updateImages()
之前,添加以下代碼:
numberFormatter.maximumSignificantDigits = 1
numberFormatter.maximumFractionDigits = 3
這將格式化程序配置為限制其輸出,因為兩種壓縮方法之間的差異非常明顯。 如果兩者更接近,那么更高的精確度將是有益的。
在resetLabels()
之后添加以下方法:
private func elapsedTime(from startDate: Date) -> String? {
let endDate = Date()
let interval = endDate.timeIntervalSince(startDate)
let intervalNumber = NSNumber(value: interval)
return numberFormatter.string(from: intervalNumber)
}
此方法使用您先前聲明的格式化程序。 它輸入一個開始日期,然后根據該日期計算持續時間,并使用數字格式化程序返回一個可選字符串。
在compressJPGImage(with :)
內部,將其添加到方法的頂部:
let startDate = Date()
接下來,替換TODO: Add compression time here…
在compressJPGImage(with:)
內部:
if let time = self.elapsedTime(from: startDate) {
self.jpgTimeLabel.text = "\(time) s"
}
立即記錄開始日期,確保方法的所有部分都對計算的持續時間有所貢獻。 然后,一旦在主隊列上解碼完成,便會設置時間標簽。
與以前一樣,您需要為HEIC
圖像壓縮方法添加隨附的邏輯。 將此添加到compressHEICImage(with :)
的頂部:
let startDate = Date()
并替換TODO: Add compression time here…
,其內容如下:
if let time = self.elapsedTime(from: startDate) {
self.heicTimeLabel.text = "\(time) s"
}
鍵入或復制類似代碼時,請確保設置正確的標簽。 注意,此heicTimeLabel
是在HEIC
方法中設置的,而jpgTimeLabel
是在JPG
方法中設置的。
Build
并運行
現在,您可以做出完全明智的決定。 JPG
壓縮非常快,但需要較大的圖像。 相反,HEIC
圖像較小,但壓縮速度較慢。
了解用戶的設備是一件好事。 由于HEIC需要更長的時間,因此當電池電量不足時,您可能會節省空間。 您還可以檢查設備的可用存儲空間。 如果磁盤已滿75%
,則始終選擇HEIC
圖像壓縮。
Sharing HEIC
最后要考慮的一點是共享HEIC
圖像。 JPEG
壓縮算法多年來一直是Web
上的標準,但是HEIC必須提供的靈活性和節省空間令人印象深刻。
許多設計良好的網站已經使用JPEG
壓縮其內容。 如果網站的文件已經很小,那么節省50%
并不是很誘人。 但是節省一半高質量iPhone
照片的空間更有意義。
雖然可能不是時候在網上全部使用HEIC
,但某些網站提供了上傳HEIC
照片的支持。 在Apple生態系統內,其他應用在處理HEIC內容時應該沒有問題。 共享內容時,最好選擇共享的格式。
在updateImages()
下面添加以下方法:
private func shareImage(_ image: UIImage) {
let avc = UIActivityViewController(
activityItems: [image],
applicationActivities: nil
)
present(avc, animated: true)
}
此方法共享以兩種格式壓縮的圖像。 UIImage
類負責處理其基礎格式,因此您不必這樣做。
要使用此功能,請在MainViewController.swift
中將以下內容添加到shareButtonPressed()
:
let avc = UIAlertController(
title: "Share",
message: "How would you like to share?",
preferredStyle: .alert
)
if let jpgImage = jpgImageView.image {
avc.addAction(UIAlertAction(title: "JPG", style: .default) { _ in
self.shareImage(jpgImage)
})
}
if let heicImage = heicImageView.image {
avc.addAction(UIAlertAction(title: "HEIC", style: .default) { _ in
self.shareImage(heicImage)
})
}
avc.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
present(avc, animated: true)
這設置了此應用程序的簡單共享功能,因為圖像視圖中已經有兩個壓縮圖像。 對于將所有圖像存儲為HEIC
的真實應用,您需要在共享之前將圖像轉換為JPEG
。
要添加到示例應用程序中的最后一項功能是在沒有可用圖像時阻止共享。 對于較大的文件,HEIC圖像壓縮可能會失敗或需要更長的時間。 在此期間,最好禁用共享按鈕,以免造成混淆并維護良好的用戶體驗。
在updateImages()
內部添加:
navigationItem.leftBarButtonItem?.isEnabled = false
此行在開始壓縮之前禁用共享按鈕。
接下來,將每次出現的TODO: Disable share button here…
替換為:
self.navigationItem.leftBarButtonItem?.isEnabled = true
每次壓縮完成后,重新啟用共享按鈕。 如果HEIC
圖片壓縮仍在處理中,則您只會在alert
中看到JPG
選項。 對于您的應用,等待兩個選項均可用可能更有意義。
構建并運行。
恭喜,示例應用已完成!
您現在應該對HEIC,它的優點和缺點有一定的了解。 HEIC有很多用例,隨著時間的推移,它只會越來越受歡迎。
回顧HEIC
的好處:
- 與
JPEG
相比,文件大小小50%
。 - 包含許多圖像項。
- 圖像派生,非破壞性編輯。
- 圖像序列,例如實時照片
(Live Photos)
。 - 用于存儲深度或
HDR
數據的輔助圖像項目。 - 圖像元數據,例如位置或相機信息。
有關Apple的HEIC的更多信息,請查看2017年關于 HEIF and HEVC的WWDC會議。
后記
本篇主要講述了HEIC圖像壓縮,感興趣的給個贊或者關注~~~