版本记录
版本号 | 时间 |
---|---|
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图像压缩,感兴趣的给个赞或者关注~~~