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.
139 lines
4.4 KiB
139 lines
4.4 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
|
|
|
|
import ImageIO
|
|
|
|
// MARK: - ImageEncoding
|
|
|
|
public protocol ImageEncoding {
|
|
/// Encodes the given image.
|
|
func encode(_ image: PlatformImage) -> Data?
|
|
|
|
/// An optional method which encodes the given image container.
|
|
func encode(_ container: ImageContainer, context: ImageEncodingContext) -> Data?
|
|
}
|
|
|
|
public extension ImageEncoding {
|
|
func encode(_ container: ImageContainer, context: ImageEncodingContext) -> Data? {
|
|
self.encode(container.image)
|
|
}
|
|
}
|
|
|
|
// MARK: - ImageEncoder
|
|
|
|
public typealias ImageEncoder = ImageEncoders.Default
|
|
|
|
/// Image encoding context used when selecting which encoder to use.
|
|
public struct ImageEncodingContext {
|
|
public let request: ImageRequest
|
|
public let image: PlatformImage
|
|
public let urlResponse: URLResponse?
|
|
}
|
|
|
|
// MARK: - ImageEncoders
|
|
|
|
public enum ImageEncoders {}
|
|
|
|
// MARK: - ImageEncoders.Default
|
|
|
|
public extension ImageEncoders {
|
|
/// A default adaptive encoder which uses best encoder available depending
|
|
/// on the input image and its configuration.
|
|
struct Default: ImageEncoding {
|
|
public var compressionQuality: Float
|
|
|
|
/// Set to `true` to switch to HEIF when it is available on the current hardware.
|
|
/// `false` by default.
|
|
public var isHEIFPreferred = false
|
|
|
|
public init(compressionQuality: Float = 0.8) {
|
|
self.compressionQuality = compressionQuality
|
|
}
|
|
|
|
public func encode(_ image: PlatformImage) -> Data? {
|
|
guard let cgImage = image.cgImage else {
|
|
return nil
|
|
}
|
|
let type: ImageType
|
|
if cgImage.isOpaque {
|
|
if isHEIFPreferred && ImageEncoders.ImageIO.isSupported(type: .heic) {
|
|
type = .heic
|
|
} else {
|
|
type = .jpeg
|
|
}
|
|
} else {
|
|
type = .png
|
|
}
|
|
let encoder = ImageEncoders.ImageIO(type: type, compressionRatio: compressionQuality)
|
|
return encoder.encode(image)
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - ImageEncoders.ImageIO
|
|
|
|
public extension ImageEncoders {
|
|
/// An Image I/O based encoder.
|
|
///
|
|
/// Image I/O is a system framework that allows applications to read and
|
|
/// write most image file formats. This framework offers high efficiency,
|
|
/// color management, and access to image metadata.
|
|
struct ImageIO: ImageEncoding {
|
|
public let type: ImageType
|
|
public let compressionRatio: Float
|
|
|
|
/// - parameter format: The output format. Make sure that the format is
|
|
/// supported on the current hardware.s
|
|
/// - parameter compressionRatio: 0.8 by default.
|
|
public init(type: ImageType, compressionRatio: Float = 0.8) {
|
|
self.type = type
|
|
self.compressionRatio = compressionRatio
|
|
}
|
|
|
|
private static let lock = NSLock()
|
|
private static var availability = [ImageType: Bool]()
|
|
|
|
/// Retuns `true` if the encoding is available for the given format on
|
|
/// the current hardware. Some of the most recent formats might not be
|
|
/// available so its best to check before using them.
|
|
public static func isSupported(type: ImageType) -> Bool {
|
|
lock.lock()
|
|
defer { lock.unlock() }
|
|
if let isAvailable = availability[type] {
|
|
return isAvailable
|
|
}
|
|
let isAvailable = CGImageDestinationCreateWithData(
|
|
NSMutableData() as CFMutableData, type.rawValue as CFString, 1, nil
|
|
) != nil
|
|
availability[type] = isAvailable
|
|
return isAvailable
|
|
}
|
|
|
|
public func encode(_ image: PlatformImage) -> Data? {
|
|
let data = NSMutableData()
|
|
let options: NSDictionary = [
|
|
kCGImageDestinationLossyCompressionQuality: compressionRatio
|
|
]
|
|
guard let source = image.cgImage,
|
|
let destination = CGImageDestinationCreateWithData(
|
|
data as CFMutableData, type.rawValue as CFString, 1, nil
|
|
) else {
|
|
return nil
|
|
}
|
|
CGImageDestinationAddImage(destination, source, options)
|
|
CGImageDestinationFinalize(destination)
|
|
return data as Data
|
|
}
|
|
}
|
|
}
|