| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377 | 
							- //
 
- //  Image.swift
 
- //  Kingfisher
 
- //
 
- //  Created by Wei Wang on 16/1/6.
 
- //
 
- //  Copyright (c) 2019 Wei Wang <onevcat@gmail.com>
 
- //
 
- //  Permission is hereby granted, free of charge, to any person obtaining a copy
 
- //  of this software and associated documentation files (the "Software"), to deal
 
- //  in the Software without restriction, including without limitation the rights
 
- //  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 
- //  copies of the Software, and to permit persons to whom the Software is
 
- //  furnished to do so, subject to the following conditions:
 
- //
 
- //  The above copyright notice and this permission notice shall be included in
 
- //  all copies or substantial portions of the Software.
 
- //
 
- //  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 
- //  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 
- //  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 
- //  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 
- //  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 
- //  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 
- //  THE SOFTWARE.
 
- #if os(macOS)
 
- import AppKit
 
- private var imagesKey: Void?
 
- private var durationKey: Void?
 
- #else
 
- import UIKit
 
- import MobileCoreServices
 
- private var imageSourceKey: Void?
 
- #endif
 
- #if !os(watchOS)
 
- import CoreImage
 
- #endif
 
- import CoreGraphics
 
- import ImageIO
 
- private var animatedImageDataKey: Void?
 
- // MARK: - Image Properties
 
- extension KingfisherWrapper where Base: KFCrossPlatformImage {
 
-     private(set) var animatedImageData: Data? {
 
-         get { return getAssociatedObject(base, &animatedImageDataKey) }
 
-         set { setRetainedAssociatedObject(base, &animatedImageDataKey, newValue) }
 
-     }
 
-     
 
-     #if os(macOS)
 
-     var cgImage: CGImage? {
 
-         return base.cgImage(forProposedRect: nil, context: nil, hints: nil)
 
-     }
 
-     
 
-     var scale: CGFloat {
 
-         return 1.0
 
-     }
 
-     
 
-     private(set) var images: [KFCrossPlatformImage]? {
 
-         get { return getAssociatedObject(base, &imagesKey) }
 
-         set { setRetainedAssociatedObject(base, &imagesKey, newValue) }
 
-     }
 
-     
 
-     private(set) var duration: TimeInterval {
 
-         get { return getAssociatedObject(base, &durationKey) ?? 0.0 }
 
-         set { setRetainedAssociatedObject(base, &durationKey, newValue) }
 
-     }
 
-     
 
-     var size: CGSize {
 
-         return base.representations.reduce(.zero) { size, rep in
 
-             let width = max(size.width, CGFloat(rep.pixelsWide))
 
-             let height = max(size.height, CGFloat(rep.pixelsHigh))
 
-             return CGSize(width: width, height: height)
 
-         }
 
-     }
 
-     #else
 
-     var cgImage: CGImage? { return base.cgImage }
 
-     var scale: CGFloat { return base.scale }
 
-     var images: [KFCrossPlatformImage]? { return base.images }
 
-     var duration: TimeInterval { return base.duration }
 
-     var size: CGSize { return base.size }
 
-     
 
-     private(set) var imageSource: CGImageSource? {
 
-         get { return getAssociatedObject(base, &imageSourceKey) }
 
-         set { setRetainedAssociatedObject(base, &imageSourceKey, newValue) }
 
-     }
 
-     #endif
 
-     // Bitmap memory cost with bytes.
 
-     var cost: Int {
 
-         let pixel = Int(size.width * size.height * scale * scale)
 
-         guard let cgImage = cgImage else {
 
-             return pixel * 4
 
-         }
 
-         return pixel * cgImage.bitsPerPixel / 8
 
-     }
 
- }
 
- // MARK: - Image Conversion
 
