APNGKit:用于在iOS和macOS中加载和显示APNG图像的高性能框架

APNGKit 是一个高性能框架,用于在 iOS 和 macOS 中加载和显示 APNG 图像。它是用
高级抽象构建的,并带来了令人愉快的API。因此,在使用APNGKit
播放APNG格式的图像时,您会感到宾至如归和快乐。

APNG,什么以及为什么?

动画便携式网络图形 (APNG) 是一种扩展众所周知的 PNG 格式的
文件格式。它允许动画 PNG 文件的工作方式类似于动画 GIF 文件,同时支持
24 位图像和 GIF 不可用的 8 位透明度。这意味着更好的动画质量。同时
,如果精心创建,文件大小与 GIF 相当甚至更小。

说话很便宜;给我看图像。您可以单击图像以查看动画时的外观。

APNGKit 演示

很酷。APNG要好得多!但是等等...为什么我以前没有听说过 APNG?它不是一种流行的格式,那么我为什么要
在我的下一个伟大的iOS / macOS应用程序中使用它呢?

问得好!APNG 是常规 PNG 的绝佳扩展,使用起来也非常简单,与当前的 PNG 标准不冲突
(它包含一个标准的 PNG 标头,因此如果您的平台不支持 APNG,它将被
识别为普通 PNG,其第一帧显示为静态图像)。但不幸的是,它是一种反叛
格式,因此不被巴布亚新几内亚集团接受。但是,它被许多供应商接受,甚至在W3C标准
被提及。还有另一种称为MNG(多图像网络图形)的
格式,它由与PNG相同的团队创建。这是一个全面的格式,但非常非常非常
(重要的事要说三遍)复杂。它是如此复杂,以至于尽管它是一个“标准”,但它几乎被普遍拒绝。
只有一个名为Konqueror的“流行”浏览器(至少我以前在高中
时用过它)支持MNG,这确实是一个可悲但合理的故事。

Even though APNG is not accepted currently, we continue to see the widespread implementation of it. Apple recently
supported APNG in both desktop and mobile Safari.
Microsoft Edge and Chrome are also considering adding APNG support since it is already officially added in
WebKit core.

APNG is such a nice format to bring users much better experience of animating images. The more APNG is used, the more
recognition and support it will get. Not only in the browsers world, but also in the apps we always love. That's why
I created this framework.

Installation

Requirement

iOS 9.0+ / macOS 10.11+ / tvOS 9.0+

Swift Package Manager

The recommended way to install APNGKit is to use Swift Package Manager. Adding it to your project with Xcode:

CocoaPods

CocoaPods is a dependency manager for Cocoa projects.

$ gem install cocoapods
Bash

To integrate APNGKit into your Xcode project using CocoaPods, specify it in your :Podfile

source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '9.0'
use_frameworks!

target 'your_app' do
  pod 'APNGKit', '~> 2.0'
end
Ruby

Then, run the following command:

$ pod install
Bash

You should open the instead of the after you installed anything from CocoaPods.{Project}.xcworkspace{Project}.xcodeproj

For more information about how to use CocoaPods, I suggest this tutorial.

Usage

Basic

Import APNGKit into your source files in which you want to use the framework.

import APNGKit
Swift

Load an APNG Image

// 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)
Swift

You may notice that all initializers are throwable. If anything is wrong during creating the image, it let you know the
error explicitly and you have a chance to handle it. We will cover the error handling soon later.

Display an APNG Image

When you have an object, you can use it to initialize an image view and display it on screen with an
, which is a subclass of or :
APNGImageAPNGImageViewUIViewNSView

let image: APNGImage = ... // You already have an APNG image object.

let imageView = APNGImageView(image: image)
view.addSubview(imageView)
Swift

Start animation

The animation will be played automatically as soon as the image view is created with a valid APNG image. If you do not
want the animation to be played automatically, set the property to before you
assign an image:
autoStartAnimationWhenSetImagefalse

