Gifu/Source/ImageSourceHelpers.swift

78 lines
2.8 KiB
Swift
Executable File

import ImageIO
import MobileCoreServices
import UIKit
typealias GIFProperties = [String : Double]
let defaultDuration: Double = 0
/// Retruns the duration of a frame at a specific index using an image source (an `CGImageSource` instance).
///
/// - returns: A frame duration.
func CGImageSourceGIFFrameDuration(imageSource: CGImageSource, index: Int) -> NSTimeInterval {
if !imageSource.isAnimatedGIF { return 0.0 }
guard let properties = imageSource.GIFPropertiesAtIndex(index),
let duration = durationFromGIFProperties(properties),
let cappedDuration = capDuration(duration)
else { return defaultDuration }
return cappedDuration
}
/// Ensures that a duration is never smaller than a threshold value.
///
/// - returns: A capped frame duration.
func capDuration(duration: Double) -> Double? {
if duration < 0 { return .None }
let threshold = 0.02 - Double(FLT_EPSILON)
let cappedDuration = duration < threshold ? 0.1 : duration
return cappedDuration
}
/// Returns a frame duration from a `GIFProperties` dictionary.
///
/// - returns: A frame duration.
func durationFromGIFProperties(properties: GIFProperties) -> Double? {
guard let unclampedDelayTime = properties[String(kCGImagePropertyGIFUnclampedDelayTime)],
let delayTime = properties[String(kCGImagePropertyGIFDelayTime)]
else { return .None }
return duration(unclampedDelayTime, delayTime: delayTime)
}
/// Calculates frame duration based on both clamped and unclamped times.
///
/// - returns: A frame duration.
func duration(unclampedDelayTime: Double, delayTime: Double) -> Double {
let delayArray = [unclampedDelayTime, delayTime]
return delayArray.filter(isPositive).first ?? defaultDuration
}
/// Checks if a `Double` value is positive.
///
/// - returns: A boolean value that is `true` if the tested value is positive.
func isPositive(value: Double) -> Bool {
return value >= 0
}
/// An extension of `CGImageSourceRef` that add GIF introspection and easier property retrieval.
extension CGImageSourceRef {
/// Returns whether the image source contains an animated GIF.
///
/// - returns: A boolean value that is `true` if the image source contains animated GIF data.
var isAnimatedGIF: Bool {
let isTypeGIF = UTTypeConformsTo(CGImageSourceGetType(self) ?? "", kUTTypeGIF)
let imageCount = CGImageSourceGetCount(self)
return isTypeGIF != false && imageCount > 1
}
/// Returns the GIF properties at a specific index.
///
/// - parameter index: The index of the GIF properties to retrieve.
/// - returns: A dictionary containing the GIF properties at the passed in index.
func GIFPropertiesAtIndex(index: Int) -> GIFProperties? {
let imageProperties = CGImageSourceCopyPropertiesAtIndex(self, index, nil) as Dictionary?
return imageProperties?[String(kCGImagePropertyGIFDictionary)] as? GIFProperties
}
}