- extension KingfisherWrapper where Base: KFCrossPlatformImage {
 
-     #if os(macOS)
 
-     static func image(cgImage: CGImage, scale: CGFloat, refImage: KFCrossPlatformImage?) -> KFCrossPlatformImage {
 
-         return KFCrossPlatformImage(cgImage: cgImage, size: .zero)
 
-     }
 
-     
 
-     /// Normalize the image. This getter does nothing on macOS but return the image itself.
 
-     public var normalized: KFCrossPlatformImage { return base }
 
-     #else
 
-     /// Creating an image from a give `CGImage` at scale and orientation for refImage. The method signature is for
 
-     /// compatibility of macOS version.
 
-     static func image(cgImage: CGImage, scale: CGFloat, refImage: KFCrossPlatformImage?) -> KFCrossPlatformImage {
 
-         return KFCrossPlatformImage(cgImage: cgImage, scale: scale, orientation: refImage?.imageOrientation ?? .up)
 
-     }
 
-     
 
-     /// Returns normalized image for current `base` image.
 
-     /// This method will try to redraw an image with orientation and scale considered.
 
-     public var normalized: KFCrossPlatformImage {
 
-         // prevent animated image (GIF) lose it's images
 
-         guard images == nil else { return base.copy() as! KFCrossPlatformImage }
 
-         // No need to do anything if already up
 
-         guard base.imageOrientation != .up else { return base.copy() as! KFCrossPlatformImage }
 
-         return draw(to: size, inverting: true, refImage: KFCrossPlatformImage()) {
 
-             fixOrientation(in: $0)
 
-             return true
 
-         }
 
-     }
 
-     func fixOrientation(in context: CGContext) {
 
-         var transform = CGAffineTransform.identity
 
-         let orientation = base.imageOrientation
 
-         switch orientation {
 
-         case .down, .downMirrored:
 
-             transform = transform.translatedBy(x: size.width, y: size.height)
 
-             transform = transform.rotated(by: .pi)
 
-         case .left, .leftMirrored:
 
-             transform = transform.translatedBy(x: size.width, y: 0)
 
-             transform = transform.rotated(by: .pi / 2.0)
 
-         case .right, .rightMirrored:
 
-             transform = transform.translatedBy(x: 0, y: size.height)
 
-             transform = transform.rotated(by: .pi / -2.0)
 
-         case .up, .upMirrored:
 
-             break
 
-         #if compiler(>=5)
 
-         @unknown default:
 
-             break
 
-         #endif
 
-         }
 
-         //Flip image one more time if needed to, this is to prevent flipped image
 
-         switch orientation {
 
-         case .upMirrored, .downMirrored:
 
-             transform = transform.translatedBy(x: size.width, y: 0)
 
-             transform = transform.scaledBy(x: -1, y: 1)
 
-         case .leftMirrored, .rightMirrored:
 
-             transform = transform.translatedBy(x: size.height, y: 0)
 
-             transform = transform.scaledBy(x: -1, y: 1)
 
-         case .up, .down, .left, .right:
 
-             break
 
-         #if compiler(>=5)
 
-         @unknown default:
 
-             break
 
-         #endif
 
-         }
 
-         context.concatenate(transform)
 
-         switch orientation {
 
-         case .left, .leftMirrored, .right, .rightMirrored:
 
-             context.draw(cgImage!, in: CGRect(x: 0, y: 0, width: size.height, height: size.width))
 
-         default:
 
-             context.draw(cgImage!, in: CGRect(x: 0, y: 0, width: size.width, height: size.height))
 
-         }
 
-     }
 
-     #endif
 
- }
 
- // MARK: - Image Representation
 
