版本記錄
| 版本號 | 時間 |
|---|---|
| V1.0 | 2019.10.20 星期日 |
前言
App中很多時候都需要進行圖像處理,包括各種縮放、濾鏡和壓縮等處理,好的圖像處理不僅可以提高App的性能,也會給用戶帶來耳目一新的感覺,這里重新開了一個專題,專門講述對圖像的各種處理。感興趣的可以看下面幾篇文章。
1. 圖像處理相關(guān)(一) —— 圖像深度相關(guān)處理簡單示例(一)
開始
首先看下主要內(nèi)容:
主要內(nèi)容:在此
HEIC圖像壓縮教程中,您將學習如何將圖像轉(zhuǎn)換為HEIC和JPEG格式,并比較它們的效率以獲得最佳性能。這里是翻譯原文地址
接著看下寫作環(huán)境
Swift 5, iOS 13, Xcode 11
在當今的現(xiàn)代世界中,照片和視頻通常會占用移動設(shè)備的大部分磁盤空間。 由于Apple繼續(xù)在iPhone的攝像頭上投入時間和金錢,因此使用iOS設(shè)備的人們將繼續(xù)保持這種情況。 高質(zhì)量的照片意味著更大的圖像數(shù)據(jù)。 4K攝像機占用這么多空間是有原因的!
要存儲大量圖像數(shù)據(jù),通過增加硬件存儲大小,您可以做很多事情。 為了幫助最小化數(shù)據(jù)占用空間,發(fā)明了各種數(shù)據(jù)壓縮算法。 數(shù)據(jù)壓縮算法很多,沒有一種適合所有解決方案的算法。 對于圖像,Apple已采用HEIC圖像壓縮。 您將在本教程中了解有關(guān)此壓縮的所有信息。
1. Formatting and HEIC Image Compression
JPEG一詞通常用于描述圖片的文件類型。盡管文件的擴展名.jpg或.jpeg可能會引起誤解,但JPEG實際上是一種壓縮格式。通過JPEG壓縮創(chuàng)建的最常見文件類型為JFIF或EXIFF。
HEIF(High Efficiency Image File Format)是一種新的圖像文件格式,在許多方面都比其JPEG更好。該格式由MPEG在2013年開發(fā),聲稱保存的數(shù)據(jù)量是JPEG的兩倍,并支持多種類型的圖像數(shù)據(jù),包括:
ItemsSequencesDerivationsMetadataAuxiliary image items
這些數(shù)據(jù)類型使HEIF比JPEG可以存儲的單個圖像的數(shù)據(jù)更具靈活性。這使得非常實用的用例(例如存儲圖像的編輯)非常有效。您還可以存儲最新iPhone上記錄的圖像深度數(shù)據(jù)。
MPEG規(guī)范中定義了一些文件擴展名。對于他們的HEIF文件,Apple決定使用.heic擴展名,即High Image Image Container。他們的選擇表明使用了HEVC編解碼器,但是Apple的設(shè)備也可以讀取其他一些編解碼器壓縮的文件。
首先,打開已經(jīng)下載的項目。
該項目是一個簡單的示例應(yīng)用程序,顯示兩個圖像視圖以及一個用于調(diào)整JPEG和HEIC圖像壓縮級別的滑塊。 在每個圖像視圖旁邊是幾個標簽,用于顯示有關(guān)所選圖像的信息,這些標簽目前全部都不起任何作用。
該應(yīng)用程序的目的是通過顯示圖像壓縮所需的時間以及HEIC文件的大小來顯示使用HEIC和JPEG的優(yōu)點。 它還顯示了如何使用share sheet共享HEIC文件。
Saving As HEIC
在啟動程序項目打開的情況下,構(gòu)建并運行以查看實際應(yīng)用程序的用戶界面。

