通过 iOS 创建和导出动画 gif?
Posted
技术标签:
【中文标题】通过 iOS 创建和导出动画 gif?【英文标题】:Create and export an animated gif via iOS? 【发布时间】:2013-02-01 15:13:32 【问题描述】:我在 ios 应用程序中有一系列用户自定义的图像,这些图像以简单的逐帧翻书样式进行动画处理。
我的问题是:有没有办法让用户将他们的动画导出为动画 gif?理想情况下,我希望他们能够通过电子邮件、社交分享 (T/FB) 或(最坏的情况......)将动画 gif 保存到他们的文档文件夹中,以便通过 iTunes 检索。
我知道如何将 .png 保存到照片库中,并且我找到了一种将动画录制为 QT 文件的方法 (http://www.cimgf.com/2009/02/03/record-your-core-animation-animation/),但我还没有找到一种方法来踢出一个普通的旧文件动画 gif。我在核心动画或其他地方遗漏了什么吗?是否有任何人可以推荐的方法、框架或资源?对不起,如果问题太笼统 - 很难找到一个起点。
【问题讨论】:
【参考方案1】:您可以使用 Image I/O 框架(它是 iOS SDK 的一部分)创建动画 GIF。您还需要包含MobileCoreServices
框架,它定义了 GIF 类型常量。您需要将这些框架添加到您的目标中,并将它们的标题导入您要创建动画 GIF 的文件中,如下所示:
#import <ImageIO/ImageIO.h>
#import <MobileCoreServices/MobileCoreServices.h>
用例子来解释是最容易的。我将向您展示我用来在 iPhone 5 上制作此 GIF 的代码:
首先,这里有一个辅助函数,它接受一个大小和一个角度,并返回那个角度的红色圆盘的UIImage
:
static UIImage *frameImage(CGSize size, CGFloat radians)
UIGraphicsBeginImageContextWithOptions(size, YES, 1);
[[UIColor whiteColor] setFill];
UIRectFill(CGRectInfinite);
CGContextRef gc = UIGraphicsGetCurrentContext();
CGContextTranslateCTM(gc, size.width / 2, size.height / 2);
CGContextRotateCTM(gc, radians);
CGContextTranslateCTM(gc, size.width / 4, 0);
[[UIColor redColor] setFill];
CGFloat w = size.width / 10;
CGContextFillEllipseInRect(gc, CGRectMake(-w / 2, -w / 2, w, w));
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
现在我们可以创建 GIF。首先,我们将定义一个帧数常量,因为我们以后需要两次:
static void makeAnimatedGif(void)
static NSUInteger const kFrameCount = 16;
我们需要一个属性字典来指定动画应该重复的次数:
NSDictionary *fileProperties = @
(__bridge id)kCGImagePropertyGIFDictionary: @
(__bridge id)kCGImagePropertyGIFLoopCount: @0, // 0 means loop forever
;
我们需要另一个属性字典,我们将把它附加到每个帧上,指定该帧应该显示多长时间:
NSDictionary *frameProperties = @
(__bridge id)kCGImagePropertyGIFDictionary: @
(__bridge id)kCGImagePropertyGIFDelayTime: @0.02f, // a float (not double!) in seconds, rounded to centiseconds in the GIF data
;
我们还将在文档目录中为 GIF 创建一个 URL:
NSURL *documentsDirectoryURL = [[NSFileManager defaultManager] URLForDirectory:NSDocumentDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:YES error:nil];
NSURL *fileURL = [documentsDirectoryURL URLByAppendingPathComponent:@"animated.gif"];
现在我们可以创建一个CGImageDestination
,将 GIF 写入指定的 URL:
CGImageDestinationRef destination = CGImageDestinationCreateWithURL((__bridge CFURLRef)fileURL, kUTTypeGIF, kFrameCount, NULL);
CGImageDestinationSetProperties(destination, (__bridge CFDictionaryRef)fileProperties);
我发现将fileProperties
作为CGImageDestinationCreateWithURL
的最后一个参数传递不 工作。你必须使用CGImageDestinationSetProperties
。
现在我们可以创建和编写我们的框架了:
for (NSUInteger i = 0; i < kFrameCount; i++)
@autoreleasepool
UIImage *image = frameImage(CGSizeMake(300, 300), M_PI * 2 * i / kFrameCount);
CGImageDestinationAddImage(destination, image.CGImage, (__bridge CFDictionaryRef)frameProperties);
请注意,我们将帧属性字典与每个帧图像一起传递。
在我们准确地添加了指定数量的帧之后,我们最终确定了目标并释放它:
if (!CGImageDestinationFinalize(destination))
NSLog(@"failed to finalize image destination");
CFRelease(destination);
NSLog(@"url=%@", fileURL);
如果您在模拟器上运行此程序,您可以从调试控制台复制 URL 并将其粘贴到浏览器中以查看图像。如果您在设备上运行它,您可以使用 Xcode Organizer 窗口从设备下载应用程序沙箱并查看图像。或者,您可以使用像 iExplorer
这样的应用程序,让您直接浏览设备的文件系统。 (这不需要越狱。)
我在运行 iOS 6.1 的 iPhone 5 上对此进行了测试,但我相信代码应该可以追溯到 iOS 4.0。
我已将所有代码放在this gist 中以便于复制。
【讨论】:
Rob,我今天让你的技术奏效了,但是有一个很大的限制。系统将动画序列中所有图像的数据保留在内存中,直到完成调用完成。我正在创建一些相当大的 GIF 序列,并且在 30 FPS 时很快就会耗尽内存并崩溃。有没有办法以某种方式将帧缓冲到磁盘?我正在从 OpenGL 创建我的 CGImages,并使用数据提供程序。有一种数据提供者可以从文件中读取其内容。我想知道它是否会依次读取每一帧并在完成 GIF 时释放它。 快速搜索和检查让我认为Giraffe 可能会做你想做的事。 @robmayoff 嗨 Rob,我使用你的代码已经有一段时间了,但我发现了一个奇怪的错误,如果彼此相邻的帧相同,我总是在 CGImageDestinationFinalize(destination) 获得 EXE_BAD_ACCESS在输入序列中。知道为什么会这样吗? @Pascal 这是一个系统错误,序列的末尾不能有相同的帧,即如果你有 n 帧,第 n 帧和第 n 帧不能相同。我最终修剪了序列的尾部以获得相同的帧。 (而且我使用一种非常幼稚的方式来检测相同的帧、文件大小以及 EXIF 标签中的时间戳,对我来说效果很好......) @kevin 检查this question 及其cmets。我没有测试过,但我没有理由怀疑这些信息。【参考方案2】:对于 Swift 3
import Foundation
import UIKit
import ImageIO
import MobileCoreServices
extension UIImage
static func animatedGif(from images: [UIImage])
let fileProperties: CFDictionary = [kCGImagePropertyGIFDictionary as String: [kCGImagePropertyGIFLoopCount as String: 0]] as CFDictionary
let frameProperties: CFDictionary = [kCGImagePropertyGIFDictionary as String: [(kCGImagePropertyGIFDelayTime as String): 1.0]] as CFDictionary
let documentsDirectoryURL: URL? = try? FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
let fileURL: URL? = documentsDirectoryURL?.appendingPathComponent("animated.gif")
if let url = fileURL as CFURL?
if let destination = CGImageDestinationCreateWithURL(url, kUTTypeGIF, images.count, nil)
CGImageDestinationSetProperties(destination, fileProperties)
for image in images
if let cgImage = image.cgImage
CGImageDestinationAddImage(destination, cgImage, frameProperties)
if !CGImageDestinationFinalize(destination)
print("Failed to finalize the image destination")
print("Url = \(fileURL)")
我已经从above answer 转换了它。希望对你有帮助。
以gist 的形式提供。
欢迎编辑。
【讨论】:
一个有趣的替代方法是在Array
的扩展中实现此方法,其中Element == UIImage
。
好主意@NicolasMiari【参考方案3】:
如果您正在寻找 Swift 3 解决方案,可以查看https://github.com/onmyway133/GifMagic。它有Encoder
和Decoder
可以组装和反汇编gif 文件。
基本上,你应该使用Image IO
框架和这些函数CGImageDestinationCreateWithURL
,CGImageDestinationSetProperties
,CGImageDestinationAddImage
,CGImageDestinationFinalize
还有 Swift https://developer.apple.com/library/content/documentation/Swift/Conceptual/BuildingCocoaApps/WorkingWithCocoaDataTypes.html
从带注释的 API 返回的 Core Foundation 对象在 Swift 中自动进行内存管理——您无需自己调用 CFRetain、CFRelease 或 CFAutorelease 函数。
【讨论】:
以上是关于通过 iOS 创建和导出动画 gif?的主要内容,如果未能解决你的问题,请参考以下文章
iOS启动图-从网络获取的gif图,在本地一直是没有动画,还模糊的