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

2 years ago
  1. // The MIT License (MIT)
  2. //
  3. // Copyright (c) 2015-2021 Alexander Grebenyuk (github.com/kean).
  4. #if !os(macOS)
  5. import UIKit
  6. #else
  7. import Cocoa
  8. #endif
  9. #if os(watchOS)
  10. import WatchKit
  11. #endif
  12. import ImageIO
  13. // MARK: - ImageEncoding
  14. public protocol ImageEncoding {
  15. /// Encodes the given image.
  16. func encode(_ image: PlatformImage) -> Data?
  17. /// An optional method which encodes the given image container.
  18. func encode(_ container: ImageContainer, context: ImageEncodingContext) -> Data?
  19. }
  20. public extension ImageEncoding {
  21. func encode(_ container: ImageContainer, context: ImageEncodingContext) -> Data? {
  22. self.encode(container.image)
  23. }
  24. }
  25. // MARK: - ImageEncoder
  26. public typealias ImageEncoder = ImageEncoders.Default
  27. /// Image encoding context used when selecting which encoder to use.
  28. public struct ImageEncodingContext {
  29. public let request: ImageRequest
  30. public let image: PlatformImage
  31. public let urlResponse: URLResponse?
  32. }
  33. // MARK: - ImageEncoders
  34. public enum ImageEncoders {}
  35. // MARK: - ImageEncoders.Default
  36. public extension ImageEncoders {
  37. /// A default adaptive encoder which uses best encoder available depending
  38. /// on the input image and its configuration.
  39. struct Default: ImageEncoding {
  40. public var compressionQuality: Float
  41. /// Set to `true` to switch to HEIF when it is available on the current hardware.
  42. /// `false` by default.
  43. public var isHEIFPreferred = false
  44. public init(compressionQuality: Float = 0.8) {
  45. self.compressionQuality = compressionQuality
  46. }
  47. public func encode(_ image: PlatformImage) -> Data? {
  48. guard let cgImage = image.cgImage else {
  49. return nil
  50. }
  51. let type: ImageType
  52. if cgImage.isOpaque {
  53. if isHEIFPreferred && ImageEncoders.ImageIO.isSupported(type: .heic) {
  54. type = .heic
  55. } else {
  56. type = .jpeg
  57. }
  58. } else {
  59. type = .png
  60. }
  61. let encoder = ImageEncoders.ImageIO(type: type, compressionRatio: compressionQuality)
  62. return encoder.encode(image)
  63. }
  64. }
  65. }
  66. // MARK: - ImageEncoders.ImageIO
  67. public extension ImageEncoders {
  68. /// An Image I/O based encoder.
  69. ///
  70. /// Image I/O is a system framework that allows applications to read and
  71. /// write most image file formats. This framework offers high efficiency,
  72. /// color management, and access to image metadata.
  73. struct ImageIO: ImageEncoding {
  74. public let type: ImageType
  75. public let compressionRatio: Float
  76. /// - parameter format: The output format. Make sure that the format is
  77. /// supported on the current hardware.s
  78. /// - parameter compressionRatio: 0.8 by default.
  79. public init(type: ImageType, compressionRatio: Float = 0.8) {
  80. self.type = type
  81. self.compressionRatio = compressionRatio
  82. }
  83. private static let lock = NSLock()
  84. private static var availability = [ImageType: Bool]()
  85. /// Retuns `true` if the encoding is available for the given format on
  86. /// the current hardware. Some of the most recent formats might not be
  87. /// available so its best to check before using them.
  88. public static func isSupported(type: ImageType) -> Bool {
  89. lock.lock()
  90. defer { lock.unlock() }
  91. if let isAvailable = availability[type] {
  92. return isAvailable
  93. }
  94. let isAvailable = CGImageDestinationCreateWithData(
  95. NSMutableData() as CFMutableData, type.rawValue as CFString, 1, nil
  96. ) != nil
  97. availability[type] = isAvailable
  98. return isAvailable
  99. }
  100. public func encode(_ image: PlatformImage) -> Data? {
  101. let data = NSMutableData()
  102. let options: NSDictionary = [
  103. kCGImageDestinationLossyCompressionQuality: compressionRatio
  104. ]
  105. guard let source = image.cgImage,
  106. let destination = CGImageDestinationCreateWithData(
  107. data as CFMutableData, type.rawValue as CFString, 1, nil
  108. ) else {
  109. return nil
  110. }
  111. CGImageDestinationAddImage(destination, source, options)
  112. CGImageDestinationFinalize(destination)
  113. return data as Data
  114. }
  115. }
  116. }