APNGKit 是一个高性能框架,用于在 iOS 和 macOS 中加载和显示 APNG 图像。它基于高级抽象构建,并提供了令人愉悦的 API。因此,当您使用 APNGKit 处理 APNG 格式的图像时,您会感到宾至如归,并充满乐趣。
动画便携式网络图形 (APNG) 是一种文件格式,它是对广为人知的 PNG 格式的扩展。它允许使用动画 PNG 文件,其工作方式类似于动画 GIF 文件,同时支持 GIF 不支持的 24 位图像和 8 位透明度。这意味着更高的动画质量。与此同时,如果创建得当,其文件大小与 GIF 相当甚至更小。
空谈无益,有图为证。您可以点击图像,查看动画效果。
这太酷了。APNG 确实更好!但是等等……为什么我以前从未听说过 APNG?它不是一种流行的格式,那么为什么我应该在我的下一个伟大的 iOS/macOS 应用程序中使用它呢?
好问题!APNG 是对常规 PNG 的出色扩展,它也非常易于使用,并且与当前的 PNG 标准不冲突(它包含标准的 PNG 标头,因此如果您的平台不支持 APNG,它将被识别为普通的 PNG,其第一帧将显示为静态图像)。但不幸的是,它是一种非主流格式,因此未被 PNG 组织接受。但是,它已被许多供应商接受,甚至在 W3C 标准中被提及。还有另一种格式称为 MNG(多图像网络图形),它是由与 PNG 同一个团队创建的。它是一种全面的格式,但非常非常非常(重要的事要说三遍)复杂。它非常复杂,以至于尽管它是一个“标准”,但几乎被普遍拒绝。只有一款“流行”的浏览器叫做 Konqueror(至少我在高中时用过它)支持 MNG,这真是一个悲伤但合理的故事。
即使 APNG 目前尚未被接受,我们仍然看到它的广泛实现。苹果最近在 桌面和移动 Safari 中都支持了 APNG。Microsoft Edge 和 Chrome 也在考虑添加 APNG 支持,因为它已经正式添加到 WebKit 核心中。
APNG 是一种很好的格式,可以为用户带来更好的动画图像体验。APNG 使用得越多,它将获得更多的认可和支持。不仅在浏览器领域,而且在我们一直喜爱的应用程序中也是如此。这就是我创建这个框架的原因。
iOS 9.0+ / macOS 10.11+ / tvOS 9.0+
安装 APNGKit 的 推荐方式 是使用 Swift Package Manager。使用 Xcode 将其添加到您的项目
CocoaPods 是 Cocoa 项目的依赖管理器。
$ gem install cocoapods
要使用 CocoaPods 将 APNGKit 集成到您的 Xcode 项目中,请在您的 Podfile
中指定它
source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '9.0'
use_frameworks!
target 'your_app' do
pod 'APNGKit', '~> 2.0'
end
然后,运行以下命令
$ pod install
在您从 CocoaPods 安装任何内容后,您应该打开 {Project}.xcworkspace
而不是 {Project}.xcodeproj
。
有关如何使用 CocoaPods 的更多信息,我建议您阅读 这篇教程。
将 APNGKit 导入到您想要使用该框架的源文件中。
import APNGKit
// Load an APNG image from file in main bundle
var image = try APNGImage(named: "your_image")
// Or
// Load an APNG image from file at specified path
if let url = Bundle.main.url(forResource: "your_image", withExtension: "apng") {
image = try APNGImage(fileURL: path)
}
// Or
// Load an APNG image from data
let data: Data = ... // From disk or network or anywhere else.
image = try APNGImage(data: data)
您可能会注意到所有初始化器都可能抛出错误。如果在创建图像期间出现任何问题,它会明确告知您错误,您有机会处理它。我们稍后将介绍错误处理。
当您拥有一个 APNGImage
对象时,您可以使用它来初始化一个图像视图,并使用 APNGImageView
在屏幕上显示它,APNGImageView
是 UIView
或 NSView
的子类
let image: APNGImage = ... // You already have an APNG image object.
let imageView = APNGImageView(image: image)
view.addSubview(imageView)
动画将在使用有效的 APNG 图像创建图像视图后立即自动播放。如果您不希望动画自动播放,请在分配图像之前将 autoStartAnimationWhenSetImage
属性设置为 false
let imageView = APNGImageView(frame: .zero)
imageView.autoStartAnimationWhenSetImage = false
imageView.image = image
// Start the animation manually:
imageView.startAnimating()
如果您是 Interface Builder 爱好者,请将一个 UIView
(或 NSView
)(请注意,不是 UIImageView
或 NSImageView
)拖到画布上,并将其类修改为 APNGImageView
。然后,您可以像往常一样拖动一个 IBOutlet
并使用它,例如设置其 image
属性。
APNG 在 APNGImage
中将播放循环计数定义为 numberOfPlays
,APNGKit 默认遵循它。要检查每个循环的结束,请将自己注册为 APNGImageView.onOnePlayDone
的委托
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let imageView = APNGImageView(image: image)
imageView.onOnePlayDone.delegate(on: self) { (self, count) in
print("Played: \(count)")
}
}
}
当 numberOfPlays
为 nil
时,动画将永远播放。如果它是一个有限的非零值,则当循环计数达到限制时,动画将在最后一帧停止。要检查整个动画是否完成,请使用 onAllPlaysDone
imageView.onAllPlaysDone.delegate(on: self) { (self, _) in
print("All done.")
}
APNGKit 默认以流式方式加载数据,它在播放动画时读取帧信息。由于 APNG 在每一帧中编码了持续时间,因此在加载所有帧信息之前,无法获得整个动画的持续时间。在第一次读取过程完成之前,您只能获得已加载帧的部分持续时间。要获得完整持续时间,请使用 APNGImage.onFramesInformationPrepared
let image = try APNGImage(named: "image")
image.onFramesInformationPrepared.delegate(on: self) { (self, _) in
switch image.duration {
case .full(let duration):
print("Full duration: \(duration)")
case .partial:
print("This should not happen.")
}
}
imageView.image = image
或者,您可以在创建 APNGImage
时指定 .fullFirstPass
选项。它在开始渲染和动画图像之前读取所有帧
let image = try APNGImage(named: "image", options: [.fullFirstPass])
print(image.duration) // .full(duration)
APNGKit 提供了其他一些读取选项。请允许我现在跳过它,您可以在文档中查看它们。
如果出现任何问题,创建 APNGImage
可能会抛出错误。解码时所有可能的错误都定义为 APNGKitError.decoderError
。当创建图像时发生错误时,您应该检查是否应将其视为普通静态图像。如果是,请尝试将其设置为静态图像
do {
let image = try APNGImage(named: data.imageName)
imageView.image = image
} catch {
if let normalImage = error.apngError?.normalImage {
imageView.staticImage = normalImage
} else {
print("Error: \(error)")
}
}
如果某些帧损坏,则应显示 APNG 中定义的默认图像作为回退。您可以在 APNGKit 中免费获得此功能。要在此情况发生时收到通知,请监听 APNGImageView.onFallBackToDefaultImage
imageView.onDecodingFrameError.delegate(on: self) { (self, error) in
print("A frame cannot be decoded. After this, either onFallBackToDefaultImage or onFallBackToDefaultImageFailed happens.")
}
imageView.onFallBackToDefaultImage.delegate(on: self) { (self, _) in
print("Fall back to default image.")
}
imageView.onFallBackToDefaultImageFailed.delegate(on: self) { (self, error) in
print("Tried to fall back to default image, but it fails: \(error)")
}
当您构建项目时,Xcode 将压缩您的应用程序包中的所有 PNG 文件。由于 APNG 是 PNG 的扩展格式,Xcode 会认为该文件中存在冗余数据,并将其压缩为单个静态图像。当这种情况发生时,您可能会在 APNGKit 中看到一条日志消息
CgBI
chunk found. It seems that the input image is compressed by Xcode and not supported by APNGKit. Consider to rename it toapng
to prevent compressing.
通常,这并不是您在使用 APNG 时所期望的。您可以通过在应用程序目标的构建设置中将 “COMPRESS_PNG_FILES” 设置为 NO 来禁用 PNG 压缩。但是,它也会阻止 Xcode 优化您的其他常规 PNG。
更好的方法是将您的 APNG 文件重命名为 “png” 以外的扩展名。如果您这样做,Xcode 将停止将您的 APNG 文件识别为 PNG 格式,并且不会对它们应用压缩。建议的扩展名是 “apng”,APNGKit 将无缝检测和处理它。
README 文件中的演示大象图像是从 ICS Lab 盗用的,您可以在 这里 找到原始帖子。
如果您对 APNG 感兴趣,您可以从以下链接了解更多信息(其中一些链接是用中文编写的)。
APNGKit 现在只能加载和显示 APNG 图像。创建功能将在稍后开发。如果您现在需要创建 APNG 文件,我建议您暂时使用 iSparta 或 apngasm。
APNGKit 在 MIT 许可证下发布。有关详细信息,请参阅 LICENSE。