在開始壓縮圖像之前,您需要能夠選擇圖像。 杰里米·托馬斯Jeremy Thomas在Unsplash上使用的默認圖片效果不錯,但最好看看它在您自己的內(nèi)容上的效果。
在MainViewController.swift內(nèi)部,將以下內(nèi)容添加到文件的底部:
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的簡單實現(xiàn)。 在這里:
- 1) 當按下取消按鈕時,關(guān)閉選擇器。
- 2) 從選擇器中獲取原始圖像,以在此應(yīng)用程序中獲得最佳效果。
- 3) 存儲此圖像并更新圖像視圖。
目前,updateImages()不執(zhí)行任何操作。 接下來,將這些行添加到空的addButtonPressed()中:
let picker = UIImagePickerController()
picker.delegate = self
present(picker, animated: true)
這提供了一個圖像選擇器,使用戶有機會選擇自己的圖像。 但是,您仍然需要更新圖像視圖以使此工作正常。
用以下內(nèi)容替換compressJPGImage(with :)和compressHEICImage(with :)的空實現(xiàn):
private func compressJPGImage(with quality: CGFloat) {
jpgImageView.image = originalImage
}
private func compressHEICImage(with quality: CGFloat) {
heicImageView.image = originalImage
}
現(xiàn)在,兩個圖像視圖都將顯示選定的圖像。 選定的圖像是臨時的,但會驗證圖像選擇器是否正常工作。
現(xiàn)在,構(gòu)建并運行該應(yīng)用程序。 選擇一個圖像以查看它是否同時出現(xiàn)在兩個圖像視圖中

選擇圖像后,您可以繼續(xù)使用壓縮滑塊。 它什么也沒做,但是最終它可以改變每種類型的壓縮強度。
在模擬器上壓縮圖像比在設(shè)備上壓縮圖像慢得多。 要解決此問題,您需要一個條件來確定如何讀取滑塊的值。
首先在originalImage上方的MainViewController.swift頂部添加以下內(nèi)容:
private var previousQuality: Float = 0
此屬性將存儲最后一個滑塊值,您稍后將使用該值來根據(jù)滑塊的值限制更新次數(shù)。
接下來,在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()
}
這兩種方法都會更新屏幕上的圖像。 唯一的區(qū)別是,底部方法根據(jù)滑塊值的明顯變化來限制更新次數(shù)。
在viewDidLoad()的底部添加以下內(nèi)容:
#if targetEnvironment(simulator)
compressionSlider.addTarget(
self,
action: #selector(sliderEndedTouch),
for: [.touchUpInside, .touchUpOutside]
)
#else
compressionSlider.addTarget(
self,
action: #selector(sliderDidChange),
for: .valueChanged
)
#endif
這會根據(jù)當前開發(fā)環(huán)境將目標操作注冊到滑塊,從而提高了在沒有物理設(shè)備的情況下開發(fā)應(yīng)用程序的質(zhì)量。 這種設(shè)置很有用,因為雖然模擬器可以加快開發(fā)速度,但有時性能并不理想。
有了這些功能,終于可以開始壓縮這些圖像了。
在MainViewController.swift的頂部添加以下屬性:
private let compressionQueue = OperationQueue()
操作隊列(operation queue)是減輕繁重工作負擔的一種方法,可確保應(yīng)用程序的其余部分響應(yīng)。 使用隊列還提供了取消任何活動的壓縮任務(wù)的能力。 對于此示例,在開始新任務(wù)之前取消當前任務(wù)是有意義的。
在updateImages()內(nèi)對resetLabels()的調(diào)用之后,添加以下行:
compressionQueue.cancelAllOperations()
在添加新任務(wù)之前,這將取消當前隊列中的所有操作。 如果沒有此步驟,您可能會在視圖中設(shè)置錯誤的壓縮質(zhì)量的圖像。
接下來,將compressJPGImage(with :)的內(nèi)容替換為以下內(nèi)容:
// 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) 將壓縮任務(wù)添加到已定義的操作隊列中。
- 3) 使用質(zhì)量參數(shù)壓縮原始圖像并將其轉(zhuǎn)換為
Data。 - 4) 從壓縮數(shù)據(jù)創(chuàng)建
UIImage并更新主線程上的圖像視圖。 請記住,UI操作應(yīng)始終在主線程上進行。 您即將在此方法中添加更多代碼。
就是使用JPEG編解碼器壓縮圖像。 要添加HEIC圖像壓縮,請將compressHEICImage(with :)的內(nèi)容替換為:
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圖像壓縮方法只有一個區(qū)別。 圖像數(shù)據(jù)在UIImage + Additions.swift中的幫助器方法中壓縮,該方法目前為空。
打開UIImage + Additions.swift,您會發(fā)現(xiàn)heicData(compressionQuality :)的空實現(xiàn)。 在添加方法的內(nèi)容之前,您需要自定義錯誤類型。
在擴展的頂部添加以下內(nèi)容:
enum HEICError: Error {
case heicNotSupported
case cgImageMissing
case couldNotFinalize
}
此Error枚舉包含幾種情況,以說明使用HEIC壓縮圖像時可能出錯的情況。 并非所有的iOS設(shè)備都可以捕獲HEIC內(nèi)容,但是大多數(shù)運行iOS 11或更高版本的設(shè)備都可以讀取和編輯此內(nèi)容。
將heicData(compressionQuality :)的內(nèi)容替換為:
// 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
是時候分解一下:
- 首先,您需要一個空的數(shù)據(jù)緩沖區(qū)。此外,您可以使用
CGImageDestinationCreateWithData(_:_:_:_ :)為HEIC編碼的內(nèi)容創(chuàng)建目標。此方法是Image I / O框架的一部分,用作一種容器,可以在添加圖像數(shù)據(jù)之前添加圖像數(shù)據(jù)并更新其屬性。如果這里有問題,則設(shè)備上沒有HEIC。 - 您需要確保有要處理的圖像數(shù)據(jù)。
- 使用鍵
kCGImageDestinationLossyCompressionQuality來應(yīng)用傳遞給該方法的參數(shù)。您正在使用NSDictionary類型,因為CoreGraphics需要它。
最后,將圖像數(shù)據(jù)和選項一起應(yīng)用到目標。 -
CGImageDestinationFinalize(_ :)完成HEIC圖像壓縮,如果成功,則返回true。
Build并運行?,F(xiàn)在您應(yīng)該看到圖像將根據(jù)滑塊的值而改變。底部的圖像應(yīng)該花更長的時間顯示,因為HEIC圖像壓縮涉及更多的壓縮過程,因為它可以節(jié)省更多磁盤空間。

