Gifu/Source/Animator.swift

111 lines
4.1 KiB
Swift
Raw Normal View History

import UIKit
import ImageIO
2015-01-20 19:44:15 +00:00
import Runes
2015-01-23 00:02:08 +00:00
/// Responsible for storing and updating the frames of a `AnimatableImageView` instance via delegation.
2015-06-04 23:36:47 +00:00
class Animator {
2015-01-23 00:02:08 +00:00
/// Maximum duration to increment the frame timer with.
private let maxTimeStep = 1.0
/// An array of animated frames from a single GIF image.
private var animatedFrames = [AnimatedFrame]()
2015-06-04 23:24:34 +00:00
/// The size to resize all frames to
private let size: CGSize
/// The content mode to use when resizing
private let contentMode: UIViewContentMode
/// Maximum number of frames to load at once
2015-06-04 23:36:47 +00:00
private let maxNumberOfFrames: Int
/// The total number of frames in the GIF.
private var numberOfFrames = 0
/// A reference to the original image source.
private var imageSource: CGImageSourceRef
2015-01-23 00:02:08 +00:00
/// The index of the current GIF frame.
private var currentFrameIndex = 0
/// The index of the current GIF frame from the source.
private var currentPreloadIndex = 0
2015-01-23 00:02:08 +00:00
/// Time elapsed since the last frame change. Used to determine when the frame should be updated.
private var timeSinceLastFrameChange: NSTimeInterval = 0.0
2015-01-23 00:02:08 +00:00
/// The current image frame to show.
var currentFrame: UIImage? {
return frameAtIndex(currentFrameIndex)
}
2015-06-04 23:24:34 +00:00
/// Is this image animatable?
var isAnimatable: Bool {
return imageSource.isAnimatedGIF
}
2015-01-23 00:02:08 +00:00
/// Initializes an animator instance from raw GIF image data and an `Animatable` delegate.
///
/// :param: data The raw GIF image data.
/// :param: delegate An `Animatable` delegate.
2015-06-04 23:36:47 +00:00
init(data: NSData, size: CGSize, contentMode: UIViewContentMode, framePreloadCount: Int) {
2015-06-04 23:24:34 +00:00
let options = [String(kCGImageSourceShouldCache): kCFBooleanFalse]
imageSource = CGImageSourceCreateWithData(data, options)
self.size = size
self.contentMode = contentMode
2015-06-04 23:36:47 +00:00
maxNumberOfFrames = framePreloadCount
}
2015-01-22 10:54:27 +00:00
// MARK: - Frames
2015-01-23 00:02:08 +00:00
/// Loads the frames from an image source, resizes them, then caches them in `animatedFrames`.
2015-06-04 23:36:47 +00:00
func prepareFrames() {
numberOfFrames = Int(CGImageSourceGetCount(imageSource))
2015-06-04 23:36:47 +00:00
let framesToProcess = min(numberOfFrames, maxNumberOfFrames)
animatedFrames.reserveCapacity(framesToProcess)
animatedFrames = reduce(0..<framesToProcess, []) { $0 + pure(prepareFrame($1)) }
currentPreloadIndex = framesToProcess
}
2014-12-12 21:49:15 +00:00
/// Loads a single frame from an image source, resizes it, then returns an `AnimatedFrame`.
///
/// :param: index The index of the GIF image source to prepare
/// :returns: An AnimatedFrame object
private func prepareFrame(index: Int) -> AnimatedFrame {
let frameDuration = CGImageSourceGIFFrameDuration(imageSource, index)
let frameImageRef = CGImageSourceCreateImageAtIndex(imageSource, index, nil)
let image = UIImage(CGImage: frameImageRef)
let scaledImage: UIImage?
2015-06-04 23:24:34 +00:00
switch contentMode {
case .ScaleAspectFit: scaledImage = image?.resizeAspectFit(size)
case .ScaleAspectFill: scaledImage = image?.resizeAspectFill(size)
default: scaledImage = image?.resize(size)
}
return AnimatedFrame(image: scaledImage, duration: frameDuration)
}
2015-01-23 00:02:08 +00:00
/// Returns the frame at a particular index.
///
/// :param: index The index of the frame.
/// :returns: An optional image at a given frame.
private func frameAtIndex(index: Int) -> UIImage? {
return animatedFrames[index].image
}
2015-01-23 00:02:08 +00:00
/// Updates the current frame if necessary using the frame timer and the duration of each frame in `animatedFrames`.
///
/// :returns: An optional image at a given frame.
2015-06-04 23:24:34 +00:00
func updateCurrentFrame(duration: CFTimeInterval) -> Bool {
timeSinceLastFrameChange += min(maxTimeStep, duration)
var frameDuration = animatedFrames[currentFrameIndex].duration
if timeSinceLastFrameChange >= frameDuration {
timeSinceLastFrameChange -= frameDuration
let lastFrameIndex = currentFrameIndex
currentFrameIndex = ++currentFrameIndex % animatedFrames.count
2015-06-04 23:36:47 +00:00
// Loads the next needed frame for progressive loading
if animatedFrames.count < numberOfFrames {
animatedFrames[lastFrameIndex] = prepareFrame(currentPreloadIndex)
currentPreloadIndex = ++currentPreloadIndex % numberOfFrames
}
2015-06-04 23:24:34 +00:00
return true
}
2015-06-04 23:24:34 +00:00
return false
}
}