在 Swift 开发中使用 HEIC/HEIF 压缩图片大小
了解如何在 iOS 开发中使用更现代的 HEIC/HEIF 格式,以提高应用的性能,并通过 SwiftData 实现图片的自动转换和存储,轻松优化图片处理过程。
2024 年 9 月 29 日更新:
经过一段时间对 HEIC 格式的实际使用,发现虽然它能节省约 50% 的存储空间,但对设备性能的要求更高。展示图片时,HEIC 文件需要解码为常规的 JPEG,否则应用可能会出现明显的卡顿,特别是在同时展示数十张图片的情况下。尽管可以通过异步方式进行解码和加载,但每次解码都会占用内存,同时也增加了编程的复杂性。
因此,如果你的 App 不需要存储大量图片,或者不必利用 HEIC 保存原始信息的优势,建议直接使用 JPEG 格式。为了减少存储空间而增加性能负担和编程难度,并不值得。
什么是 HEIC/HEIF 格式
HEIC(High-Efficiency Image Container)和 HEIF(High-Efficiency Image Format)是一种基于 HEVC(High-Efficiency Video Coding,即 H.265)压缩算法的图片格式。与传统的 JPEG 相比,HEIC/HEIF 可以在相同质量下将图片大小减少 50% 以上,这对于存储和传输大量图片的场景来说具有重要意义。
测试图片 | JPEG(压缩前) | JPEG(0.3 压缩率) | HEIC(0.3 压缩率) |
图片一(截图) | 1495.71 KB | 84.86 KB | 39.27kb |
图片二(拍照) | 6899.27 KB | 736.79 KB | 302.57 KB |
HEIC 最早由苹果公司在 iOS 11 和 macOS High Sierra 中引入,逐渐成为高效图片存储的标准格式。如今,越来越多的设备和平台也开始支持 HEIC/HEIF 格式。
HEIC/HEIF 与其他格式的对比
与常见的 JPEG、PNG 等格式相比,HEIC/HEIF 在图片压缩方面具有明显优势:
- 更高的压缩效率:HEIC/HEIF 使用更先进的 HEVC 编码技术,能够在保持高画质的同时,极大地减少文件体积。
- 支持更多功能:HEIC/HEIF 支持图像透明度、多图像存储(如动态图片、连拍照片)、更广的色域和更高的色深。这意味着在保证图片质量的前提下,它还能保存更多的信息,如照片的元数据、编辑历史等。
- 无损编辑:HEIC/HEIF 支持存储编辑信息,如裁剪、旋转、滤镜效果等,可以进行无损编辑,保留原始图像的细节。
将图片转换为 HEIC 格式
Swift 并未直接提供将 JPEG/PNG 等传统格式图片转换为 HEIC 格式的 API,因此我们需要手动创建一个函数来实现这个需求。
创建自定义函数实现格式转换
可以使用 UIImage
的 jpegData
方法与 ImageIO
框架配合,实现图片格式的转换:
import UIKit
import ImageIO
import MobileCoreServices
func convertImageToHEIC(image: UIImage) -> Data? {
let imageData = NSMutableData()
guard let imageDestination = CGImageDestinationCreateWithData(imageData, AVFileType.heic as CFString, 1, nil),
let cgImage = image.cgImage else {
return nil
}
let options: [CFString: Any] = [
kCGImageDestinationLossyCompressionQuality: 0.7 // 压缩质量,0.0-1.0
]
CGImageDestinationAddImage(imageDestination, cgImage, options as CFDictionary)
guard CGImageDestinationFinalize(imageDestination) else {
return nil
}
return imageData as Data
}
在这段代码中我们做了以下工作:
UIImage
被转换为 HEIC 格式的数据。kCGImageDestinationLossyCompressionQuality
用于控制压缩质量,范围在 0.0 到 1.0 之间,数值越高表示质量越好但文件越大。- 使用
CGImageDestinationCreateWithData
方法创建一个用于保存 HEIC 图片的目标,传入AVFileType.heic
指定保存格式。 - 调用
CGImageDestinationFinalize
来完成压缩并返回最终的 HEIC 数据。
convertImageToHEIC()
函数可以用于任何UIImage
类型的图片,将其转换为 HEIC 格式的数据,并返回一个 Data?
类型。如果转换成功,函数会返回转换后的 HEIC 数据;如果转换失败,则返回 nil
。
在图片保存中使用 HEIC 格式
我们可以在存储或传输图片时,使用更加高效的 HEIC 格式。
以下是一个完整的示例,包括从现有的 UIImage
转换为 HEIC 格式,并将其保存到文件系统中:
import UIKit
func saveImageAsHEIC(image: UIImage, fileName: String) -> Bool {
guard let heicData = convertImageToHEIC(image: image) else {
print("Failed to convert image to HEIC.")
return false
}
let fileManager = FileManager.default
guard let documentsDirectory = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first else {
print("Could not access document directory.")
return false
}
let fileURL = documentsDirectory.appendingPathComponent("\(fileName).heic")
do {
try heicData.write(to: fileURL)
print("HEIC image saved at: \(fileURL)")
return true
} catch {
print("Failed to save HEIC image: \(error)")
return false
}
}
在这个示例中,我们调用了 convertImageToHEIC
来将图片转换为 HEIC 格式,并将生成的 HEIC 数据保存到应用的文档目录中。要调用这个方法,只需要传入要转换的 UIImage
和文件名,例如:
if let image = UIImage(named: "exampleImage") {
saveImageAsHEIC(image: image, fileName: "compressedImage")
}
在网络传输中使用 HEIC 格式
你也可以在使用 convertImageToHEIC
函数后,直接将转换后的数据用于网络请求:
func uploadImageAsHEIC(image: UIImage, url: URL) {
guard let heicData = convertImageToHEIC(image: image) else {
print("Failed to convert image to HEIC.")
return
}
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("image/heic", forHTTPHeaderField: "Content-Type")
let task = URLSession.shared.uploadTask(with: request, from: heicData) { data, response, error in
if let error = error {
print("Upload failed: \(error)")
} else {
print("Upload successful!")
}
}
task.resume()
}
在这个示例中,我们使用 convertImageToHEIC
将图片转换为 HEIC 格式的数据,并将该数据用于网络上传。这样可以极大地减少上传数据的大小,提高网络传输的效率。
调用此方法时,只需要提供要上传的 UIImage
和服务器的 URL
:
if let image = UIImage(named: "exampleImage") {
let uploadURL = URL(string: "https://example.com/upload")!
uploadImageAsHEIC(image: image, url: uploadURL)
}
在 SwiftData 中存储 HEIC 格式图片
如果使用 SwiftData 来实现数据的持久化,可以有两种实现方式:
- 每次存储数据时,先将图片转为 HEIC,然后再存储到 SwiftData 数据结构中。
- 在 SwiftData 中实现,每次存储图片时自动转为 HEIC 格式进行存储。这种方式显然更加简单。
在存储时自动转换图片格式
具体来说,我们可以在 SwiftData 的模型中,通过自定义的属性观察器(didSet
)来实现自动将图片数据转换为 HEIC 格式。
在下面的示例中,我们将创建一个 PhotoEntity
,在每次设置图片时自动将其转换为 HEIC 格式进行存储。
import SwiftData
import UIKit
import ImageIO
import MobileCoreServices
@Model
class PhotoEntity: Identifiable {
@Attribute(.unique) var id: String
// 公开 originalImage,用于外部访问和展示
var originalImage: UIImage? {
didSet {
heicImageData = originalImage.flatMap { convertImageToHEIC(image: $0) }
}
}
// 用于存储 HEIC 数据,设置为 private
@Attribute private var heicImageData: Data?
init(id: String = UUID().uuidString, image: UIImage?) {
self.id = id
self.originalImage = image
self.heicImageData = image.flatMap { convertImageToHEIC(image: $0) }
}
// 提供外部设置图片的方法
func setImage(_ image: UIImage) {
self.originalImage = image
}
// HEIC 转换函数
private func convertImageToHEIC(image: UIImage) -> Data? {
let imageData = NSMutableData()
guard let imageDestination = CGImageDestinationCreateWithData(imageData, AVFileType.heic as CFString, 1, nil),
let cgImage = image.cgImage else {
return nil
}
let options: [CFString: Any] = [
kCGImageDestinationLossyCompressionQuality: 0.8 // 压缩质量,0.0-1.0
]
CGImageDestinationAddImage(imageDestination, cgImage, options as CFDictionary)
guard CGImageDestinationFinalize(imageDestination) else {
return nil
}
return imageData as Data
}
}
在上述数据结构中,我们创建了两个变量 originalImage 和 heicImageData:
originalImage
是一个 UIImage 类型的变量,方便外部接口来访问图片。它是一个计算属性,并不存储实际的数据。- 当它被设置时,将图片转换为 HEIC 格式,并最终存储到 heicImageData 中。
- 当它被读取时,返回对应 heicImageData 中存储的值。
heicImageData
是用于存储最终 HEIC 格式图片数据的变量,供 SwiftData 持久化保存。它是 private 属性,不提供对外的直接访问。
向 SwiftData 中存储与读取图片数据
要向 SwiftData 中存储图片,可以这样使用:
import SwiftData
let photoEntity = PhotoEntity(image: UIImage(named: "exampleImage")) // 通过初始化时设置图片
do {
try SwiftData.save(photoEntity) // 保存 photoEntity,其中包含 HEIC 格式的图片数据
} catch {
print("Failed to save photo entity: \(error)")
}
从 SwiftData 中读取图片数据,可以直接使用 SwiftUI 提供的 Image 组件:
import SwiftUI
struct PhotoView: View {
var photoEntity: PhotoEntity
var body: some View {
if let image = photoEntity.originalImage {
Image(uiImage: image)
.resizable()
.scaledToFit()
} else {
Text("No Image Available")
}
}
}
通过将图片转换为 HEIC 格式,并使用 SwiftData 存储 HEIC 图片数据,可以显著减少图片的存储空间,提升应用的性能。在 Swift 开发中,HEIC 提供了更高效的图片存储和传输方式,为优化存储空间和提高图片质量提供了有力支持。