使用GIF图片一般场景是:使用手机相册的GIF图片并上传到某个地方,或者是将浏览到的GIF图片保存到手机相册。这边会涉及到
- GIF类型判断
- GIF压缩
- GIF保存到相册
GIF类型判断
关于图片类型的判断,网上一搜就有很多了,这就直接放代码了:
extension Data{
enum ImageFormat {
case Unknow
case JPEG
case PNG
case GIF
case TIFF
case WebP
case HEIC
case HEIF
}
var fitSampleCount:Int{
guard let imageSource = CGImageSourceCreateWithData(self as CFData, [kCGImageSourceShouldCache: false] as CFDictionary) else {
return 1
}
let frameCount = CGImageSourceGetCount(imageSource)
var sampleCount = 1
switch frameCount {
case 2..<8:
sampleCount = 2
case 8..<20:
sampleCount = 3
case 20..<30:
sampleCount = 4
case 30..<40:
sampleCount = 5
case 40..<Int.max:
sampleCount = 6
default:break
}
return sampleCount
}
var imageFormat:ImageFormat {
var buffer = [UInt8](repeating: 0, count: 1)
self.copyBytes(to: &buffer, count: 1)
switch buffer {
case [0xFF]: return .JPEG
case [0x89]: return .PNG
case [0x47]: return .GIF
case [0x49],[0x4D]: return .TIFF
case [0x52] where self.count >= 12:
if let str = String(data: self[0...11], encoding: .ascii), str.hasPrefix("RIFF"), str.hasSuffix("WEBP") {
return .WebP
}
case [0x00] where self.count >= 12:
if let str = String(data: self[8...11], encoding: .ascii) {
let HEICBitMaps = Set(["heic", "heis", "heix", "hevc", "hevx"])
if HEICBitMaps.contains(str) {
return .HEIC
}
let HEIFBitMaps = Set(["mif1", "msf1"])
if HEIFBitMaps.contains(str) {
return .HEIF
}
}
default: break;
}
return .Unknow
}
}
上面是data的扩展来判断图片类型,可是如何拿到UIImage的data这里就有点小坑了。
1、拿到相册图片的data,需要用到PHAsset
这里我用了TZImagePickerController这个三方图片选择器了
func imagePickerController(_ picker: TZImagePickerController!, didFinishPickingPhotos photos: [UIImage]!, sourceAssets assets: [Any]!, isSelectOriginalPhoto: Bool) {
//获取图片资源
for (index,image) in assets.enumerated() {
let myAsset = image as? PHAsset
let options = PHImageRequestOptions()
options.resizeMode = .fast
options.isSynchronous = true
let imageManager = PHCachingImageManager()
imageManager.requestImageData(for: myAsset ?? PHAsset(), options: options) { (data, dataUti, orientation, info) in
if data?.imageFormat != .GIF {
//普通图片
}else {
//GIF图片
}
}
}
}
2、拿到网络图片或者APP沙盒路径下的图片data
网络图片:
do {
//网络图片
let data = try Data.init(contentsOf: URL(string: "imageUrl")!)
//本地图片
// let data = try Data.init(contentsOf: URL(fileURLWithPath: "imagePath"))
if data.imageFormat != .GIF {
//普通图片
}else {
//GIF图片
}
} catch let error as NSError {
}
GIF压缩
基本思路就是先抽帧,减少图片的张数。再对剩下的图片进行压缩、编码。合成。
let sampleCount = data.fitSampleCount
if let resultData = compressImageData(data, sampleCount: sampleCount){
print(resultData)
} else {
}
/// 同步压缩图片抽取帧数
///
/// - Parameters:
/// - rawData: 原始图片数据
/// - sampleCount: 采样频率,比如 3 则每三张用第一张,然后延长时间
/// - Returns: 处理后数据
func compressImageData(_ rawData:Data, sampleCount:Int) -> Data?{
guard let imageSource = CGImageSourceCreateWithData(rawData as CFData, [kCGImageSourceShouldCache: false] as CFDictionary),
let writeData = CFDataCreateMutable(nil, 0),
let imageType = CGImageSourceGetType(imageSource) else {
return nil
}
// 计算帧的间隔
let frameDurations = imageSource.frameDurations
// 合并帧的时间,最长不可高于 200ms
let mergeFrameDurations = (0..<frameDurations.count).filter{ $0 % sampleCount == 0 }.map{ min(frameDurations[$0..<min($0 + sampleCount, frameDurations.count)].reduce(0.0) { $0 + $1 }, 0.2) }
// 抽取帧 每 n 帧使用 1 帧
let sampleImageFrames = (0..<frameDurations.count).filter{ $0 % sampleCount == 0 }.compactMap{ CGImageSourceCreateImageAtIndex(imageSource, $0, nil) }
guard let imageDestination = CGImageDestinationCreateWithData(writeData, imageType, sampleImageFrames.count, nil) else{
return nil
}
// 每一帧图片都进行重新编码
zip(sampleImageFrames, mergeFrameDurations).forEach{
// 设置帧间隔
let frameProperties = [kCGImagePropertyGIFDictionary : [kCGImagePropertyGIFDelayTime: $1, kCGImagePropertyGIFUnclampedDelayTime: $1]]
CGImageDestinationAddImage(imageDestination, $0, frameProperties as CFDictionary)
}
guard CGImageDestinationFinalize(imageDestination) else {
return nil
}
return writeData as Data
}
extension CGImageSource {
func frameDurationAtIndex(_ index: Int) -> Double{
var frameDuration = Double(0.1)
guard let frameProperties = CGImageSourceCopyPropertiesAtIndex(self, index, nil) as? [AnyHashable:Any], let gifProperties = frameProperties[kCGImagePropertyGIFDictionary] as? [AnyHashable:Any] else {
return frameDuration
}
if let unclampedDuration = gifProperties[kCGImagePropertyGIFUnclampedDelayTime] as? NSNumber {
frameDuration = unclampedDuration.doubleValue
} else {
if let clampedDuration = gifProperties[kCGImagePropertyGIFDelayTime] as? NSNumber {
frameDuration = clampedDuration.doubleValue
}
}
if frameDuration < 0.011 {
frameDuration = 0.1
}
return frameDuration
}
var frameDurations:[Double]{
let frameCount = CGImageSourceGetCount(self)
return (0..<frameCount).map{ self.frameDurationAtIndex($0) }
}
}
GIF保存到相册
系统提供了一个直接将图片写入到相册的方法,但是GIF写入到相册后就变成静态图片了
UIImageWriteToSavedPhotosAlbum(_ image: UIImage, _ completionTarget: Any?, _ completionSelector: Selector?, _ contextInfo: UnsafeMutableRawPointer?)
想要把GIF完整保存到相册就需要用到下面这个方法了:
open class func creationRequestForAssetFromImage(atFileURL fileURL: URL) -> Self?
具体写法:
import AssetsLibrary
import Photos
//GIFFileUrl 为图片沙盒路径,所以要将图片先存放在沙盒路径中。
PHPhotoLibrary.shared().performChanges({
PHAssetChangeRequest.creationRequestForAssetFromImage(atFileURL: URL(fileURLWithPath: GIFFileUrl))
}) { (success, error) in
}
技术有限,哪里有问题的话,欢迎指出(部分代码来自网上,侵权删)