图片压缩纬度

gif是序列帧动画,并且不像视频一样有预测帧,所以只需要单纯的从帧率、尺寸、质量三个维度进行压缩即可

方案

因为是单纯的序列帧,所以只需要想办法获取gif的所有图片,进行固定间隔的抽帧,然后遍历所有图片进行尺寸和质量压缩即可

实现

UIImage不可直接读取序列帧图片,ImageIO提供了读取序列帧的api

  1. 首先:
import ImageIO
  1. 这里先把UIImage转为CGImageSource,方便后续读取图片信息和序列帧
// kCGImageSourceShouldCache是否以解码形式缓存在内存中,如果设置true,占用内存会相对大一些,gif图片又比较简单,所以设置false
let imageSource = CGImageSourceCreateWithData(data as CFData, [kCGImageSourceShouldCache: false] as CFDictionary)
  1. 开辟图片数据储存空间
let writeData = CFDataCreateMutable(nil, 0)
  1. 创建图片操作句柄
// 获取gif帧数
let frameCount = CGImageSourceGetCount(imageSource)
// 使用imageDestination可以随意操作生成图片的参数
let imageDestination = CGImageDestinationCreateWithData(writeData, kUTTypeGIF, frameCount, nil)
  1. 获取帧间时间数组
// 如果不需要抽帧处理的话,直接用原来的时间数据即可
// 如果需要抽帧处理,根据抽帧方案,获取对应的数据
let originFrameDurations = imageSource.frameDurations
  1. 循环压缩每一帧图片
// 设置动画循环次数,一定要放在CGImageDestinationAddImage之前执行,否则不生效
let gifProperties = [kCGImagePropertyGIFDictionary: [kCGImagePropertyGIFLoopCount : 0]]
CGImageDestinationSetProperties(imageDestination, gifProperties as CFDictionary)
// 根据原图获取的缩略图尺寸和缩略图裁剪方案等
let options = [kCGImageSourceThumbnailMaxPixelSize: 300,
                        kCGImageSourceCreateThumbnailWithTransform: true,
                      kCGImageSourceCreateThumbnailFromImageAlways: true] as CFDictionary
                
for index in 0...frameCount - 1 {
    let duration = targetFrameDurations[index]
    if let imageFrame = CGImageSourceCreateThumbnailAtIndex(imageSource, index, options) {
        // 设置帧间隔
        let frameProperties = [kCGImagePropertyGIFDictionary: [kCGImagePropertyGIFDelayTime: duration,
        kCGImagePropertyGIFUnclampedDelayTime: duration]]
        // 使用句柄把图片的缩略图(既压缩后的图片)添加到writeData中
        CGImageDestinationAddImage(imageDestination, imageFrame, frameProperties as CFDictionary)
    }
}

let gifImage = UIImage(data: writeData)
  1. 如果需要压缩质量
func compressCIImage(_ ciImage: CIImage, compressionQuality: CGFloat) -> Data? {
    guard let cgImage = CIContext().createCGImage(ciImage, from: ciImage.extent) else {
        return nil
    }

    let uiImage = UIImage(cgImage: cgImage)
    if let compressedData = uiImage.jpegData(compressionQuality: compressionQuality) {
        return CIImage(data: compressedData)
    } else {
        return nil
    }
}
在步骤6的for循环中调用 func compressCIImage()