圖像處理相關(二) —— HEIC圖像壓縮(一)

版本記錄

版本號 時間
V1.0 2019.10.20 星期日

前言

App中很多時候都需要進行圖像處理,包括各種縮放、濾鏡和壓縮等處理,好的圖像處理不僅可以提高App的性能,也會給用戶帶來耳目一新的感覺,這里重新開了一個專題,專門講述對圖像的各種處理。感興趣的可以看下面幾篇文章。
1. 圖像處理相關(一) —— 圖像深度相關處理簡單示例(一)

開始

首先看下主要內容:

主要內容:在此HEIC圖像壓縮教程中,您將學習如何將圖像轉換為HEICJPEG格式,并比較它們的效率以獲得最佳性能。這里是翻譯原文地址

接著看下寫作環境

Swift 5, iOS 13, Xcode 11

在當今的現代世界中,照片和視頻通常會占用移動設備的大部分磁盤空間。 由于Apple繼續在iPhone的攝像頭上投入時間和金錢,因此使用iOS設備的人們將繼續保持這種情況。 高質量的照片意味著更大的圖像數據。 4K攝像機占用這么多空間是有原因的!

要存儲大量圖像數據,通過增加硬件存儲大小,您可以做很多事情。 為了幫助最小化數據占用空間,發明了各種數據壓縮算法。 數據壓縮算法很多,沒有一種適合所有解決方案的算法。 對于圖像,Apple已采用HEIC圖像壓縮。 您將在本教程中了解有關此壓縮的所有信息。

1. Formatting and HEIC Image Compression

JPEG一詞通常用于描述圖片的文件類型。盡管文件的擴展名.jpg.jpeg可能會引起誤解,但JPEG實際上是一種壓縮格式。通過JPEG壓縮創建的最常見文件類型為JFIFEXIFF

HEIF(High Efficiency Image File Format)是一種新的圖像文件格式,在許多方面都比其JPEG更好。該格式由MPEG在2013年開發,聲稱保存的數據量是JPEG的兩倍,并支持多種類型的圖像數據,包括:

  • Items
  • Sequences
  • Derivations
  • Metadata
  • Auxiliary image items

這些數據類型使HEIFJPEG可以存儲的單個圖像的數據更具靈活性。這使得非常實用的用例(例如存儲圖像的編輯)非常有效。您還可以存儲最新iPhone上記錄的圖像深度數據。

MPEG規范中定義了一些文件擴展名。對于他們的HEIF文件,Apple決定使用.heic擴展名,即High Image Image Container。他們的選擇表明使用了HEVC編解碼器,但是Apple的設備也可以讀取其他一些編解碼器壓縮的文件。

首先,打開已經下載的項目。

該項目是一個簡單的示例應用程序,顯示兩個圖像視圖以及一個用于調整JPEGHEIC圖像壓縮級別的滑塊。 在每個圖像視圖旁邊是幾個標簽,用于顯示有關所選圖像的信息,這些標簽目前全部都不起任何作用。

該應用程序的目的是通過顯示圖像壓縮所需的時間以及HEIC文件的大小來顯示使用HEICJPEG的優點。 它還顯示了如何使用share sheet共享HEIC文件。


Saving As HEIC

在啟動程序項目打開的情況下,構建并運行以查看實際應用程序的用戶界面。

在開始壓縮圖像之前,您需要能夠選擇圖像。 杰里米·托馬斯Jeremy ThomasUnsplash上使用的默認圖片效果不錯,但最好看看它在您自己的內容上的效果。

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.swiftActions部分的末尾添加以下兩個方法:

@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可以節省多少空間,這將非常有用。

HEICJPEG之間進行選擇時,要考慮的最后一個元素是時間。 壓縮所需的時間是要考慮的關鍵數據。 如果您的應用程序需要速度大于空間,那么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圖像壓縮,感興趣的給個贊或者關注~~~

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,646評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,595評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,560評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,035評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,814評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,224評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,301評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,444評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,988評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,804評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,998評論 1 370
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,544評論 5 360
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,237評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,665評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,927評論 1 287
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,706評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,993評論 2 374

推薦閱讀更多精彩內容