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

版本記錄

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

前言

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

開始

首先看下主要內(nèi)容:

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

接著看下寫作環(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)建的最常見文件類型為JFIFEXIFF。

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

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

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

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

首先,打開已經(jīng)下載的項目。

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

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


Saving As HEIC

在啟動程序項目打開的情況下,構(gòu)建并運行以查看實際應(yīng)用程序的用戶界面。

在開始壓縮圖像之前,您需要能夠選擇圖像。 杰里米·托馬斯Jeremy ThomasUnsplash上使用的默認圖片效果不錯,但最好看看它在您自己的內(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.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()
}

這兩種方法都會更新屏幕上的圖像。 唯一的區(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é)省多少空間,這將非常有用。

HEICJPEG之間進行選擇時,要考慮的最后一個元素是時間。 壓縮所需的時間是要考慮的關(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)注~~~

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容