Measuring Time
現(xiàn)在,您可能會認為整個HEIC事情并不十分令人印象深刻。 目前唯一清楚的是使用HEIC壓縮圖像的速度很慢。 好吧,接下來您將看到HEIC文件小了多少。
該項目中包含一個名為Data + Additions.swift的幫助文件,該文件包含一個計算屬性,可以漂亮地打印pretty-prints Data對象的大小。 此屬性使用Foundation框架的便捷ByteCountFormatter格式化字節(jié)大小。
在MainViewController.swift中,替換TODO: Add image size here…在compressJPGImage(with :)內(nèi)為:
self.jpgSizeLabel.text = data.prettySize
像JPEG方法一樣,替換TODO: Add image size here…在compressHEICImage(with :)內(nèi)為:
self.heicSizeLabel.text = data.prettySize
這將更新size label以反映每張圖像的尺寸。
Build并運行。 您應(yīng)該立即看到使用HEIC可以節(jié)省多少空間,這將非常有用。

在HEIC和JPEG之間進行選擇時,要考慮的最后一個元素是時間。 壓縮所需的時間是要考慮的關(guān)鍵數(shù)據(jù)。 如果您的應(yīng)用程序需要速度大于空間,那么HEIC可能不是您的最佳選擇。
在MainViewController.swift的頂部添加以下內(nèi)容:
private let numberFormatter = NumberFormatter()
格式化程序有助于使數(shù)字更具可讀性。 此格式化程序?qū)⑹棺x取精確的時間間隔更加容易。
在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)
}
此方法使用您先前聲明的格式化程序。 它輸入一個開始日期,然后根據(jù)該日期計算持續(xù)時間,并使用數(shù)字格式化程序返回一個可選字符串。
在compressJPGImage(with :)內(nèi)部,將其添加到方法的頂部:
let startDate = Date()
接下來,替換TODO: Add compression time here…在compressJPGImage(with:)內(nèi)部:
if let time = self.elapsedTime(from: startDate) {
self.jpgTimeLabel.text = "\(time) s"
}
立即記錄開始日期,確保方法的所有部分都對計算的持續(xù)時間有所貢獻。 然后,一旦在主隊列上解碼完成,便會設(shè)置時間標簽。
與以前一樣,您需要為HEIC圖像壓縮方法添加隨附的邏輯。 將此添加到compressHEICImage(with :)的頂部:
let startDate = Date()
并替換TODO: Add compression time here…,其內(nèi)容如下:
if let time = self.elapsedTime(from: startDate) {
self.heicTimeLabel.text = "\(time) s"
}
鍵入或復制類似代碼時,請確保設(shè)置正確的標簽。 注意,此heicTimeLabel是在HEIC方法中設(shè)置的,而jpgTimeLabel是在JPG方法中設(shè)置的。
Build并運行

