Files
SideStore/Pods/Nuke/Sources/ImageDecoding.swift

220 lines
7.9 KiB
Swift

// The MIT License (MIT)
//
// Copyright (c) 2015-2019 Alexander Grebenyuk (github.com/kean).
#if !os(macOS)
import UIKit
#else
import Cocoa
#endif
#if os(watchOS)
import WatchKit
#endif
// MARK: - ImageDecoding
/// Decodes image data.
public protocol ImageDecoding {
/// Produces an image from the image data. A decoder is a one-shot object
/// created for a single image decoding session. If image pipeline has
/// progressive decoding enabled, the `decode(data:isFinal:)` method gets
/// called each time the data buffer has new data available. The decoder may
/// decide whether or not to produce a new image based on the previous scans.
func decode(data: Data, isFinal: Bool) -> Image?
}
// An image decoder that uses native APIs. Supports progressive decoding.
// The decoder is stateful.
public final class ImageDecoder: ImageDecoding {
// `nil` if decoder hasn't detected whether progressive decoding is enabled.
private(set) internal var isProgressive: Bool?
// Number of scans that the decoder has found so far. The last scan might be
// incomplete at this point.
private(set) internal var numberOfScans = 0
private var lastStartOfScan: Int = 0 // Index of the last Start of Scan that we found
private var scannedIndex: Int = -1 // Index at which previous scan was finished
public init() { }
public func decode(data: Data, isFinal: Bool) -> Image? {
let format = ImageFormat.format(for: data)
guard !isFinal else { // Just decode the data.
let image = _decode(data)
if ImagePipeline.Configuration.isAnimatedImageDataEnabled, case .gif? = format { // Keep original data around in case of GIF
image?.animatedImageData = data
}
return image
}
// Determined (if we haven't yet) whether the image supports progressive
// decoding or not (only proressive JPEG is allowed for now, but you can
// add support for other formats by implementing your own decoder).
isProgressive = isProgressive ?? format?.isProgressive
guard isProgressive == true else { return nil }
// Check if there is more data to scan.
guard (scannedIndex + 1) < data.count else { return nil }
// Start scaning from the where we left off previous time.
var index = (scannedIndex + 1)
var numberOfScans = self.numberOfScans
while index < (data.count - 1) {
scannedIndex = index
// 0xFF, 0xDA - Start Of Scan
if data[index] == 0xFF, data[index+1] == 0xDA {
lastStartOfScan = index
numberOfScans += 1
}
index += 1
}
// Found more scans this the previous time
guard numberOfScans > self.numberOfScans else { return nil }
self.numberOfScans = numberOfScans
// `> 1` checks that we've received a first scan (SOS) and then received
// and also received a second scan (SOS). This way we know that we have
// at least one full scan available.
return (numberOfScans > 1 && lastStartOfScan > 0) ? _decode(data[0..<lastStartOfScan]) : nil
}
}
// Image initializers are documented as fully-thread safe:
//
// > The immutable nature of image objects also means that they are safe
// to create and use from any thread.
//
// However, there are some versions of iOS which violated this. The
// `UIImage` is supposably fully thread safe again starting with iOS 10.
//
// The `queue.sync` call below prevents the majority of the potential
// crashes that could happen on the previous versions of iOS.
//
// See also https://github.com/AFNetworking/AFNetworking/issues/2572
private let _queue = DispatchQueue(label: "com.github.kean.Nuke.DataDecoder")
internal func _decode(_ data: Data) -> Image? {
return _queue.sync {
#if os(macOS)
return NSImage(data: data)
#else
#if os(iOS) || os(tvOS)
let scale = UIScreen.main.scale
#else
let scale = WKInterfaceDevice.current().screenScale
#endif
return UIImage(data: data, scale: scale)
#endif
}
}
// MARK: - ImageDecoderRegistry
/// A register of image codecs (only decoding).
public final class ImageDecoderRegistry {
/// A shared registry.
public static let shared = ImageDecoderRegistry()
private var matches = [(ImageDecodingContext) -> ImageDecoding?]()
/// Returns a decoder which matches the given context.
public func decoder(for context: ImageDecodingContext) -> ImageDecoding {
for match in matches {
if let decoder = match(context) {
return decoder
}
}
return ImageDecoder() // Return default decoder if couldn't find a custom one.
}
/// Registers a decoder to be used in a given decoding context. The closure
/// is going to be executed before all other already registered closures.
public func register(_ match: @escaping (ImageDecodingContext) -> ImageDecoding?) {
matches.insert(match, at: 0)
}
func clear() {
matches = []
}
}
/// Image decoding context used when selecting which decoder to use.
public struct ImageDecodingContext {
public let request: ImageRequest
internal let urlResponse: URLResponse?
public let data: Data
}
// MARK: - Image Formats
enum ImageFormat: Equatable {
/// `isProgressive` is nil if we determined that it's a jpeg, but we don't
/// know if it is progressive or baseline yet.
case jpeg(isProgressive: Bool?)
case png
case gif
// Returns `nil` if not enough data.
static func format(for data: Data) -> ImageFormat? {
// JPEG magic numbers https://en.wikipedia.org/wiki/JPEG
if _match(data, [0xFF, 0xD8, 0xFF]) {
var index = 3 // start scanning right after magic numbers
while index < (data.count - 1) {
// A example of first few bytes of progressive jpeg image:
// FF D8 FF E0 00 10 4A 46 49 46 00 01 01 00 00 48 00 ...
//
// 0xFF, 0xC0 - Start Of Frame (baseline DCT)
// 0xFF, 0xC2 - Start Of Frame (progressive DCT)
// https://en.wikipedia.org/wiki/JPEG
if data[index] == 0xFF {
if data[index+1] == 0xC2 { return .jpeg(isProgressive: true) } // progressive
if data[index+1] == 0xC0 { return .jpeg(isProgressive: false) } // baseline
}
index += 1
}
// It's a jpeg but we don't know if progressive or not yet.
return .jpeg(isProgressive: nil)
}
// GIF magic numbers https://en.wikipedia.org/wiki/GIF
if _match(data, [0x47, 0x49, 0x46]) {
return .gif
}
// PNG Magic numbers https://en.wikipedia.org/wiki/Portable_Network_Graphics
if _match(data, [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]) {
return .png
}
// Either not enough data, or we just don't know this format yet.
return nil
}
var isProgressive: Bool? {
if case let .jpeg(isProgressive) = self { return isProgressive }
return false
}
private static func _match(_ data: Data, _ numbers: [UInt8]) -> Bool {
guard data.count >= numbers.count else { return false }
return !zip(numbers.indices, numbers).contains { (index, number) in
data[index] != number
}
}
}
// MARK: - Animated Images
private var _animatedImageDataAK = "Nuke.AnimatedImageData.AssociatedKey"
extension Image {
// Animated image data. Only not `nil` when image data actually contains
// an animated image.
public var animatedImageData: Data? {
get { return objc_getAssociatedObject(self, &_animatedImageDataAK) as? Data }
set { objc_setAssociatedObject(self, &_animatedImageDataAK, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
}
}