let imageView = APNGImageView(frame: .zero)
imageView.autoStartAnimationWhenSetImage = false
imageView.image = image

// Start the animation manually:
imageView.startAnimating()
Swift

Image owner

DO NOT set the same image to multiple image views, which is not allowed in APNGKit. Every created has an
owner, you cannot set multiple owners to it, otherwise it confuses how to sync with the screen. To display the same
image, create a new one:
APNGImage

let image = try APNGImage(named: "image")
imageView.image = image

// You cannot set the same image to another image view.
// This causes an error.
anotherImageView.image = image

// You can create another one and set it.
// This works.
let anotherImage = try APNGImage(named: "image")
anotherImageView.image = anotherImage

// Or first remove the original owner.
// This works
imageView.image = nil
anotherImageView.image = image
Swift

XIB or Storyboard

If you are an Interface Builder lover, drag a (or ) (Please note, not a or )
to the canvas, and modify its class to . Then, you can drag an and play with it as usual, such
as setting its property.
UIViewNSViewUIImageViewNSImageViewAPNGImageViewIBOutletimage

Delegates

APNG defines the play loop count as in , and APNGKit respects it by default. To inspect the
end of each loop, register yourself as a delegate of :
numberOfPlaysAPNGImageAPNGImageView.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)")
        }
    }
}
Swift

When is , the animation will be played forever. If it is a limited non-zero value, the animation
will be stopped at the final frame when the loop count reaches the limit. To inspect the whole animation is done,
use :
numberOfPlaysnilonAllPlaysDone

imageView.onAllPlaysDone.delegate(on: self) { (self, _) in
    print("All done.")
}
Swift

APNGKit loads the data in a streaming way by default, it reads the frame information while playing the animation. Since
APNG encodes the duration in each frame, it is not possible to get the whole animation duration before loading all frames
information. Before the first reading pass finishes, you can only get a partial duration for loaded frames. To get the
full duration, use :
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
Swift

Or, you can specify the option while creating the . It reads all frames before starting
rendering and animating the image:
.fullFirstPassAPNGImage

let image = try APNGImage(named: "image", options: [.fullFirstPass])
print(image.duration) // .full(duration)
Swift

APNGKit provides a few other reading options. Please let me skip it for now and you can check them in documentation.

Error handling

While creating image

Creating an can throw an error if anything goes wrong. All possible errors while decoding are defined as an
. When an error happens while creating the image, you are expected to check if it should be
treated as a normal static image. If so, try to set it as the static image:
APNGImageAPNGKitError.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)")
    }
}
Swift

While playing animation

If some frames are broken, the default image defined in APNG should be displayed as a fallback. You can get this in
APNGKit for free. To get notified when this happens, listen to :
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)")
}
Swift

PNG compression

Xcode will compress all PNG files in your app bundle when you build the project. Since APNG is an extension format of
PNG, Xcode will think there are redundancy data in that file and compress it into a single static image. When this happens,
you may inspect a log message from APNGKit:

CgBI chunk found. It seems that the input image is compressed by Xcode and not supported by APNGKit.
Consider to rename it to to prevent compressing.
apng

Usually this is not what you want when working with APNG. You can disable the PNG compression by setting
"COMPRESS_PNG_FILES" to NO in the build settings of your app target. However, it will also prevent Xcode to optimize
your other regular PNGs.

A better approach would be renaming your APNG files with an extension besides of "png". If you do so, Xcode will stop
recognizing your APNG files as PNG format, and will not apply compression on them. A suggested extension is "apng",
which will be detected and handled by APNGKit seamlessly.

Acknowledgement

The demo elephant image in README file is stolen from ICS Lab, you can find the original post here.

Reference

If you are interested in APNG, you can know more about it from the links below (some of them are written in Chinese).

APNGKit can only load and display APNG image now. The creating feature will be developed later. If you need to create APNG file now, I suggest using iSparta or apngasm instead for now.

License

APNGKit is released under the MIT license. See LICENSE for details.

GitHub

https://github.com/onevcat/APNGKit