現(xiàn)在,您可以做出完全明智的決定。 JPG壓縮非常快,但需要較大的圖像。 相反,HEIC圖像較小,但壓縮速度較慢。
了解用戶的設(shè)備是一件好事。 由于HEIC需要更長的時間,因此當電池電量不足時,您可能會節(jié)省空間。 您還可以檢查設(shè)備的可用存儲空間。 如果磁盤已滿75%,則始終選擇HEIC圖像壓縮。
Sharing HEIC
最后要考慮的一點是共享HEIC圖像。 JPEG壓縮算法多年來一直是Web上的標準,但是HEIC必須提供的靈活性和節(jié)省空間令人印象深刻。
許多設(shè)計良好的網(wǎng)站已經(jīng)使用JPEG壓縮其內(nèi)容。 如果網(wǎng)站的文件已經(jīng)很小,那么節(jié)省50%并不是很誘人。 但是節(jié)省一半高質(zhì)量iPhone照片的空間更有意義。
雖然可能不是時候在網(wǎng)上全部使用HEIC,但某些網(wǎng)站提供了上傳HEIC照片的支持。 在Apple生態(tài)系統(tǒng)內(nèi),其他應(yīng)用在處理HEIC內(nèi)容時應(yīng)該沒有問題。 共享內(nèi)容時,最好選擇共享的格式。
在updateImages()下面添加以下方法:
private func shareImage(_ image: UIImage) {
let avc = UIActivityViewController(
activityItems: [image],
applicationActivities: nil
)
present(avc, animated: true)
}
此方法共享以兩種格式壓縮的圖像。 UIImage類負責處理其基礎(chǔ)格式,因此您不必這樣做。
要使用此功能,請在MainViewController.swift中將以下內(nèi)容添加到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)
這設(shè)置了此應(yīng)用程序的簡單共享功能,因為圖像視圖中已經(jīng)有兩個壓縮圖像。 對于將所有圖像存儲為HEIC的真實應(yīng)用,您需要在共享之前將圖像轉(zhuǎn)換為JPEG。
要添加到示例應(yīng)用程序中的最后一項功能是在沒有可用圖像時阻止共享。 對于較大的文件,HEIC圖像壓縮可能會失敗或需要更長的時間。 在此期間,最好禁用共享按鈕,以免造成混淆并維護良好的用戶體驗。
在updateImages()內(nèi)部添加:
navigationItem.leftBarButtonItem?.isEnabled = false
此行在開始壓縮之前禁用共享按鈕。
接下來,將每次出現(xiàn)的TODO: Disable share button here…替換為:
self.navigationItem.leftBarButtonItem?.isEnabled = true
每次壓縮完成后,重新啟用共享按鈕。 如果HEIC圖片壓縮仍在處理中,則您只會在alert中看到JPG選項。 對于您的應(yīng)用,等待兩個選項均可用可能更有意義。
構(gòu)建并運行。

恭喜,示例應(yīng)用已完成!
您現(xiàn)在應(yīng)該對HEIC,它的優(yōu)點和缺點有一定的了解。 HEIC有很多用例,隨著時間的推移,它只會越來越受歡迎。
回顧HEIC的好處:
- 與
JPEG相比,文件大小小50%。 - 包含許多圖像項。
- 圖像派生,非破壞性編輯。
- 圖像序列,例如實時照片
(Live Photos)。 - 用于存儲深度或
HDR數(shù)據(jù)的輔助圖像項目。 - 圖像元數(shù)據(jù),例如位置或相機信息。
有關(guān)Apple的HEIC的更多信息,請查看2017年關(guān)于 HEIF and HEVC的WWDC會議。
后記
本篇主要講述了HEIC圖像壓縮,感興趣的給個贊或者關(guān)注~~~
