iOS GIF图片的加载和合成

写在前面的

一颗伤心死掉的橘子树

那颗死掉的橘子树

不拘一世之利以为己私分,
不以王天下为已处显。
显则明。万物一府,死生同状。

扯淡结束开始进入文章正题

iOS中GIF图片是无法像jpg,png等一样直接加载出来的,有很多的第三方库也提供了这方面的功能,这里总结一下GIF图片加载的几种方式,以及使用多张png图片合成GIF图片的方法。

加载GIF图片

1.使用UIWebView/WKWebView

使用UIWebView/WKWebView相当于使用coreText 将GIF的数据编码渲染到UIWebView/WKWebView上,使用data数据来加载,本地和网络图片差不多。

下面使用本地图片来举例
//1.使用webview来显示GIF

self.view.addSubview(webview)
webview.frame = self.view.bounds

let path = Bundle.main.path(forResource: "timg", ofType: "gif")
if let data = try? Data.init(contentsOf: URL.init(fileURLWithPath: path!)) {
    webview.load(data as Data, mimeType: "image/gif", characterEncodingName: "UTF-8", baseURL: Bundle.main.resourceURL!)
}

//当然也可以直接加载文件路径,下面两种load方法都可以实现
webview.load(URLRequest.init(url: URL.init(fileURLWithPath: path!)))
webview.loadFileURL(URL.init(fileURLWithPath: path!), allowingReadAccessTo: Bundle.main.resourceURL!)
//OC 的写法类似,就不重复写了。
运行效果
小结

我本地的原图的宽度是500像素的,但是从上面的效果图可以看到,图片有并没有按照像素来显示,而是根据图片比例来显示,而且宽度自动为self.view的宽度。说明GIF图片数据在渲染时并不能控制图片显示的大小,以及GIF执行的帧数和运行次数。

2.使用GIF 数据来实现

也可以使用 ImageIO 系统框架来实现GIF的播放,很多第三方开源的库也是这样做的。类似SDWebImage,Kingfisher,以及YYImage 都可以实现一句话来加载GIF图片的功能,其中Kingfisher 是swift语言,另外两个是OC的。

其实两种语言都一样,这边只是介绍swift语言。

下面的方法是根据GIF的data数据来得到一个([UIImage], TimeInterval) 的元组

// MARK: - ImageIO
private func showGif() ->([UIImage], TimeInterval)? {
    let path = Bundle.main.path(forResource: "timg", ofType: "gif")
    let data = try? Data.init(contentsOf: URL.init(fileURLWithPath: path!))
    let source = CGImageSourceCreateWithData(data as! CFData, nil)
    let count = CGImageSourceGetCount(source!)
    let options: NSDictionary = [kCGImageSourceShouldCache as String: true, kCGImageSourceTypeIdentifierHint as String: kUTTypeGIF]
    var gifDuration = 0.0
    var images = [UIImage]()
    
    func frameDuration(from gifInfo: NSDictionary) -> Double {
        let gifDefaultFrameDuration = 0.100
        let unclampedDelayTime = gifInfo[kCGImagePropertyGIFUnclampedDelayTime as String] as? NSNumber
        let delayTime = gifInfo[kCGImagePropertyGIFDelayTime as String] as? NSNumber
        let duration = unclampedDelayTime ?? delayTime
        guard let frameDuration = duration else { return gifDefaultFrameDuration }
        
        return frameDuration.doubleValue > 0.011 ? frameDuration.doubleValue : gifDefaultFrameDuration
    }
    for i in 0 ..< count {
        guard let imageRef = CGImageSourceCreateImageAtIndex(source!, i, options) else {
            return nil
        }
        if count == 1 {
            //只有一张图片时
            gifDuration = Double.infinity//无穷大
        }else {
            // Animated GIF
            guard let properties = CGImageSourceCopyPropertiesAtIndex(source!, i, nil), let gifinfo = (properties as NSDictionary)[kCGImagePropertyGIFDictionary as String] as? NSDictionary  else {
                return nil
            }
            gifDuration += frameDuration(from: gifinfo)
        }
        images.append(UIImage.init(cgImage: imageRef, scale: UIScreen.main.scale, orientation: .up))
    }
    return (images, gifDuration)
}

然后在viewDidLoad 中调用下面的代码

var imageView: UIImageView?
let (images, duration) = showGif()!
let animatedImage = UIImage.animatedImage(with: images, duration: duration)
imageView = UIImageView.init(image: animatedImage)

self.view.addSubview(imageView!)
imageView?.center = self.view.center

