You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

428 lines
14 KiB

// The MIT License (MIT)
//
// Copyright (c) 2015-2021 Alexander Grebenyuk (github.com/kean).
#if !os(macOS)
import UIKit
#else
import Cocoa
#endif
#if os(watchOS)
import WatchKit
#endif
// MARK: - ImageDecoding
/// An image decoder.
///
/// A decoder is a one-shot object created for a single image decoding session.
///
/// - note: If you need additional information in the decoder, you can pass
/// anything that you might need from the `ImageDecodingContext`.
public protocol ImageDecoding {
/// Produces an image from the given image data.
func decode(_ data: Data) -> ImageContainer?
/// Produces an image from the given partially dowloaded image data.
/// This method might be called multiple times during a single decoding
/// session. When the image download is complete, `decode(data:)` method is called.
///
/// - returns: nil by default.
func decodePartiallyDownloadedData(_ data: Data) -> ImageContainer?
}
public extension ImageDecoding {
/// The default implementation which simply returns `nil` (no progressive
/// decoding available).
func decodePartiallyDownloadedData(_ data: Data) -> ImageContainer? {
nil
}
}
extension ImageDecoding {
func decode(_ data: Data, urlResponse: URLResponse?, isCompleted: Bool) -> ImageResponse? {
func _decode() -> ImageContainer? {
if isCompleted {
return decode(data)
} else {
return decodePartiallyDownloadedData(data)
}
}
guard let container = autoreleasepool(invoking: _decode) else {
return nil
}
#if !os(macOS)
ImageDecompression.setDecompressionNeeded(true, for: container.image)
#endif
return ImageResponse(container: container, urlResponse: urlResponse)
}
}
public typealias ImageDecoder = ImageDecoders.Default
// MARK: - ImageDecoders
public enum ImageDecoders {}
// MARK: - ImageDecoders.Default
// An image decoder that uses native APIs. Supports progressive decoding.
// The decoder is stateful.
public extension ImageDecoders {
/// The default decoder which supports all of the formats natively supported
/// by the system.
///
/// - note: The decoder automatically sets the scale of the decoded images to
/// match the scale of the screen.
///
/// - note: The default decoder supports progressive JPEG. It produces a new
/// preview every time it encounters a new full frame.
final class Default: ImageDecoding, ImageDecoderRegistering {
// Number of scans that the decoder has found so far. The last scan might be
// incomplete at this point.
var numberOfScans: Int { scanner.numberOfScans }
private var scanner = ProgressiveJPEGScanner()
/// A user info key to get the scan number (Int).
public static let scanNumberKey = "ImageDecoders.Default.scanNumberKey"
private var container: ImageContainer?
public init() { }
public init?(data: Data, context: ImageDecodingContext) {
guard let container = _decode(data) else {
return nil
}
self.container = container
}
public init?(partiallyDownloadedData data: Data, context: ImageDecodingContext) {
// Determined 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).
guard ImageType(data) == .jpeg,
ImageProperties.JPEG(data)?.isProgressive == true else {
return nil
}
}
public func decode(_ data: Data) -> ImageContainer? {
container ?? _decode(data)
}
private func _decode(_ data: Data) -> ImageContainer? {
guard let image = ImageDecoders.Default._decode(data) else {
return nil
}
// Keep original data around in case of GIF
let type = ImageType(data)
if ImagePipeline.Configuration._isAnimatedImageDataEnabled, type == .gif {
image._animatedImageData = data
}
var container = ImageContainer(image: image, data: image._animatedImageData)
container.type = type
if type == .gif {
container.data = data
}
if numberOfScans > 0 {
container.userInfo[ImageDecoders.Default.scanNumberKey] = numberOfScans
}
return container
}
public func decodePartiallyDownloadedData(_ data: Data) -> ImageContainer? {
guard let endOfScan = scanner.scan(data), endOfScan > 0 else {
return nil
}
guard let image = ImageDecoder._decode(data[0...endOfScan]) else {
return nil
}
return ImageContainer(image: image, type: .jpeg, isPreview: true, userInfo: [ImageDecoders.Default.scanNumberKey: numberOfScans])
}
}
}
private struct ProgressiveJPEGScanner {
// Number of scans that the decoder has found so far. The last scan might be
// incomplete at this point.
private(set) var numberOfScans = 0
private var lastStartOfScan: Int = 0 // Index of the last found Start of Scan
private var scannedIndex: Int = -1 // Index at which previous scan was finished
/// Scans the given data. If finds new scans, returns the last index of the
/// last available scan.
mutating func scan(_ data: Data) -> Int? {
// Check if there is more data to scan.
guard (scannedIndex + 1) < data.count else {
return nil
}
// Start scaning from the where it 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.
guard numberOfScans > 1 && lastStartOfScan > 0 else {
return nil
}
return lastStartOfScan - 1
}
}
extension ImageDecoders.Default {
static func _decode(_ data: Data) -> PlatformImage? {
#if os(macOS)
return NSImage(data: data)
#else
return UIImage(data: data, scale: Screen.scale)
#endif
}
}
// MARK: - ImageDecoders.Empty
public extension ImageDecoders {
/// A decoder which returns an empty placeholder image and attaches image
/// data to the image container.
struct Empty: ImageDecoding {
public let isProgressive: Bool
/// - parameter isProgressive: If `false`, returns nil for every progressive
/// scan. `false` by default.
public init(isProgressive: Bool = false) {
self.isProgressive = isProgressive
}
public func decodePartiallyDownloadedData(_ data: Data) -> ImageContainer? {
isProgressive ? ImageContainer(image: PlatformImage(), data: data, userInfo: [:]) : nil
}
public func decode(_ data: Data) -> ImageContainer? {
ImageContainer(image: PlatformImage(), data: data, userInfo: [:])
}
}
}
// MARK: - ImageDecoderRegistering
/// An image decoder which supports automatically registering in the decoder register.
public protocol ImageDecoderRegistering: ImageDecoding {
/// Returns non-nil if the decoder can be used to decode the given data.
///
/// - parameter data: The same data is going to be delivered to decoder via
/// `decode(_:)` method. The same instance of the decoder is going to be used.
init?(data: Data, context: ImageDecodingContext)
/// Returns non-nil if the decoder can be used to progressively decode the
/// given partially downloaded data.
///
/// - parameter data: The first and the next data chunks are going to be
/// delivered to the decoder via `decodePartiallyDownloadedData(_:)` method.
init?(partiallyDownloadedData data: Data, context: ImageDecodingContext)
}
public extension ImageDecoderRegistering {
/// The default implementation which simply returns `nil` (no progressive
/// decoding available).
init?(partiallyDownloadedData data: Data, context: ImageDecodingContext) {
return nil
}
}
// MARK: - ImageDecoderRegistry
/// A register of image codecs (only decoding).
public final class ImageDecoderRegistry {
/// A shared registry.
public static let shared = ImageDecoderRegistry()
private struct Match {
let closure: (ImageDecodingContext) -> ImageDecoding?
}
private var matches = [Match]()
public init() {
self.register(ImageDecoders.Default.self)
}
/// Returns a decoder which matches the given context.
public func decoder(for context: ImageDecodingContext) -> ImageDecoding? {
for match in matches {
if let decoder = match.closure(context) {
return decoder
}
}
return nil
}
// MARK: - Registering
/// Registers the given decoder.
public func register<Decoder: ImageDecoderRegistering>(_ decoder: Decoder.Type) {
register { context in
if context.isCompleted {
return decoder.init(data: context.data, context: context)
} else {
return decoder.init(partiallyDownloadedData: context.data, context: context)
}
}
}
/// 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(closure: match), at: 0)
}
/// Removes all registered decoders.
public func clear() {
matches = []
}
}
/// Image decoding context used when selecting which decoder to use.
public struct ImageDecodingContext {
public let request: ImageRequest
public let data: Data
/// Returns `true` if the download was completed.
public let isCompleted: Bool
public let urlResponse: URLResponse?
public init(request: ImageRequest, data: Data, isCompleted: Bool, urlResponse: URLResponse?) {
self.request = request
self.data = data
self.isCompleted = isCompleted
self.urlResponse = urlResponse
}
}
// MARK: - ImageType
/// A uniform type identifier (UTI).
public struct ImageType: ExpressibleByStringLiteral, Hashable {
public let rawValue: String
public init(rawValue: String) {
self.rawValue = rawValue
}
public init(stringLiteral value: String) {
self.rawValue = value
}
public static let png: ImageType = "public.png"
public static let jpeg: ImageType = "public.jpeg"
public static let gif: ImageType = "com.compuserve.gif"
/// HEIF (High Effeciency Image Format) by Apple.
public static let heic: ImageType = "public.heic"
/// WebP
///
/// Native decoding support only available on the following platforms: macOS 11,
/// iOS 14, watchOS 7, tvOS 14.
public static let webp: ImageType = "public.webp"
}
public extension ImageType {
/// Determines a type of the image based on the given data.
init?(_ data: Data) {
guard let type = ImageType.make(data) else {
return nil
}
self = type
}
private static func make(_ data: Data) -> ImageType? {
func _match(_ numbers: [UInt8?]) -> Bool {
guard data.count >= numbers.count else {
return false
}
return zip(numbers.indices, numbers).allSatisfy { index, number in
guard let number = number else { return true }
return data[index] == number
}
}
// JPEG magic numbers https://en.wikipedia.org/wiki/JPEG
if _match([0xFF, 0xD8, 0xFF]) { return .jpeg }
// PNG Magic numbers https://en.wikipedia.org/wiki/Portable_Network_Graphics
if _match([0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]) { return .png }
// GIF magic numbers https://en.wikipedia.org/wiki/GIF
if _match([0x47, 0x49, 0x46]) { return .gif }
// WebP magic numbers https://en.wikipedia.org/wiki/List_of_file_signatures
if _match([0x52, 0x49, 0x46, 0x46, nil, nil, nil, nil, 0x57, 0x45, 0x42, 0x50]) { return .webp }
// Either not enough data, or we just don't support this format.
return nil
}
}
// MARK: - ImageProperties
enum ImageProperties {}
// Keeping this private for now, not sure neither about the API, not the implementation.
extension ImageProperties {
struct JPEG {
public var isProgressive: Bool
public init?(_ data: Data) {
guard let isProgressive = ImageProperties.JPEG.isProgressive(data) else {
return nil
}
self.isProgressive = isProgressive
}
private static func isProgressive(_ data: Data) -> Bool? {
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
//
// As an alternative, Image I/O provides facilities to parse
// JPEG metadata via CGImageSourceCopyPropertiesAtIndex. It is a
// bit too convoluted to use and most likely slightly less
// efficient that checking this one special bit directly.
if data[index] == 0xFF {
if data[index + 1] == 0xC2 {
return true
}
if data[index + 1] == 0xC0 {
return false // baseline
}
}
index += 1
}
return nil
}
}
}