在 Swift 开发中使用 HEIC/HEIF 压缩图片大小

了解如何在 iOS 开发中使用更现代的 HEIC/HEIF 格式,以提高应用的性能,并通过 SwiftData 实现图片的自动转换和存储,轻松优化图片处理过程。

在 Swift 开发中使用 HEIC/HEIF 压缩图片大小

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 在图片压缩方面具有明显优势:

  1. 更高的压缩效率:HEIC/HEIF 使用更先进的 HEVC 编码技术,能够在保持高画质的同时,极大地减少文件体积。
  2. 支持更多功能:HEIC/HEIF 支持图像透明度、多图像存储(如动态图片、连拍照片)、更广的色域和更高的色深。这意味着在保证图片质量的前提下,它还能保存更多的信息,如照片的元数据、编辑历史等。
  3. 无损编辑:HEIC/HEIF 支持存储编辑信息,如裁剪、旋转、滤镜效果等,可以进行无损编辑,保留原始图像的细节。

将图片转换为 HEIC 格式

Swift 并未直接提供将 JPEG/PNG 等传统格式图片转换为 HEIC 格式的 API,因此我们需要手动创建一个函数来实现这个需求。

创建自定义函数实现格式转换

可以使用 UIImagejpegData 方法与 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
}

在这段代码中我们做了以下工作:

  1. UIImage 被转换为 HEIC 格式的数据。kCGImageDestinationLossyCompressionQuality 用于控制压缩质量,范围在 0.0 到 1.0 之间,数值越高表示质量越好但文件越大
  2. 使用 CGImageDestinationCreateWithData 方法创建一个用于保存 HEIC 图片的目标,传入 AVFileType.heic 指定保存格式。
  3. 调用 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 提供了更高效的图片存储和传输方式,为优化存储空间和提高图片质量提供了有力支持。