- extension KingfisherWrapper where Base: KFCrossPlatformImage {
 
-     /// Returns PNG representation of `base` image.
 
-     ///
 
-     /// - Returns: PNG data of image.
 
-     public func pngRepresentation() -> Data? {
 
-         #if os(macOS)
 
-             guard let cgImage = cgImage else {
 
-                 return nil
 
-             }
 
-             let rep = NSBitmapImageRep(cgImage: cgImage)
 
-             return rep.representation(using: .png, properties: [:])
 
-         #else
 
-             #if swift(>=4.2)
 
-             return base.pngData()
 
-             #else
 
-             return UIImagePNGRepresentation(base)
 
-             #endif
 
-         #endif
 
-     }
 
-     /// Returns JPEG representation of `base` image.
 
-     ///
 
-     /// - Parameter compressionQuality: The compression quality when converting image to JPEG data.
 
-     /// - Returns: JPEG data of image.
 
-     public func jpegRepresentation(compressionQuality: CGFloat) -> Data? {
 
-         #if os(macOS)
 
-             guard let cgImage = cgImage else {
 
-                 return nil
 
-             }
 
-             let rep = NSBitmapImageRep(cgImage: cgImage)
 
-             return rep.representation(using:.jpeg, properties: [.compressionFactor: compressionQuality])
 
-         #else
 
-             #if swift(>=4.2)
 
-             return base.jpegData(compressionQuality: compressionQuality)
 
-             #else
 
-             return UIImageJPEGRepresentation(base, compressionQuality)
 
-             #endif
 
-         #endif
 
-     }
 
-     /// Returns GIF representation of `base` image.
 
-     ///
 
-     /// - Returns: Original GIF data of image.
 
-     public func gifRepresentation() -> Data? {
 
-         return animatedImageData
 
-     }
 
-     /// Returns a data representation for `base` image, with the `format` as the format indicator.
 
-     ///
 
-     /// - Parameter format: The format in which the output data should be. If `unknown`, the `base` image will be
 
-     ///                     converted in the PNG representation.
 
-     ///
 
-     /// - Returns: The output data representing.
 
-     /// Returns a data representation for `base` image, with the `format` as the format indicator.
 
-     /// - Parameters:
 
-     ///   - format: The format in which the output data should be. If `unknown`, the `base` image will be
 
-     ///   converted in the PNG representation.
 
-     ///   - compressionQuality: The compression quality when converting image to a lossy format data.
 
-     public func data(format: ImageFormat, compressionQuality: CGFloat = 1.0) -> Data? {
 
-         return autoreleasepool { () -> Data? in
 
-             let data: Data?
 
-             switch format {
 
-             case .PNG: data = pngRepresentation()
 
-             case .JPEG: data = jpegRepresentation(compressionQuality: compressionQuality)
 
-             case .GIF: data = gifRepresentation()
 
-             case .unknown: data = normalized.kf.pngRepresentation()
 
-             }
 
-             
 
-             return data
 
-         }
 
-     }
 
- }
 
- // MARK: - Creating Images
 