使用let animatedImage = UIImage.animatedImage(with: images, duration: duration)可以创建一个动画图片,然后使用imageView = UIImageView.init(image: animatedImage)创建一个UIImageView? (为什么用这个方法呢,因为这个不用写imageView的width和height,会根据图片的像素自动渲染大小,不用设置大小。。。

运行效果图
小结

原图的像素宽度为500,模拟器的屏幕为@2x的像素,所以图片显示大小应该是刚刚好的。这种相对于webView来显示就优化得多,也可以设置图片动画的持续时间。

同样的道理,我们如果用imags这个image数组 加入到ImageView动画组中,就可以设置动画的次数,就可以实现类似于新浪微博多个GIF图的微博 GIF图片会依次播放的功能。

let (images, duration) = showGif()!
//let animatedImage = UIImage.animatedImage(with: images, duration: duration)
imageView = UIImageView.init(image: images.first)
        
self.view.addSubview(imageView!)
imageView?.center = self.view.center
        
imageView?.animationImages = images
imageView?.animationDuration = duration
imageView?.animationRepeatCount = 3
imageView?.startAnimating()

GIF图片的显示就介绍到这里

GIF的合成

GIF图片合成思路:

多帧图像合成GIF的过程和GIF分解多帧图像的过程互逆,GIF图片分解过程倒过来推,就是GIF图像合成的过程。
从功能上来说,GIF图片的合成分为以下三个主要部分。
(1)加载待处理的n张原始数据源。
(2)在Document目录下构建GIF文件。
(3)设置GIF文件属性,利用ImageIO编码GIF文件。

1.首先将图片加入到工程中


图片导入到bundle中

然后将读取的图片依次加载到images中。

let bundlePath = Bundle.main.path(forResource: "images", ofType: "bundle")
print("bundlePath === \(bundlePath)")
var images = [UIImage]()

for i in 1 ..< 10 {
    let path = bundlePath?.appending("/\(i).tiff")
    let image = UIImage.init(contentsOfFile: path!)
    images.append(image!)
}

2.构建在Document目录下的GIF文件路径。具体实现如下所示。

//构建在Document目录下的GIF文件路径
let docs = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
let documentsDirectory = docs[0] as String
let gifPath = documentsDirectory+"/mine.gif"
print("gifPath === \(gifPath)")

let url = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, gifPath as CFString, CFURLPathStyle.cfurlposixPathStyle, false)
let destion = CGImageDestinationCreateWithURL(url!, kUTTypeGIF, images.count, nil)
//CGImageDestinationCreateWithURL方法的作用是创建一个图片的目标对象,为了便于大家理解,这里把图片目标对象比喻为一个集合体。 
//集合体中描述了构成当前图片目标对象的一系列参数,如图片的URL地址、图片类型、图片帧数、配置参数等。
//本代码中将mine.gif的本地文件路径作为参数1传递给这个图片目标对象,参数2描述了图片的类型为GIF图片,参数3表明当前GIF图片构成的帧数,参数4暂时给它一个空值。

3.待处理图片源已经加载到代码中,GIF图片Destination也已经完成构建,下面就需要使用ImageIO框架把多帧PNG图片编码到GIF图片中,其处理流程如下。

//设置gif图片属性,利用9张tiff图片构建gif
let cgimagePropertiesDic = [kCGImagePropertyGIFDelayTime as String: 0.1]//设置每帧之间播放时间
let cgimagePropertiesDestDic = [kCGImagePropertyGIFDictionary as String: cgimagePropertiesDic]

for cgimage in images {
    // 依次为gif图像对象添加每一帧元素
    CGImageDestinationAddImage(destion!, cgimage.cgImage!, cgimagePropertiesDestDic as CFDictionary?)
}
let gifPropertiesDic:NSMutableDictionary = NSMutableDictionary()
gifPropertiesDic.setValue(kCGImagePropertyColorModelRGB, forKey: kCGImagePropertyColorModel as String)
gifPropertiesDic.setValue(16, forKey:kCGImagePropertyDepth as String)// 设置图像的颜色深度
gifPropertiesDic.setValue(3, forKey:kCGImagePropertyGIFLoopCount as String)// 设置Gif执行次数, 0则为无限执行
gifPropertiesDic.setValue(NSNumber.init(booleanLiteral: true), forKey: kCGImagePropertyGIFHasGlobalColorMap as String)
let gifDictionaryDestDic = [kCGImagePropertyGIFDictionary as String: gifPropertiesDic]
CGImageDestinationSetProperties(destion!, gifDictionaryDestDic as CFDictionary?)//为gif图像设置属性

CGImageDestinationFinalize(destion!)//最后释放 目标对象 destion

//生成GIF图片成功

这样就生成GIF图片成功了,最后我们来测试一下生成的GIF图片能否成功显示。

//测试一下显示GIF图片
let (images2, duration) = showGif(path: gifPath)!
let animatedImage = UIImage.animatedImage(with: images2, duration: duration)
imageView = UIImageView.init(image: animatedImage)

self.view.addSubview(imageView!)
imageView?.center = self.view.center

运行之后确实是可以显示的

大功告成!

最后的话

最后附上demo 的地址
https://github.com/aichiko/Swift_Diary
喜欢的话可以点赞一下。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,830评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,992评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,875评论 0 331
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,837评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,734评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,091评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,550评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,217评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,368评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,298评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,350评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,027评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,623评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,706评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,940评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,349评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,936评论 2 341

推荐阅读更多精彩内容