- extension KingfisherWrapper where Base: KFCrossPlatformImage {
 
-     /// Creates an animated image from a given data and options. Currently only GIF data is supported.
 
-     ///
 
-     /// - Parameters:
 
-     ///   - data: The animated image data.
 
-     ///   - options: Options to use when creating the animated image.
 
-     /// - Returns: An `Image` object represents the animated image. It is in form of an array of image frames with a
 
-     ///            certain duration. `nil` if anything wrong when creating animated image.
 
-     public static func animatedImage(data: Data, options: ImageCreatingOptions) -> KFCrossPlatformImage? {
 
-         let info: [String: Any] = [
 
-             kCGImageSourceShouldCache as String: true,
 
-             kCGImageSourceTypeIdentifierHint as String: kUTTypeGIF
 
-         ]
 
-         
 
-         guard let imageSource = CGImageSourceCreateWithData(data as CFData, info as CFDictionary) else {
 
-             return nil
 
-         }
 
-         
 
-         #if os(macOS)
 
-         guard let animatedImage = GIFAnimatedImage(from: imageSource, for: info, options: options) else {
 
-             return nil
 
-         }
 
-         var image: KFCrossPlatformImage?
 
-         if options.onlyFirstFrame {
 
-             image = animatedImage.images.first
 
-         } else {
 
-             image = KFCrossPlatformImage(data: data)
 
-             var kf = image?.kf
 
-             kf?.images = animatedImage.images
 
-             kf?.duration = animatedImage.duration
 
-         }
 
-         image?.kf.animatedImageData = data
 
-         return image
 
-         #else
 
-         
 
-         var image: KFCrossPlatformImage?
 
-         if options.preloadAll || options.onlyFirstFrame {
 
-             // Use `images` image if you want to preload all animated data
 
-             guard let animatedImage = GIFAnimatedImage(from: imageSource, for: info, options: options) else {
 
-                 return nil
 
-             }
 
-             if options.onlyFirstFrame {
 
-                 image = animatedImage.images.first
 
-             } else {
 
-                 let duration = options.duration <= 0.0 ? animatedImage.duration : options.duration
 
-                 image = .animatedImage(with: animatedImage.images, duration: duration)
 
-             }
 
-             image?.kf.animatedImageData = data
 
-         } else {
 
-             image = KFCrossPlatformImage(data: data, scale: options.scale)
 
-             var kf = image?.kf
 
-             kf?.imageSource = imageSource
 
-             kf?.animatedImageData = data
 
-         }
 
-         
 
-         return image
 
-         #endif
 
-     }
 
-     /// Creates an image from a given data and options. `.JPEG`, `.PNG` or `.GIF` is supported. For other
 
-     /// image format, image initializer from system will be used. If no image object could be created from
 
-     /// the given `data`, `nil` will be returned.
 
-     ///
 
-     /// - Parameters:
 
-     ///   - data: The image data representation.
 
-     ///   - options: Options to use when creating the image.
 
-     /// - Returns: An `Image` object represents the image if created. If the `data` is invalid or not supported, `nil`
 
-     ///            will be returned.
 
-     public static func image(data: Data, options: ImageCreatingOptions) -> KFCrossPlatformImage? {
 
-         var image: KFCrossPlatformImage?
 
-         switch data.kf.imageFormat {
 
-         case .JPEG:
 
-             image = KFCrossPlatformImage(data: data, scale: options.scale)
 
-         case .PNG:
 
-             image = KFCrossPlatformImage(data: data, scale: options.scale)
 
-         case .GIF:
 
-             image = KingfisherWrapper.animatedImage(data: data, options: options)
 
-         case .unknown:
 
-             image = KFCrossPlatformImage(data: data, scale: options.scale)
 
-         }
 
-         return image
 
-     }
 
-     
 
-     /// Creates a downsampled image from given data to a certain size and scale.
 
-     ///
 
-     /// - Parameters:
 
-     ///   - data: The image data contains a JPEG or PNG image.
 
-     ///   - pointSize: The target size in point to which the image should be downsampled.
 
-     ///   - scale: The scale of result image.
 
-     /// - Returns: A downsampled `Image` object following the input conditions.
 
-     ///
 
-     /// - Note:
 
-     /// Different from image `resize` methods, downsampling will not render the original
 
-     /// input image in pixel format. It does downsampling from the image data, so it is much
 
-     /// more memory efficient and friendly. Choose to use downsampling as possible as you can.
 
-     ///
 
-     /// The input size should be smaller than the size of input image. If it is larger than the
 
-     /// original image size, the result image will be the same size of input without downsampling.
 
-     public static func downsampledImage(data: Data, to pointSize: CGSize, scale: CGFloat) -> KFCrossPlatformImage? {
 
-         let imageSourceOptions = [kCGImageSourceShouldCache: false] as CFDictionary
 
-         guard let imageSource = CGImageSourceCreateWithData(data as CFData, imageSourceOptions) else {
 
-             return nil
 
-         }
 
-         
 
-         let maxDimensionInPixels = max(pointSize.width, pointSize.height) * scale
 
-         let downsampleOptions = [
 
-             kCGImageSourceCreateThumbnailFromImageAlways: true,
 
-             kCGImageSourceShouldCacheImmediately: true,
 
-             kCGImageSourceCreateThumbnailWithTransform: true,
 
-             kCGImageSourceThumbnailMaxPixelSize: maxDimensionInPixels] as CFDictionary
 
-         guard let downsampledImage = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, downsampleOptions) else {
 
-             return nil
 
-         }
 
-         return KingfisherWrapper.image(cgImage: downsampledImage, scale: scale, refImage: nil)
 
-     }
 
- }
 
 
  |