update
This commit is contained in:
146
Pods/Kingfisher/Sources/Image/Filter.swift
generated
Normal file
146
Pods/Kingfisher/Sources/Image/Filter.swift
generated
Normal file
@@ -0,0 +1,146 @@
|
||||
//
|
||||
// Filter.swift
|
||||
// Kingfisher
|
||||
//
|
||||
// Created by Wei Wang on 2016/08/31.
|
||||
//
|
||||
// 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(watchOS)
|
||||
|
||||
import CoreImage
|
||||
|
||||
// Reuse the same CI Context for all CI drawing.
|
||||
private let ciContext = CIContext(options: nil)
|
||||
|
||||
/// Represents the type of transformer method, which will be used in to provide a `Filter`.
|
||||
public typealias Transformer = (CIImage) -> CIImage?
|
||||
|
||||
/// Represents a processor based on a `CIImage` `Filter`.
|
||||
/// It requires a filter to create an `ImageProcessor`.
|
||||
public protocol CIImageProcessor: ImageProcessor {
|
||||
var filter: Filter { get }
|
||||
}
|
||||
|
||||
extension CIImageProcessor {
|
||||
|
||||
/// Processes the input `ImageProcessItem` with this processor.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - item: Input item which will be processed by `self`.
|
||||
/// - options: Options when processing the item.
|
||||
/// - Returns: The processed image.
|
||||
///
|
||||
/// - Note: See documentation of `ImageProcessor` protocol for more.
|
||||
public func process(item: ImageProcessItem, options: KingfisherParsedOptionsInfo) -> KFCrossPlatformImage? {
|
||||
switch item {
|
||||
case .image(let image):
|
||||
return image.kf.apply(filter)
|
||||
case .data:
|
||||
return (DefaultImageProcessor.default |> self).process(item: item, options: options)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A wrapper struct for a `Transformer` of CIImage filters. A `Filter`
|
||||
/// value could be used to create a `CIImage` processor.
|
||||
public struct Filter {
|
||||
|
||||
let transform: Transformer
|
||||
|
||||
public init(transform: @escaping Transformer) {
|
||||
self.transform = transform
|
||||
}
|
||||
|
||||
/// Tint filter which will apply a tint color to images.
|
||||
public static var tint: (KFCrossPlatformColor) -> Filter = {
|
||||
color in
|
||||
Filter {
|
||||
input in
|
||||
|
||||
let colorFilter = CIFilter(name: "CIConstantColorGenerator")!
|
||||
colorFilter.setValue(CIColor(color: color), forKey: kCIInputColorKey)
|
||||
|
||||
let filter = CIFilter(name: "CISourceOverCompositing")!
|
||||
|
||||
let colorImage = colorFilter.outputImage
|
||||
filter.setValue(colorImage, forKey: kCIInputImageKey)
|
||||
filter.setValue(input, forKey: kCIInputBackgroundImageKey)
|
||||
|
||||
return filter.outputImage?.cropped(to: input.extent)
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents color control elements. It is a tuple of
|
||||
/// `(brightness, contrast, saturation, inputEV)`
|
||||
public typealias ColorElement = (CGFloat, CGFloat, CGFloat, CGFloat)
|
||||
|
||||
/// Color control filter which will apply color control change to images.
|
||||
public static var colorControl: (ColorElement) -> Filter = { arg -> Filter in
|
||||
let (brightness, contrast, saturation, inputEV) = arg
|
||||
return Filter { input in
|
||||
let paramsColor = [kCIInputBrightnessKey: brightness,
|
||||
kCIInputContrastKey: contrast,
|
||||
kCIInputSaturationKey: saturation]
|
||||
let blackAndWhite = input.applyingFilter("CIColorControls", parameters: paramsColor)
|
||||
let paramsExposure = [kCIInputEVKey: inputEV]
|
||||
return blackAndWhite.applyingFilter("CIExposureAdjust", parameters: paramsExposure)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension KingfisherWrapper where Base: KFCrossPlatformImage {
|
||||
|
||||
/// Applies a `Filter` containing `CIImage` transformer to `self`.
|
||||
///
|
||||
/// - Parameter filter: The filter used to transform `self`.
|
||||
/// - Returns: A transformed image by input `Filter`.
|
||||
///
|
||||
/// - Note:
|
||||
/// Only CG-based images are supported. If any error happens
|
||||
/// during transforming, `self` will be returned.
|
||||
public func apply(_ filter: Filter) -> KFCrossPlatformImage {
|
||||
|
||||
guard let cgImage = cgImage else {
|
||||
assertionFailure("[Kingfisher] Tint image only works for CG-based image.")
|
||||
return base
|
||||
}
|
||||
|
||||
let inputImage = CIImage(cgImage: cgImage)
|
||||
guard let outputImage = filter.transform(inputImage) else {
|
||||
return base
|
||||
}
|
||||
|
||||
guard let result = ciContext.createCGImage(outputImage, from: outputImage.extent) else {
|
||||
assertionFailure("[Kingfisher] Can not make an tint image within context.")
|
||||
return base
|
||||
}
|
||||
|
||||
#if os(macOS)
|
||||
return fixedForRetinaPixel(cgImage: result, to: size)
|
||||
#else
|
||||
return KFCrossPlatformImage(cgImage: result, scale: base.scale, orientation: base.imageOrientation)
|
||||
#endif
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
177
Pods/Kingfisher/Sources/Image/GIFAnimatedImage.swift
generated
Normal file
177
Pods/Kingfisher/Sources/Image/GIFAnimatedImage.swift
generated
Normal file
@@ -0,0 +1,177 @@
|
||||
//
|
||||
// AnimatedImage.swift
|
||||
// Kingfisher
|
||||
//
|
||||
// Created by onevcat on 2018/09/26.
|
||||
//
|
||||
// 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.
|
||||
|
||||
import Foundation
|
||||
import ImageIO
|
||||
|
||||
/// Represents a set of image creating options used in Kingfisher.
|
||||
public struct ImageCreatingOptions {
|
||||
|
||||
/// The target scale of image needs to be created.
|
||||
public let scale: CGFloat
|
||||
|
||||
/// The expected animation duration if an animated image being created.
|
||||
public let duration: TimeInterval
|
||||
|
||||
/// For an animated image, whether or not all frames should be loaded before displaying.
|
||||
public let preloadAll: Bool
|
||||
|
||||
/// For an animated image, whether or not only the first image should be
|
||||
/// loaded as a static image. It is useful for preview purpose of an animated image.
|
||||
public let onlyFirstFrame: Bool
|
||||
|
||||
/// Creates an `ImageCreatingOptions` object.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - scale: The target scale of image needs to be created. Default is `1.0`.
|
||||
/// - duration: The expected animation duration if an animated image being created.
|
||||
/// A value less or equal to `0.0` means the animated image duration will
|
||||
/// be determined by the frame data. Default is `0.0`.
|
||||
/// - preloadAll: For an animated image, whether or not all frames should be loaded before displaying.
|
||||
/// Default is `false`.
|
||||
/// - onlyFirstFrame: For an animated image, whether or not only the first image should be
|
||||
/// loaded as a static image. It is useful for preview purpose of an animated image.
|
||||
/// Default is `false`.
|
||||
public init(
|
||||
scale: CGFloat = 1.0,
|
||||
duration: TimeInterval = 0.0,
|
||||
preloadAll: Bool = false,
|
||||
onlyFirstFrame: Bool = false)
|
||||
{
|
||||
self.scale = scale
|
||||
self.duration = duration
|
||||
self.preloadAll = preloadAll
|
||||
self.onlyFirstFrame = onlyFirstFrame
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents the decoding for a GIF image. This class extracts frames from an `imageSource`, then
|
||||
/// hold the images for later use.
|
||||
public class GIFAnimatedImage {
|
||||
let images: [KFCrossPlatformImage]
|
||||
let duration: TimeInterval
|
||||
|
||||
init?(from frameSource: ImageFrameSource, options: ImageCreatingOptions) {
|
||||
let frameCount = frameSource.frameCount
|
||||
var images = [KFCrossPlatformImage]()
|
||||
var gifDuration = 0.0
|
||||
|
||||
for i in 0 ..< frameCount {
|
||||
guard let imageRef = frameSource.frame(at: i) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
if frameCount == 1 {
|
||||
gifDuration = .infinity
|
||||
} else {
|
||||
// Get current animated GIF frame duration
|
||||
gifDuration += frameSource.duration(at: i)
|
||||
}
|
||||
images.append(KingfisherWrapper.image(cgImage: imageRef, scale: options.scale, refImage: nil))
|
||||
if options.onlyFirstFrame { break }
|
||||
}
|
||||
self.images = images
|
||||
self.duration = gifDuration
|
||||
}
|
||||
|
||||
convenience init?(from imageSource: CGImageSource, for info: [String: Any], options: ImageCreatingOptions) {
|
||||
let frameSource = CGImageFrameSource(data: nil, imageSource: imageSource, options: info)
|
||||
self.init(from: frameSource, options: options)
|
||||
}
|
||||
|
||||
/// Calculates frame duration for a gif frame out of the kCGImagePropertyGIFDictionary dictionary.
|
||||
public static func getFrameDuration(from gifInfo: [String: Any]?) -> TimeInterval {
|
||||
let defaultFrameDuration = 0.1
|
||||
guard let gifInfo = gifInfo else { return defaultFrameDuration }
|
||||
|
||||
let unclampedDelayTime = gifInfo[kCGImagePropertyGIFUnclampedDelayTime as String] as? NSNumber
|
||||
let delayTime = gifInfo[kCGImagePropertyGIFDelayTime as String] as? NSNumber
|
||||
let duration = unclampedDelayTime ?? delayTime
|
||||
|
||||
guard let frameDuration = duration else { return defaultFrameDuration }
|
||||
return frameDuration.doubleValue > 0.011 ? frameDuration.doubleValue : defaultFrameDuration
|
||||
}
|
||||
|
||||
/// Calculates frame duration at a specific index for a gif from an `imageSource`.
|
||||
public static func getFrameDuration(from imageSource: CGImageSource, at index: Int) -> TimeInterval {
|
||||
guard let properties = CGImageSourceCopyPropertiesAtIndex(imageSource, index, nil)
|
||||
as? [String: Any] else { return 0.0 }
|
||||
|
||||
let gifInfo = properties[kCGImagePropertyGIFDictionary as String] as? [String: Any]
|
||||
return getFrameDuration(from: gifInfo)
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a frame source for animated image
|
||||
public protocol ImageFrameSource {
|
||||
/// Source data associated with this frame source.
|
||||
var data: Data? { get }
|
||||
|
||||
/// Count of total frames in this frame source.
|
||||
var frameCount: Int { get }
|
||||
|
||||
/// Retrieves the frame at a specific index. The result image is expected to be
|
||||
/// no larger than `maxSize`. If the index is invalid, implementors should return `nil`.
|
||||
func frame(at index: Int, maxSize: CGSize?) -> CGImage?
|
||||
|
||||
/// Retrieves the duration at a specific index. If the index is invalid, implementors should return `0.0`.
|
||||
func duration(at index: Int) -> TimeInterval
|
||||
}
|
||||
|
||||
public extension ImageFrameSource {
|
||||
/// Retrieves the frame at a specific index. If the index is invalid, implementors should return `nil`.
|
||||
func frame(at index: Int) -> CGImage? {
|
||||
return frame(at: index, maxSize: nil)
|
||||
}
|
||||
}
|
||||
|
||||
struct CGImageFrameSource: ImageFrameSource {
|
||||
let data: Data?
|
||||
let imageSource: CGImageSource
|
||||
let options: [String: Any]?
|
||||
|
||||
var frameCount: Int {
|
||||
return CGImageSourceGetCount(imageSource)
|
||||
}
|
||||
|
||||
func frame(at index: Int, maxSize: CGSize?) -> CGImage? {
|
||||
var options = self.options as? [CFString: Any]
|
||||
if let maxSize = maxSize, maxSize != .zero {
|
||||
options = (options ?? [:]).merging([
|
||||
kCGImageSourceCreateThumbnailFromImageIfAbsent: true,
|
||||
kCGImageSourceCreateThumbnailWithTransform: true,
|
||||
kCGImageSourceShouldCacheImmediately: true,
|
||||
kCGImageSourceThumbnailMaxPixelSize: max(maxSize.width, maxSize.height)
|
||||
], uniquingKeysWith: { $1 })
|
||||
}
|
||||
return CGImageSourceCreateImageAtIndex(imageSource, index, options as CFDictionary?)
|
||||
}
|
||||
|
||||
func duration(at index: Int) -> TimeInterval {
|
||||
return GIFAnimatedImage.getFrameDuration(from: imageSource, at: index)
|
||||
}
|
||||
}
|
||||
|
||||
88
Pods/Kingfisher/Sources/Image/GraphicsContext.swift
generated
Normal file
88
Pods/Kingfisher/Sources/Image/GraphicsContext.swift
generated
Normal file
@@ -0,0 +1,88 @@
|
||||
//
|
||||
// GraphicsContext.swift
|
||||
// Kingfisher
|
||||
//
|
||||
// Created by taras on 19/04/2021.
|
||||
//
|
||||
// Copyright (c) 2021 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 canImport(AppKit) && !targetEnvironment(macCatalyst)
|
||||
import AppKit
|
||||
#endif
|
||||
#if canImport(UIKit)
|
||||
import UIKit
|
||||
#endif
|
||||
|
||||
enum GraphicsContext {
|
||||
static func begin(size: CGSize, scale: CGFloat) {
|
||||
#if os(macOS)
|
||||
NSGraphicsContext.saveGraphicsState()
|
||||
#else
|
||||
UIGraphicsBeginImageContextWithOptions(size, false, scale)
|
||||
#endif
|
||||
}
|
||||
|
||||
static func current(size: CGSize, scale: CGFloat, inverting: Bool, cgImage: CGImage?) -> CGContext? {
|
||||
#if os(macOS)
|
||||
guard let rep = NSBitmapImageRep(
|
||||
bitmapDataPlanes: nil,
|
||||
pixelsWide: Int(size.width),
|
||||
pixelsHigh: Int(size.height),
|
||||
bitsPerSample: cgImage?.bitsPerComponent ?? 8,
|
||||
samplesPerPixel: 4,
|
||||
hasAlpha: true,
|
||||
isPlanar: false,
|
||||
colorSpaceName: .calibratedRGB,
|
||||
bytesPerRow: 0,
|
||||
bitsPerPixel: 0) else
|
||||
{
|
||||
assertionFailure("[Kingfisher] Image representation cannot be created.")
|
||||
return nil
|
||||
}
|
||||
rep.size = size
|
||||
guard let context = NSGraphicsContext(bitmapImageRep: rep) else {
|
||||
assertionFailure("[Kingfisher] Image context cannot be created.")
|
||||
return nil
|
||||
}
|
||||
|
||||
NSGraphicsContext.current = context
|
||||
return context.cgContext
|
||||
#else
|
||||
guard let context = UIGraphicsGetCurrentContext() else {
|
||||
return nil
|
||||
}
|
||||
if inverting { // If drawing a CGImage, we need to make context flipped.
|
||||
context.scaleBy(x: 1.0, y: -1.0)
|
||||
context.translateBy(x: 0, y: -size.height)
|
||||
}
|
||||
return context
|
||||
#endif
|
||||
}
|
||||
|
||||
static func end() {
|
||||
#if os(macOS)
|
||||
NSGraphicsContext.restoreGraphicsState()
|
||||
#else
|
||||
UIGraphicsEndImageContext()
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
426
Pods/Kingfisher/Sources/Image/Image.swift
generated
Normal file
426
Pods/Kingfisher/Sources/Image/Image.swift
generated
Normal file
@@ -0,0 +1,426 @@
|
||||
//
|
||||
// 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
|
||||
|
||||
#if canImport(UniformTypeIdentifiers)
|
||||
import UniformTypeIdentifiers
|
||||
#endif
|
||||
|
||||
private var animatedImageDataKey: Void?
|
||||
private var imageFrameCountKey: Void?
|
||||
|
||||
// MARK: - Image Properties
|
||||
extension KingfisherWrapper where Base: KFCrossPlatformImage {
|
||||
private(set) var animatedImageData: Data? {
|
||||
get { return getAssociatedObject(base, &animatedImageDataKey) }
|
||||
set { setRetainedAssociatedObject(base, &animatedImageDataKey, newValue) }
|
||||
}
|
||||
|
||||
public var imageFrameCount: Int? {
|
||||
get { return getAssociatedObject(base, &imageFrameCountKey) }
|
||||
set { setRetainedAssociatedObject(base, &imageFrameCountKey, 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 }
|
||||
|
||||
/// The image source reference of current image.
|
||||
public var imageSource: CGImageSource? {
|
||||
get {
|
||||
guard let frameSource = frameSource as? CGImageFrameSource else { return nil }
|
||||
return frameSource.imageSource
|
||||
}
|
||||
}
|
||||
|
||||
/// The custom frame source of current image.
|
||||
public private(set) var frameSource: ImageFrameSource? {
|
||||
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
|
||||
}
|
||||
let bytesPerPixel = cgImage.bitsPerPixel / 8
|
||||
guard let imageCount = images?.count else {
|
||||
return pixel * bytesPerPixel
|
||||
}
|
||||
return pixel * bytesPerPixel * imageCount
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
return base.pngData()
|
||||
#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
|
||||
return base.jpegData(compressionQuality: compressionQuality)
|
||||
#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.
|
||||
/// - 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.
|
||||
///
|
||||
/// - Returns: The output data representing.
|
||||
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? {
|
||||
#if os(xrOS)
|
||||
let info: [String: Any] = [
|
||||
kCGImageSourceShouldCache as String: true,
|
||||
kCGImageSourceTypeIdentifierHint as String: UTType.gif.identifier
|
||||
]
|
||||
#else
|
||||
let info: [String: Any] = [
|
||||
kCGImageSourceShouldCache as String: true,
|
||||
kCGImageSourceTypeIdentifierHint as String: kUTTypeGIF
|
||||
]
|
||||
#endif
|
||||
|
||||
guard let imageSource = CGImageSourceCreateWithData(data as CFData, info as CFDictionary) else {
|
||||
return nil
|
||||
}
|
||||
let frameSource = CGImageFrameSource(data: data, imageSource: imageSource, options: info)
|
||||
#if os(macOS)
|
||||
let baseImage = KFCrossPlatformImage(data: data)
|
||||
#else
|
||||
let baseImage = KFCrossPlatformImage(data: data, scale: options.scale)
|
||||
#endif
|
||||
return animatedImage(source: frameSource, options: options, baseImage: baseImage)
|
||||
}
|
||||
|
||||
/// Creates an animated image from a given frame source.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - source: The frame source to create animated image from.
|
||||
/// - options: Options to use when creating the animated image.
|
||||
/// - baseImage: An optional image object to be used as the key frame of the animated image. If `nil`, the first
|
||||
/// frame of the `source` will be used.
|
||||
/// - 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(source: ImageFrameSource, options: ImageCreatingOptions, baseImage: KFCrossPlatformImage? = nil) -> KFCrossPlatformImage? {
|
||||
#if os(macOS)
|
||||
guard let animatedImage = GIFAnimatedImage(from: source, options: options) else {
|
||||
return nil
|
||||
}
|
||||
var image: KFCrossPlatformImage?
|
||||
if options.onlyFirstFrame {
|
||||
image = animatedImage.images.first
|
||||
} else {
|
||||
if let baseImage = baseImage {
|
||||
image = baseImage
|
||||
} else {
|
||||
image = animatedImage.images.first
|
||||
}
|
||||
var kf = image?.kf
|
||||
kf?.images = animatedImage.images
|
||||
kf?.duration = animatedImage.duration
|
||||
}
|
||||
image?.kf.animatedImageData = source.data
|
||||
image?.kf.imageFrameCount = source.frameCount
|
||||
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: source, 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 = source.data
|
||||
} else {
|
||||
if let baseImage = baseImage {
|
||||
image = baseImage
|
||||
} else {
|
||||
guard let firstFrame = source.frame(at: 0) else {
|
||||
return nil
|
||||
}
|
||||
image = KFCrossPlatformImage(cgImage: firstFrame, scale: options.scale, orientation: .up)
|
||||
}
|
||||
var kf = image?.kf
|
||||
kf?.frameSource = source
|
||||
kf?.animatedImageData = source.data
|
||||
}
|
||||
|
||||
image?.kf.imageFrameCount = source.frameCount
|
||||
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 pointsize 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: [CFString : Any] = [
|
||||
kCGImageSourceCreateThumbnailFromImageAlways: true,
|
||||
kCGImageSourceShouldCacheImmediately: true,
|
||||
kCGImageSourceCreateThumbnailWithTransform: true,
|
||||
kCGImageSourceThumbnailMaxPixelSize: maxDimensionInPixels
|
||||
]
|
||||
guard let downsampledImage = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, downsampleOptions as CFDictionary) else {
|
||||
return nil
|
||||
}
|
||||
return KingfisherWrapper.image(cgImage: downsampledImage, scale: scale, refImage: nil)
|
||||
}
|
||||
}
|
||||
636
Pods/Kingfisher/Sources/Image/ImageDrawing.swift
generated
Normal file
636
Pods/Kingfisher/Sources/Image/ImageDrawing.swift
generated
Normal file
@@ -0,0 +1,636 @@
|
||||
//
|
||||
// ImageDrawing.swift
|
||||
// Kingfisher
|
||||
//
|
||||
// Created by onevcat on 2018/09/28.
|
||||
//
|
||||
// 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.
|
||||
|
||||
import Accelerate
|
||||
|
||||
#if canImport(AppKit) && !targetEnvironment(macCatalyst)
|
||||
import AppKit
|
||||
#endif
|
||||
#if canImport(UIKit)
|
||||
import UIKit
|
||||
#endif
|
||||
|
||||
// MARK: - Image Transforming
|
||||
extension KingfisherWrapper where Base: KFCrossPlatformImage {
|
||||
// MARK: Blend Mode
|
||||
/// Create image from `base` image and apply blend mode.
|
||||
///
|
||||
/// - parameter blendMode: The blend mode of creating image.
|
||||
/// - parameter alpha: The alpha should be used for image.
|
||||
/// - parameter backgroundColor: The background color for the output image.
|
||||
///
|
||||
/// - returns: An image with blend mode applied.
|
||||
///
|
||||
/// - Note: This method only works for CG-based image.
|
||||
#if !os(macOS)
|
||||
public func image(withBlendMode blendMode: CGBlendMode,
|
||||
alpha: CGFloat = 1.0,
|
||||
backgroundColor: KFCrossPlatformColor? = nil) -> KFCrossPlatformImage
|
||||
{
|
||||
guard let _ = cgImage else {
|
||||
assertionFailure("[Kingfisher] Blend mode image only works for CG-based image.")
|
||||
return base
|
||||
}
|
||||
|
||||
let rect = CGRect(origin: .zero, size: size)
|
||||
return draw(to: rect.size, inverting: false) { _ in
|
||||
if let backgroundColor = backgroundColor {
|
||||
backgroundColor.setFill()
|
||||
UIRectFill(rect)
|
||||
}
|
||||
|
||||
base.draw(in: rect, blendMode: blendMode, alpha: alpha)
|
||||
return false
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if os(macOS)
|
||||
// MARK: Compositing
|
||||
/// Creates image from `base` image and apply compositing operation.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - compositingOperation: The compositing operation of creating image.
|
||||
/// - alpha: The alpha should be used for image.
|
||||
/// - backgroundColor: The background color for the output image.
|
||||
/// - Returns: An image with compositing operation applied.
|
||||
///
|
||||
/// - Note: This method only works for CG-based image. For any non-CG-based image, `base` itself is returned.
|
||||
public func image(withCompositingOperation compositingOperation: NSCompositingOperation,
|
||||
alpha: CGFloat = 1.0,
|
||||
backgroundColor: KFCrossPlatformColor? = nil) -> KFCrossPlatformImage
|
||||
{
|
||||
guard let _ = cgImage else {
|
||||
assertionFailure("[Kingfisher] Compositing Operation image only works for CG-based image.")
|
||||
return base
|
||||
}
|
||||
|
||||
let rect = CGRect(origin: .zero, size: size)
|
||||
return draw(to: rect.size, inverting: false) { _ in
|
||||
if let backgroundColor = backgroundColor {
|
||||
backgroundColor.setFill()
|
||||
rect.fill()
|
||||
}
|
||||
base.draw(in: rect, from: .zero, operation: compositingOperation, fraction: alpha)
|
||||
return false
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// MARK: Round Corner
|
||||
|
||||
/// Creates a round corner image from on `base` image.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - radius: The round corner radius of creating image.
|
||||
/// - size: The target size of creating image.
|
||||
/// - corners: The target corners which will be applied rounding.
|
||||
/// - backgroundColor: The background color for the output image
|
||||
/// - Returns: An image with round corner of `self`.
|
||||
///
|
||||
/// - Note: This method only works for CG-based image. The current image scale is kept.
|
||||
/// For any non-CG-based image, `base` itself is returned.
|
||||
public func image(
|
||||
withRadius radius: Radius,
|
||||
fit size: CGSize,
|
||||
roundingCorners corners: RectCorner = .all,
|
||||
backgroundColor: KFCrossPlatformColor? = nil
|
||||
) -> KFCrossPlatformImage
|
||||
{
|
||||
|
||||
guard let _ = cgImage else {
|
||||
assertionFailure("[Kingfisher] Round corner image only works for CG-based image.")
|
||||
return base
|
||||
}
|
||||
|
||||
let rect = CGRect(origin: CGPoint(x: 0, y: 0), size: size)
|
||||
return draw(to: size, inverting: false) { _ in
|
||||
#if os(macOS)
|
||||
if let backgroundColor = backgroundColor {
|
||||
let rectPath = NSBezierPath(rect: rect)
|
||||
backgroundColor.setFill()
|
||||
rectPath.fill()
|
||||
}
|
||||
|
||||
let path = pathForRoundCorner(rect: rect, radius: radius, corners: corners)
|
||||
path.addClip()
|
||||
base.draw(in: rect)
|
||||
#else
|
||||
guard let context = UIGraphicsGetCurrentContext() else {
|
||||
assertionFailure("[Kingfisher] Failed to create CG context for image.")
|
||||
return false
|
||||
}
|
||||
|
||||
if let backgroundColor = backgroundColor {
|
||||
let rectPath = UIBezierPath(rect: rect)
|
||||
backgroundColor.setFill()
|
||||
rectPath.fill()
|
||||
}
|
||||
|
||||
let path = pathForRoundCorner(rect: rect, radius: radius, corners: corners)
|
||||
context.addPath(path.cgPath)
|
||||
context.clip()
|
||||
base.draw(in: rect)
|
||||
#endif
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a round corner image from on `base` image.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - radius: The round corner radius of creating image.
|
||||
/// - size: The target size of creating image.
|
||||
/// - corners: The target corners which will be applied rounding.
|
||||
/// - backgroundColor: The background color for the output image
|
||||
/// - Returns: An image with round corner of `self`.
|
||||
///
|
||||
/// - Note: This method only works for CG-based image. The current image scale is kept.
|
||||
/// For any non-CG-based image, `base` itself is returned.
|
||||
public func image(
|
||||
withRoundRadius radius: CGFloat,
|
||||
fit size: CGSize,
|
||||
roundingCorners corners: RectCorner = .all,
|
||||
backgroundColor: KFCrossPlatformColor? = nil
|
||||
) -> KFCrossPlatformImage
|
||||
{
|
||||
image(withRadius: .point(radius), fit: size, roundingCorners: corners, backgroundColor: backgroundColor)
|
||||
}
|
||||
|
||||
#if os(macOS)
|
||||
func pathForRoundCorner(rect: CGRect, radius: Radius, corners: RectCorner, offsetBase: CGFloat = 0) -> NSBezierPath {
|
||||
let cornerRadius = radius.compute(with: rect.size)
|
||||
let path = NSBezierPath(roundedRect: rect, byRoundingCorners: corners, radius: cornerRadius - offsetBase / 2)
|
||||
path.windingRule = .evenOdd
|
||||
return path
|
||||
}
|
||||
#else
|
||||
func pathForRoundCorner(rect: CGRect, radius: Radius, corners: RectCorner, offsetBase: CGFloat = 0) -> UIBezierPath {
|
||||
let cornerRadius = radius.compute(with: rect.size)
|
||||
return UIBezierPath(
|
||||
roundedRect: rect,
|
||||
byRoundingCorners: corners.uiRectCorner,
|
||||
cornerRadii: CGSize(
|
||||
width: cornerRadius - offsetBase / 2,
|
||||
height: cornerRadius - offsetBase / 2
|
||||
)
|
||||
)
|
||||
}
|
||||
#endif
|
||||
|
||||
#if os(iOS) || os(tvOS)
|
||||
func resize(to size: CGSize, for contentMode: UIView.ContentMode) -> KFCrossPlatformImage {
|
||||
switch contentMode {
|
||||
case .scaleAspectFit:
|
||||
return resize(to: size, for: .aspectFit)
|
||||
case .scaleAspectFill:
|
||||
return resize(to: size, for: .aspectFill)
|
||||
default:
|
||||
return resize(to: size)
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// MARK: Resizing
|
||||
/// Resizes `base` image to an image with new size.
|
||||
///
|
||||
/// - Parameter size: The target size in point.
|
||||
/// - Returns: An image with new size.
|
||||
/// - Note: This method only works for CG-based image. The current image scale is kept.
|
||||
/// For any non-CG-based image, `base` itself is returned.
|
||||
public func resize(to size: CGSize) -> KFCrossPlatformImage {
|
||||
guard let _ = cgImage else {
|
||||
assertionFailure("[Kingfisher] Resize only works for CG-based image.")
|
||||
return base
|
||||
}
|
||||
|
||||
let rect = CGRect(origin: CGPoint(x: 0, y: 0), size: size)
|
||||
return draw(to: size, inverting: false) { _ in
|
||||
#if os(macOS)
|
||||
base.draw(in: rect, from: .zero, operation: .copy, fraction: 1.0)
|
||||
#else
|
||||
base.draw(in: rect)
|
||||
#endif
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/// Resizes `base` image to an image of new size, respecting the given content mode.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - targetSize: The target size in point.
|
||||
/// - contentMode: Content mode of output image should be.
|
||||
/// - Returns: An image with new size.
|
||||
///
|
||||
/// - Note: This method only works for CG-based image. The current image scale is kept.
|
||||
/// For any non-CG-based image, `base` itself is returned.
|
||||
public func resize(to targetSize: CGSize, for contentMode: ContentMode) -> KFCrossPlatformImage {
|
||||
let newSize = size.kf.resize(to: targetSize, for: contentMode)
|
||||
return resize(to: newSize)
|
||||
}
|
||||
|
||||
// MARK: Cropping
|
||||
/// Crops `base` image to a new size with a given anchor.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - size: The target size.
|
||||
/// - anchor: The anchor point from which the size should be calculated.
|
||||
/// - Returns: An image with new size.
|
||||
///
|
||||
/// - Note: This method only works for CG-based image. The current image scale is kept.
|
||||
/// For any non-CG-based image, `base` itself is returned.
|
||||
public func crop(to size: CGSize, anchorOn anchor: CGPoint) -> KFCrossPlatformImage {
|
||||
guard let cgImage = cgImage else {
|
||||
assertionFailure("[Kingfisher] Crop only works for CG-based image.")
|
||||
return base
|
||||
}
|
||||
|
||||
let rect = self.size.kf.constrainedRect(for: size, anchor: anchor)
|
||||
guard let image = cgImage.cropping(to: rect.scaled(scale)) else {
|
||||
assertionFailure("[Kingfisher] Cropping image failed.")
|
||||
return base
|
||||
}
|
||||
|
||||
return KingfisherWrapper.image(cgImage: image, scale: scale, refImage: base)
|
||||
}
|
||||
|
||||
// MARK: Blur
|
||||
/// Creates an image with blur effect based on `base` image.
|
||||
///
|
||||
/// - Parameter radius: The blur radius should be used when creating blur effect.
|
||||
/// - Returns: An image with blur effect applied.
|
||||
///
|
||||
/// - Note: This method only works for CG-based image. The current image scale is kept.
|
||||
/// For any non-CG-based image, `base` itself is returned.
|
||||
public func blurred(withRadius radius: CGFloat) -> KFCrossPlatformImage {
|
||||
|
||||
guard let cgImage = cgImage else {
|
||||
assertionFailure("[Kingfisher] Blur only works for CG-based image.")
|
||||
return base
|
||||
}
|
||||
|
||||
// http://www.w3.org/TR/SVG/filters.html#feGaussianBlurElement
|
||||
// let d = floor(s * 3*sqrt(2*pi)/4 + 0.5)
|
||||
// if d is odd, use three box-blurs of size 'd', centered on the output pixel.
|
||||
let s = max(radius, 2.0)
|
||||
// We will do blur on a resized image (*0.5), so the blur radius could be half as well.
|
||||
|
||||
// Fix the slow compiling time for Swift 3.
|
||||
// See https://github.com/onevcat/Kingfisher/issues/611
|
||||
let pi2 = 2 * CGFloat.pi
|
||||
let sqrtPi2 = sqrt(pi2)
|
||||
var targetRadius = floor(s * 3.0 * sqrtPi2 / 4.0 + 0.5)
|
||||
|
||||
if targetRadius.isEven { targetRadius += 1 }
|
||||
|
||||
// Determine necessary iteration count by blur radius.
|
||||
let iterations: Int
|
||||
if radius < 0.5 {
|
||||
iterations = 1
|
||||
} else if radius < 1.5 {
|
||||
iterations = 2
|
||||
} else {
|
||||
iterations = 3
|
||||
}
|
||||
|
||||
let w = Int(size.width)
|
||||
let h = Int(size.height)
|
||||
|
||||
func createEffectBuffer(_ context: CGContext) -> vImage_Buffer {
|
||||
let data = context.data
|
||||
let width = vImagePixelCount(context.width)
|
||||
let height = vImagePixelCount(context.height)
|
||||
let rowBytes = context.bytesPerRow
|
||||
|
||||
return vImage_Buffer(data: data, height: height, width: width, rowBytes: rowBytes)
|
||||
}
|
||||
GraphicsContext.begin(size: size, scale: scale)
|
||||
guard let context = GraphicsContext.current(size: size, scale: scale, inverting: true, cgImage: cgImage) else {
|
||||
assertionFailure("[Kingfisher] Failed to create CG context for blurring image.")
|
||||
return base
|
||||
}
|
||||
context.draw(cgImage, in: CGRect(x: 0, y: 0, width: w, height: h))
|
||||
GraphicsContext.end()
|
||||
|
||||
var inBuffer = createEffectBuffer(context)
|
||||
|
||||
GraphicsContext.begin(size: size, scale: scale)
|
||||
guard let outContext = GraphicsContext.current(size: size, scale: scale, inverting: true, cgImage: cgImage) else {
|
||||
assertionFailure("[Kingfisher] Failed to create CG context for blurring image.")
|
||||
return base
|
||||
}
|
||||
defer { GraphicsContext.end() }
|
||||
var outBuffer = createEffectBuffer(outContext)
|
||||
|
||||
for _ in 0 ..< iterations {
|
||||
let flag = vImage_Flags(kvImageEdgeExtend)
|
||||
vImageBoxConvolve_ARGB8888(
|
||||
&inBuffer, &outBuffer, nil, 0, 0, UInt32(targetRadius), UInt32(targetRadius), nil, flag)
|
||||
// Next inBuffer should be the outButter of current iteration
|
||||
(inBuffer, outBuffer) = (outBuffer, inBuffer)
|
||||
}
|
||||
|
||||
#if os(macOS)
|
||||
let result = outContext.makeImage().flatMap {
|
||||
fixedForRetinaPixel(cgImage: $0, to: size)
|
||||
}
|
||||
#else
|
||||
let result = outContext.makeImage().flatMap {
|
||||
KFCrossPlatformImage(cgImage: $0, scale: base.scale, orientation: base.imageOrientation)
|
||||
}
|
||||
#endif
|
||||
guard let blurredImage = result else {
|
||||
assertionFailure("[Kingfisher] Can not make an blurred image within this context.")
|
||||
return base
|
||||
}
|
||||
|
||||
return blurredImage
|
||||
}
|
||||
|
||||
public func addingBorder(_ border: Border) -> KFCrossPlatformImage
|
||||
{
|
||||
guard let _ = cgImage else {
|
||||
assertionFailure("[Kingfisher] Blend mode image only works for CG-based image.")
|
||||
return base
|
||||
}
|
||||
|
||||
let rect = CGRect(origin: .zero, size: size)
|
||||
return draw(to: rect.size, inverting: false) { context in
|
||||
|
||||
#if os(macOS)
|
||||
base.draw(in: rect)
|
||||
#else
|
||||
base.draw(in: rect, blendMode: .normal, alpha: 1.0)
|
||||
#endif
|
||||
|
||||
|
||||
let strokeRect = rect.insetBy(dx: border.lineWidth / 2, dy: border.lineWidth / 2)
|
||||
context.setStrokeColor(border.color.cgColor)
|
||||
context.setAlpha(border.color.rgba.a)
|
||||
|
||||
let line = pathForRoundCorner(
|
||||
rect: strokeRect,
|
||||
radius: border.radius,
|
||||
corners: border.roundingCorners,
|
||||
offsetBase: border.lineWidth
|
||||
)
|
||||
line.lineCapStyle = .square
|
||||
line.lineWidth = border.lineWidth
|
||||
line.stroke()
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Overlay
|
||||
/// Creates an image from `base` image with a color overlay layer.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - color: The color should be use to overlay.
|
||||
/// - fraction: Fraction of input color. From 0.0 to 1.0. 0.0 means solid color,
|
||||
/// 1.0 means transparent overlay.
|
||||
/// - Returns: An image with a color overlay applied.
|
||||
///
|
||||
/// - Note: This method only works for CG-based image. The current image scale is kept.
|
||||
/// For any non-CG-based image, `base` itself is returned.
|
||||
public func overlaying(with color: KFCrossPlatformColor, fraction: CGFloat) -> KFCrossPlatformImage {
|
||||
|
||||
guard let _ = cgImage else {
|
||||
assertionFailure("[Kingfisher] Overlaying only works for CG-based image.")
|
||||
return base
|
||||
}
|
||||
|
||||
let rect = CGRect(x: 0, y: 0, width: size.width, height: size.height)
|
||||
return draw(to: rect.size, inverting: false) { context in
|
||||
#if os(macOS)
|
||||
base.draw(in: rect)
|
||||
if fraction > 0 {
|
||||
color.withAlphaComponent(1 - fraction).set()
|
||||
rect.fill(using: .sourceAtop)
|
||||
}
|
||||
#else
|
||||
color.set()
|
||||
UIRectFill(rect)
|
||||
base.draw(in: rect, blendMode: .destinationIn, alpha: 1.0)
|
||||
|
||||
if fraction > 0 {
|
||||
base.draw(in: rect, blendMode: .sourceAtop, alpha: fraction)
|
||||
}
|
||||
#endif
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Tint
|
||||
/// Creates an image from `base` image with a color tint.
|
||||
///
|
||||
/// - Parameter color: The color should be used to tint `base`
|
||||
/// - Returns: An image with a color tint applied.
|
||||
public func tinted(with color: KFCrossPlatformColor) -> KFCrossPlatformImage {
|
||||
#if os(watchOS)
|
||||
return base
|
||||
#else
|
||||
return apply(.tint(color))
|
||||
#endif
|
||||
}
|
||||
|
||||
// MARK: Color Control
|
||||
|
||||
/// Create an image from `self` with color control.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - brightness: Brightness changing to image.
|
||||
/// - contrast: Contrast changing to image.
|
||||
/// - saturation: Saturation changing to image.
|
||||
/// - inputEV: InputEV changing to image.
|
||||
/// - Returns: An image with color control applied.
|
||||
public func adjusted(brightness: CGFloat, contrast: CGFloat, saturation: CGFloat, inputEV: CGFloat) -> KFCrossPlatformImage {
|
||||
#if os(watchOS)
|
||||
return base
|
||||
#else
|
||||
return apply(.colorControl((brightness, contrast, saturation, inputEV)))
|
||||
#endif
|
||||
}
|
||||
|
||||
/// Return an image with given scale.
|
||||
///
|
||||
/// - Parameter scale: Target scale factor the new image should have.
|
||||
/// - Returns: The image with target scale. If the base image is already in the scale, `base` will be returned.
|
||||
public func scaled(to scale: CGFloat) -> KFCrossPlatformImage {
|
||||
guard scale != self.scale else {
|
||||
return base
|
||||
}
|
||||
guard let cgImage = cgImage else {
|
||||
assertionFailure("[Kingfisher] Scaling only works for CG-based image.")
|
||||
return base
|
||||
}
|
||||
return KingfisherWrapper.image(cgImage: cgImage, scale: scale, refImage: base)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Decoding Image
|
||||
extension KingfisherWrapper where Base: KFCrossPlatformImage {
|
||||
|
||||
/// Returns the decoded image of the `base` image. It will draw the image in a plain context and return the data
|
||||
/// from it. This could improve the drawing performance when an image is just created from data but not yet
|
||||
/// displayed for the first time.
|
||||
///
|
||||
/// - Note: This method only works for CG-based image. The current image scale is kept.
|
||||
/// For any non-CG-based image or animated image, `base` itself is returned.
|
||||
public var decoded: KFCrossPlatformImage { return decoded(scale: scale) }
|
||||
|
||||
/// Returns decoded image of the `base` image at a given scale. It will draw the image in a plain context and
|
||||
/// return the data from it. This could improve the drawing performance when an image is just created from
|
||||
/// data but not yet displayed for the first time.
|
||||
///
|
||||
/// - Parameter scale: The given scale of target image should be.
|
||||
/// - Returns: The decoded image ready to be displayed.
|
||||
///
|
||||
/// - Note: This method only works for CG-based image. The current image scale is kept.
|
||||
/// For any non-CG-based image or animated image, `base` itself is returned.
|
||||
public func decoded(scale: CGFloat) -> KFCrossPlatformImage {
|
||||
// Prevent animated image (GIF) losing it's images
|
||||
#if os(iOS)
|
||||
if frameSource != nil { return base }
|
||||
#else
|
||||
if images != nil { return base }
|
||||
#endif
|
||||
|
||||
guard let imageRef = cgImage else {
|
||||
assertionFailure("[Kingfisher] Decoding only works for CG-based image.")
|
||||
return base
|
||||
}
|
||||
|
||||
let size = CGSize(width: CGFloat(imageRef.width) / scale, height: CGFloat(imageRef.height) / scale)
|
||||
return draw(to: size, inverting: true, scale: scale) { context in
|
||||
context.draw(imageRef, in: CGRect(origin: .zero, size: size))
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns decoded image of the `base` image at a given scale. It will draw the image in a plain context and
|
||||
/// return the data from it. This could improve the drawing performance when an image is just created from
|
||||
/// data but not yet displayed for the first time.
|
||||
///
|
||||
/// - Parameter context: The context for drawing.
|
||||
/// - Returns: The decoded image ready to be displayed.
|
||||
///
|
||||
/// - Note: This method only works for CG-based image. The current image scale is kept.
|
||||
/// For any non-CG-based image or animated image, `base` itself is returned.
|
||||
public func decoded(on context: CGContext) -> KFCrossPlatformImage {
|
||||
// Prevent animated image (GIF) losing it's images
|
||||
#if os(iOS)
|
||||
if frameSource != nil { return base }
|
||||
#else
|
||||
if images != nil { return base }
|
||||
#endif
|
||||
|
||||
guard let refImage = cgImage,
|
||||
let decodedRefImage = refImage.decoded(on: context, scale: scale) else
|
||||
{
|
||||
assertionFailure("[Kingfisher] Decoding only works for CG-based image.")
|
||||
return base
|
||||
}
|
||||
return KingfisherWrapper.image(cgImage: decodedRefImage, scale: scale, refImage: base)
|
||||
}
|
||||
}
|
||||
|
||||
extension CGImage {
|
||||
func decoded(on context: CGContext, scale: CGFloat) -> CGImage? {
|
||||
let size = CGSize(width: CGFloat(self.width) / scale, height: CGFloat(self.height) / scale)
|
||||
context.draw(self, in: CGRect(origin: .zero, size: size))
|
||||
guard let decodedImageRef = context.makeImage() else {
|
||||
return nil
|
||||
}
|
||||
return decodedImageRef
|
||||
}
|
||||
}
|
||||
|
||||
extension KingfisherWrapper where Base: KFCrossPlatformImage {
|
||||
func draw(
|
||||
to size: CGSize,
|
||||
inverting: Bool,
|
||||
scale: CGFloat? = nil,
|
||||
refImage: KFCrossPlatformImage? = nil,
|
||||
draw: (CGContext) -> Bool // Whether use the refImage (`true`) or ignore image orientation (`false`)
|
||||
) -> KFCrossPlatformImage
|
||||
{
|
||||
#if os(macOS) || os(watchOS)
|
||||
let targetScale = scale ?? self.scale
|
||||
GraphicsContext.begin(size: size, scale: targetScale)
|
||||
guard let context = GraphicsContext.current(size: size, scale: targetScale, inverting: inverting, cgImage: cgImage) else {
|
||||
assertionFailure("[Kingfisher] Failed to create CG context for blurring image.")
|
||||
return base
|
||||
}
|
||||
defer { GraphicsContext.end() }
|
||||
let useRefImage = draw(context)
|
||||
guard let cgImage = context.makeImage() else {
|
||||
return base
|
||||
}
|
||||
let ref = useRefImage ? (refImage ?? base) : nil
|
||||
return KingfisherWrapper.image(cgImage: cgImage, scale: targetScale, refImage: ref)
|
||||
#else
|
||||
|
||||
let format = UIGraphicsImageRendererFormat.preferred()
|
||||
format.scale = scale ?? self.scale
|
||||
let renderer = UIGraphicsImageRenderer(size: size, format: format)
|
||||
|
||||
var useRefImage: Bool = false
|
||||
let image = renderer.image { rendererContext in
|
||||
|
||||
let context = rendererContext.cgContext
|
||||
if inverting { // If drawing a CGImage, we need to make context flipped.
|
||||
context.scaleBy(x: 1.0, y: -1.0)
|
||||
context.translateBy(x: 0, y: -size.height)
|
||||
}
|
||||
|
||||
useRefImage = draw(context)
|
||||
}
|
||||
if useRefImage {
|
||||
guard let cgImage = image.cgImage else {
|
||||
return base
|
||||
}
|
||||
let ref = refImage ?? base
|
||||
return KingfisherWrapper.image(cgImage: cgImage, scale: format.scale, refImage: ref)
|
||||
} else {
|
||||
return image
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if os(macOS)
|
||||
func fixedForRetinaPixel(cgImage: CGImage, to size: CGSize) -> KFCrossPlatformImage {
|
||||
|
||||
let image = KFCrossPlatformImage(cgImage: cgImage, size: base.size)
|
||||
let rect = CGRect(origin: CGPoint(x: 0, y: 0), size: size)
|
||||
|
||||
return draw(to: self.size, inverting: false) { context in
|
||||
image.draw(in: rect, from: .zero, operation: .copy, fraction: 1.0)
|
||||
return false
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
130
Pods/Kingfisher/Sources/Image/ImageFormat.swift
generated
Normal file
130
Pods/Kingfisher/Sources/Image/ImageFormat.swift
generated
Normal file
@@ -0,0 +1,130 @@
|
||||
//
|
||||
// ImageFormat.swift
|
||||
// Kingfisher
|
||||
//
|
||||
// Created by onevcat on 2018/09/28.
|
||||
//
|
||||
// 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.
|
||||
|
||||
import Foundation
|
||||
|
||||
/// Represents image format.
|
||||
///
|
||||
/// - unknown: The format cannot be recognized or not supported yet.
|
||||
/// - PNG: PNG image format.
|
||||
/// - JPEG: JPEG image format.
|
||||
/// - GIF: GIF image format.
|
||||
public enum ImageFormat {
|
||||
/// The format cannot be recognized or not supported yet.
|
||||
case unknown
|
||||
/// PNG image format.
|
||||
case PNG
|
||||
/// JPEG image format.
|
||||
case JPEG
|
||||
/// GIF image format.
|
||||
case GIF
|
||||
|
||||
struct HeaderData {
|
||||
static var PNG: [UInt8] = [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]
|
||||
static var JPEG_SOI: [UInt8] = [0xFF, 0xD8]
|
||||
static var JPEG_IF: [UInt8] = [0xFF]
|
||||
static var GIF: [UInt8] = [0x47, 0x49, 0x46]
|
||||
}
|
||||
|
||||
/// https://en.wikipedia.org/wiki/JPEG
|
||||
public enum JPEGMarker {
|
||||
case SOF0 //baseline
|
||||
case SOF2 //progressive
|
||||
case DHT //Huffman Table
|
||||
case DQT //Quantization Table
|
||||
case DRI //Restart Interval
|
||||
case SOS //Start Of Scan
|
||||
case RSTn(UInt8) //Restart
|
||||
case APPn //Application-specific
|
||||
case COM //Comment
|
||||
case EOI //End Of Image
|
||||
|
||||
var bytes: [UInt8] {
|
||||
switch self {
|
||||
case .SOF0: return [0xFF, 0xC0]
|
||||
case .SOF2: return [0xFF, 0xC2]
|
||||
case .DHT: return [0xFF, 0xC4]
|
||||
case .DQT: return [0xFF, 0xDB]
|
||||
case .DRI: return [0xFF, 0xDD]
|
||||
case .SOS: return [0xFF, 0xDA]
|
||||
case .RSTn(let n): return [0xFF, 0xD0 + n]
|
||||
case .APPn: return [0xFF, 0xE0]
|
||||
case .COM: return [0xFF, 0xFE]
|
||||
case .EOI: return [0xFF, 0xD9]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
extension Data: KingfisherCompatibleValue {}
|
||||
|
||||
// MARK: - Misc Helpers
|
||||
extension KingfisherWrapper where Base == Data {
|
||||
/// Gets the image format corresponding to the data.
|
||||
public var imageFormat: ImageFormat {
|
||||
guard base.count > 8 else { return .unknown }
|
||||
|
||||
var buffer = [UInt8](repeating: 0, count: 8)
|
||||
base.copyBytes(to: &buffer, count: 8)
|
||||
|
||||
if buffer == ImageFormat.HeaderData.PNG {
|
||||
return .PNG
|
||||
|
||||
} else if buffer[0] == ImageFormat.HeaderData.JPEG_SOI[0],
|
||||
buffer[1] == ImageFormat.HeaderData.JPEG_SOI[1],
|
||||
buffer[2] == ImageFormat.HeaderData.JPEG_IF[0]
|
||||
{
|
||||
return .JPEG
|
||||
|
||||
} else if buffer[0] == ImageFormat.HeaderData.GIF[0],
|
||||
buffer[1] == ImageFormat.HeaderData.GIF[1],
|
||||
buffer[2] == ImageFormat.HeaderData.GIF[2]
|
||||
{
|
||||
return .GIF
|
||||
}
|
||||
|
||||
return .unknown
|
||||
}
|
||||
|
||||
public func contains(jpeg marker: ImageFormat.JPEGMarker) -> Bool {
|
||||
guard imageFormat == .JPEG else {
|
||||
return false
|
||||
}
|
||||
|
||||
let bytes = [UInt8](base)
|
||||
let markerBytes = marker.bytes
|
||||
for (index, item) in bytes.enumerated() where bytes.count > index + 1 {
|
||||
guard
|
||||
item == markerBytes.first,
|
||||
bytes[index + 1] == markerBytes[1] else {
|
||||
continue
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
922
Pods/Kingfisher/Sources/Image/ImageProcessor.swift
generated
Normal file
922
Pods/Kingfisher/Sources/Image/ImageProcessor.swift
generated
Normal file
@@ -0,0 +1,922 @@
|
||||
//
|
||||
// ImageProcessor.swift
|
||||
// Kingfisher
|
||||
//
|
||||
// Created by Wei Wang on 2016/08/26.
|
||||
//
|
||||
// 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.
|
||||
|
||||
import Foundation
|
||||
import CoreGraphics
|
||||
|
||||
#if canImport(AppKit) && !targetEnvironment(macCatalyst)
|
||||
import AppKit
|
||||
#else
|
||||
import UIKit
|
||||
#endif
|
||||
|
||||
/// Represents an item which could be processed by an `ImageProcessor`.
|
||||
///
|
||||
/// - image: Input image. The processor should provide a way to apply
|
||||
/// processing on this `image` and return the result image.
|
||||
/// - data: Input data. The processor should provide a way to apply
|
||||
/// processing on this `data` and return the result image.
|
||||
public enum ImageProcessItem {
|
||||
|
||||
/// Input image. The processor should provide a way to apply
|
||||
/// processing on this `image` and return the result image.
|
||||
case image(KFCrossPlatformImage)
|
||||
|
||||
/// Input data. The processor should provide a way to apply
|
||||
/// processing on this `data` and return the result image.
|
||||
case data(Data)
|
||||
}
|
||||
|
||||
/// An `ImageProcessor` would be used to convert some downloaded data to an image.
|
||||
public protocol ImageProcessor {
|
||||
/// Identifier of the processor. It will be used to identify the processor when
|
||||
/// caching and retrieving an image. You might want to make sure that processors with
|
||||
/// same properties/functionality have the same identifiers, so correct processed images
|
||||
/// could be retrieved with proper key.
|
||||
///
|
||||
/// - Note: Do not supply an empty string for a customized processor, which is already reserved by
|
||||
/// the `DefaultImageProcessor`. It is recommended to use a reverse domain name notation string of
|
||||
/// your own for the identifier.
|
||||
var identifier: String { get }
|
||||
|
||||
/// Processes the input `ImageProcessItem` with this processor.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - item: Input item which will be processed by `self`.
|
||||
/// - options: The parsed options when processing the item.
|
||||
/// - Returns: The processed image.
|
||||
///
|
||||
/// - Note: The return value should be `nil` if processing failed while converting an input item to image.
|
||||
/// If `nil` received by the processing caller, an error will be reported and the process flow stops.
|
||||
/// If the processing flow is not critical for your flow, then when the input item is already an image
|
||||
/// (`.image` case) and there is any errors in the processing, you could return the input image itself
|
||||
/// to keep the processing pipeline continuing.
|
||||
/// - Note: Most processor only supports CG-based images. watchOS is not supported for processors containing
|
||||
/// a filter, the input image will be returned directly on watchOS.
|
||||
func process(item: ImageProcessItem, options: KingfisherParsedOptionsInfo) -> KFCrossPlatformImage?
|
||||
}
|
||||
|
||||
extension ImageProcessor {
|
||||
|
||||
/// Appends an `ImageProcessor` to another. The identifier of the new `ImageProcessor`
|
||||
/// will be "\(self.identifier)|>\(another.identifier)".
|
||||
///
|
||||
/// - Parameter another: An `ImageProcessor` you want to append to `self`.
|
||||
/// - Returns: The new `ImageProcessor` will process the image in the order
|
||||
/// of the two processors concatenated.
|
||||
public func append(another: ImageProcessor) -> ImageProcessor {
|
||||
let newIdentifier = identifier.appending("|>\(another.identifier)")
|
||||
return GeneralProcessor(identifier: newIdentifier) {
|
||||
item, options in
|
||||
if let image = self.process(item: item, options: options) {
|
||||
return another.process(item: .image(image), options: options)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func ==(left: ImageProcessor, right: ImageProcessor) -> Bool {
|
||||
return left.identifier == right.identifier
|
||||
}
|
||||
|
||||
func !=(left: ImageProcessor, right: ImageProcessor) -> Bool {
|
||||
return !(left == right)
|
||||
}
|
||||
|
||||
typealias ProcessorImp = ((ImageProcessItem, KingfisherParsedOptionsInfo) -> KFCrossPlatformImage?)
|
||||
struct GeneralProcessor: ImageProcessor {
|
||||
let identifier: String
|
||||
let p: ProcessorImp
|
||||
func process(item: ImageProcessItem, options: KingfisherParsedOptionsInfo) -> KFCrossPlatformImage? {
|
||||
return p(item, options)
|
||||
}
|
||||
}
|
||||
|
||||
/// The default processor. It converts the input data to a valid image.
|
||||
/// Images of .PNG, .JPEG and .GIF format are supported.
|
||||
/// If an image item is given as `.image` case, `DefaultImageProcessor` will
|
||||
/// do nothing on it and return the associated image.
|
||||
public struct DefaultImageProcessor: ImageProcessor {
|
||||
|
||||
/// A default `DefaultImageProcessor` could be used across.
|
||||
public static let `default` = DefaultImageProcessor()
|
||||
|
||||
/// Identifier of the processor.
|
||||
/// - Note: See documentation of `ImageProcessor` protocol for more.
|
||||
public let identifier = ""
|
||||
|
||||
/// Creates a `DefaultImageProcessor`. Use `DefaultImageProcessor.default` to get an instance,
|
||||
/// if you do not have a good reason to create your own `DefaultImageProcessor`.
|
||||
public init() {}
|
||||
|
||||
/// Processes the input `ImageProcessItem` with this processor.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - item: Input item which will be processed by `self`.
|
||||
/// - options: Options when processing the item.
|
||||
/// - Returns: The processed image.
|
||||
///
|
||||
/// - Note: See documentation of `ImageProcessor` protocol for more.
|
||||
public func process(item: ImageProcessItem, options: KingfisherParsedOptionsInfo) -> KFCrossPlatformImage? {
|
||||
switch item {
|
||||
case .image(let image):
|
||||
return image.kf.scaled(to: options.scaleFactor)
|
||||
case .data(let data):
|
||||
return KingfisherWrapper.image(data: data, options: options.imageCreatingOptions)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents the rect corner setting when processing a round corner image.
|
||||
public struct RectCorner: OptionSet {
|
||||
|
||||
/// Raw value of the rect corner.
|
||||
public let rawValue: Int
|
||||
|
||||
/// Represents the top left corner.
|
||||
public static let topLeft = RectCorner(rawValue: 1 << 0)
|
||||
|
||||
/// Represents the top right corner.
|
||||
public static let topRight = RectCorner(rawValue: 1 << 1)
|
||||
|
||||
/// Represents the bottom left corner.
|
||||
public static let bottomLeft = RectCorner(rawValue: 1 << 2)
|
||||
|
||||
/// Represents the bottom right corner.
|
||||
public static let bottomRight = RectCorner(rawValue: 1 << 3)
|
||||
|
||||
/// Represents all corners.
|
||||
public static let all: RectCorner = [.topLeft, .topRight, .bottomLeft, .bottomRight]
|
||||
|
||||
/// Creates a `RectCorner` option set with a given value.
|
||||
///
|
||||
/// - Parameter rawValue: The value represents a certain corner option.
|
||||
public init(rawValue: Int) {
|
||||
self.rawValue = rawValue
|
||||
}
|
||||
|
||||
var cornerIdentifier: String {
|
||||
if self == .all {
|
||||
return ""
|
||||
}
|
||||
return "_corner(\(rawValue))"
|
||||
}
|
||||
}
|
||||
|
||||
#if !os(macOS)
|
||||
/// Processor for adding an blend mode to images. Only CG-based images are supported.
|
||||
public struct BlendImageProcessor: ImageProcessor {
|
||||
|
||||
/// Identifier of the processor.
|
||||
/// - Note: See documentation of `ImageProcessor` protocol for more.
|
||||
public let identifier: String
|
||||
|
||||
/// Blend Mode will be used to blend the input image.
|
||||
public let blendMode: CGBlendMode
|
||||
|
||||
/// Alpha will be used when blend image.
|
||||
public let alpha: CGFloat
|
||||
|
||||
/// Background color of the output image. If `nil`, it will stay transparent.
|
||||
public let backgroundColor: KFCrossPlatformColor?
|
||||
|
||||
/// Creates a `BlendImageProcessor`.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - blendMode: Blend Mode will be used to blend the input image.
|
||||
/// - alpha: Alpha will be used when blend image. From 0.0 to 1.0. 1.0 means solid image,
|
||||
/// 0.0 means transparent image (not visible at all). Default is 1.0.
|
||||
/// - backgroundColor: Background color to apply for the output image. Default is `nil`.
|
||||
public init(blendMode: CGBlendMode, alpha: CGFloat = 1.0, backgroundColor: KFCrossPlatformColor? = nil) {
|
||||
self.blendMode = blendMode
|
||||
self.alpha = alpha
|
||||
self.backgroundColor = backgroundColor
|
||||
var identifier = "com.onevcat.Kingfisher.BlendImageProcessor(\(blendMode.rawValue),\(alpha))"
|
||||
if let color = backgroundColor {
|
||||
identifier.append("_\(color.rgbaDescription)")
|
||||
}
|
||||
self.identifier = identifier
|
||||
}
|
||||
|
||||
/// Processes the input `ImageProcessItem` with this processor.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - item: Input item which will be processed by `self`.
|
||||
/// - options: Options when processing the item.
|
||||
/// - Returns: The processed image.
|
||||
///
|
||||
/// - Note: See documentation of `ImageProcessor` protocol for more.
|
||||
public func process(item: ImageProcessItem, options: KingfisherParsedOptionsInfo) -> KFCrossPlatformImage? {
|
||||
switch item {
|
||||
case .image(let image):
|
||||
return image.kf.scaled(to: options.scaleFactor)
|
||||
.kf.image(withBlendMode: blendMode, alpha: alpha, backgroundColor: backgroundColor)
|
||||
case .data:
|
||||
return (DefaultImageProcessor.default |> self).process(item: item, options: options)
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if os(macOS)
|
||||
/// Processor for adding an compositing operation to images. Only CG-based images are supported in macOS.
|
||||
public struct CompositingImageProcessor: ImageProcessor {
|
||||
|
||||
/// Identifier of the processor.
|
||||
/// - Note: See documentation of `ImageProcessor` protocol for more.
|
||||
public let identifier: String
|
||||
|
||||
/// Compositing operation will be used to the input image.
|
||||
public let compositingOperation: NSCompositingOperation
|
||||
|
||||
/// Alpha will be used when compositing image.
|
||||
public let alpha: CGFloat
|
||||
|
||||
/// Background color of the output image. If `nil`, it will stay transparent.
|
||||
public let backgroundColor: KFCrossPlatformColor?
|
||||
|
||||
/// Creates a `CompositingImageProcessor`
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - compositingOperation: Compositing operation will be used to the input image.
|
||||
/// - alpha: Alpha will be used when compositing image.
|
||||
/// From 0.0 to 1.0. 1.0 means solid image, 0.0 means transparent image.
|
||||
/// Default is 1.0.
|
||||
/// - backgroundColor: Background color to apply for the output image. Default is `nil`.
|
||||
public init(compositingOperation: NSCompositingOperation,
|
||||
alpha: CGFloat = 1.0,
|
||||
backgroundColor: KFCrossPlatformColor? = nil)
|
||||
{
|
||||
self.compositingOperation = compositingOperation
|
||||
self.alpha = alpha
|
||||
self.backgroundColor = backgroundColor
|
||||
var identifier = "com.onevcat.Kingfisher.CompositingImageProcessor(\(compositingOperation.rawValue),\(alpha))"
|
||||
if let color = backgroundColor {
|
||||
identifier.append("_\(color.rgbaDescription)")
|
||||
}
|
||||
self.identifier = identifier
|
||||
}
|
||||
|
||||
/// Processes the input `ImageProcessItem` with this processor.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - item: Input item which will be processed by `self`.
|
||||
/// - options: Options when processing the item.
|
||||
/// - Returns: The processed image.
|
||||
///
|
||||
/// - Note: See documentation of `ImageProcessor` protocol for more.
|
||||
public func process(item: ImageProcessItem, options: KingfisherParsedOptionsInfo) -> KFCrossPlatformImage? {
|
||||
switch item {
|
||||
case .image(let image):
|
||||
return image.kf.scaled(to: options.scaleFactor)
|
||||
.kf.image(
|
||||
withCompositingOperation: compositingOperation,
|
||||
alpha: alpha,
|
||||
backgroundColor: backgroundColor)
|
||||
case .data:
|
||||
return (DefaultImageProcessor.default |> self).process(item: item, options: options)
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/// Represents a radius specified in a `RoundCornerImageProcessor`.
|
||||
public enum Radius {
|
||||
/// The radius should be calculated as a fraction of the image width. Typically the associated value should be
|
||||
/// between 0 and 0.5, where 0 represents no radius and 0.5 represents using half of the image width.
|
||||
case widthFraction(CGFloat)
|
||||
/// The radius should be calculated as a fraction of the image height. Typically the associated value should be
|
||||
/// between 0 and 0.5, where 0 represents no radius and 0.5 represents using half of the image height.
|
||||
case heightFraction(CGFloat)
|
||||
/// Use a fixed point value as the round corner radius.
|
||||
case point(CGFloat)
|
||||
|
||||
var radiusIdentifier: String {
|
||||
switch self {
|
||||
case .widthFraction(let f):
|
||||
return "w_frac_\(f)"
|
||||
case .heightFraction(let f):
|
||||
return "h_frac_\(f)"
|
||||
case .point(let p):
|
||||
return p.description
|
||||
}
|
||||
}
|
||||
|
||||
public func compute(with size: CGSize) -> CGFloat {
|
||||
let cornerRadius: CGFloat
|
||||
switch self {
|
||||
case .point(let point):
|
||||
cornerRadius = point
|
||||
case .widthFraction(let widthFraction):
|
||||
cornerRadius = size.width * widthFraction
|
||||
case .heightFraction(let heightFraction):
|
||||
cornerRadius = size.height * heightFraction
|
||||
}
|
||||
return cornerRadius
|
||||
}
|
||||
}
|
||||
|
||||
/// Processor for making round corner images. Only CG-based images are supported in macOS,
|
||||
/// if a non-CG image passed in, the processor will do nothing.
|
||||
///
|
||||
/// - Note: The input image will be rendered with round corner pixels removed. If the image itself does not contain
|
||||
/// alpha channel (for example, a JPEG image), the processed image will contain an alpha channel in memory in order
|
||||
/// to show correctly. However, when cached to disk, Kingfisher respects the original image format by default. That
|
||||
/// means the alpha channel will be removed for these images. When you load the processed image from cache again, you
|
||||
/// will lose transparent corner.
|
||||
///
|
||||
/// You could use `FormatIndicatedCacheSerializer.png` to force Kingfisher to serialize the image to PNG format in this
|
||||
/// case.
|
||||
///
|
||||
public struct RoundCornerImageProcessor: ImageProcessor {
|
||||
|
||||
/// Identifier of the processor.
|
||||
/// - Note: See documentation of `ImageProcessor` protocol for more.
|
||||
public let identifier: String
|
||||
|
||||
/// The radius will be applied in processing. Specify a certain point value with `.point`, or a fraction of the
|
||||
/// target image with `.widthFraction`. or `.heightFraction`. For example, given a square image with width and
|
||||
/// height equals, `.widthFraction(0.5)` means use half of the length of size and makes the final image a round one.
|
||||
public let radius: Radius
|
||||
|
||||
/// The target corners which will be applied rounding.
|
||||
public let roundingCorners: RectCorner
|
||||
|
||||
/// Target size of output image should be. If `nil`, the image will keep its original size after processing.
|
||||
public let targetSize: CGSize?
|
||||
|
||||
/// Background color of the output image. If `nil`, it will use a transparent background.
|
||||
public let backgroundColor: KFCrossPlatformColor?
|
||||
|
||||
/// Creates a `RoundCornerImageProcessor`.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - cornerRadius: Corner radius in point will be applied in processing.
|
||||
/// - targetSize: Target size of output image should be. If `nil`,
|
||||
/// the image will keep its original size after processing.
|
||||
/// Default is `nil`.
|
||||
/// - corners: The target corners which will be applied rounding. Default is `.all`.
|
||||
/// - backgroundColor: Background color to apply for the output image. Default is `nil`.
|
||||
///
|
||||
/// - Note:
|
||||
///
|
||||
/// This initializer accepts a concrete point value for `cornerRadius`. If you do not know the image size, but still
|
||||
/// want to apply a full round-corner (making the final image a round one), or specify the corner radius as a
|
||||
/// fraction of one dimension of the target image, use the `Radius` version instead.
|
||||
///
|
||||
public init(
|
||||
cornerRadius: CGFloat,
|
||||
targetSize: CGSize? = nil,
|
||||
roundingCorners corners: RectCorner = .all,
|
||||
backgroundColor: KFCrossPlatformColor? = nil
|
||||
)
|
||||
{
|
||||
let radius = Radius.point(cornerRadius)
|
||||
self.init(radius: radius, targetSize: targetSize, roundingCorners: corners, backgroundColor: backgroundColor)
|
||||
}
|
||||
|
||||
/// Creates a `RoundCornerImageProcessor`.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - radius: The radius will be applied in processing.
|
||||
/// - targetSize: Target size of output image should be. If `nil`,
|
||||
/// the image will keep its original size after processing.
|
||||
/// Default is `nil`.
|
||||
/// - corners: The target corners which will be applied rounding. Default is `.all`.
|
||||
/// - backgroundColor: Background color to apply for the output image. Default is `nil`.
|
||||
public init(
|
||||
radius: Radius,
|
||||
targetSize: CGSize? = nil,
|
||||
roundingCorners corners: RectCorner = .all,
|
||||
backgroundColor: KFCrossPlatformColor? = nil
|
||||
)
|
||||
{
|
||||
self.radius = radius
|
||||
self.targetSize = targetSize
|
||||
self.roundingCorners = corners
|
||||
self.backgroundColor = backgroundColor
|
||||
|
||||
self.identifier = {
|
||||
var identifier = ""
|
||||
|
||||
if let size = targetSize {
|
||||
identifier = "com.onevcat.Kingfisher.RoundCornerImageProcessor" +
|
||||
"(\(radius.radiusIdentifier)_\(size)\(corners.cornerIdentifier))"
|
||||
} else {
|
||||
identifier = "com.onevcat.Kingfisher.RoundCornerImageProcessor" +
|
||||
"(\(radius.radiusIdentifier)\(corners.cornerIdentifier))"
|
||||
}
|
||||
if let backgroundColor = backgroundColor {
|
||||
identifier += "_\(backgroundColor)"
|
||||
}
|
||||
|
||||
return identifier
|
||||
}()
|
||||
}
|
||||
|
||||
/// Processes the input `ImageProcessItem` with this processor.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - item: Input item which will be processed by `self`.
|
||||
/// - options: Options when processing the item.
|
||||
/// - Returns: The processed image.
|
||||
///
|
||||
/// - Note: See documentation of `ImageProcessor` protocol for more.
|
||||
public func process(item: ImageProcessItem, options: KingfisherParsedOptionsInfo) -> KFCrossPlatformImage? {
|
||||
switch item {
|
||||
case .image(let image):
|
||||
let size = targetSize ?? image.kf.size
|
||||
return image.kf.scaled(to: options.scaleFactor)
|
||||
.kf.image(
|
||||
withRadius: radius,
|
||||
fit: size,
|
||||
roundingCorners: roundingCorners,
|
||||
backgroundColor: backgroundColor)
|
||||
case .data:
|
||||
return (DefaultImageProcessor.default |> self).process(item: item, options: options)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public struct Border {
|
||||
public var color: KFCrossPlatformColor
|
||||
public var lineWidth: CGFloat
|
||||
|
||||
/// The radius will be applied in processing. Specify a certain point value with `.point`, or a fraction of the
|
||||
/// target image with `.widthFraction`. or `.heightFraction`. For example, given a square image with width and
|
||||
/// height equals, `.widthFraction(0.5)` means use half of the length of size and makes the final image a round one.
|
||||
public var radius: Radius
|
||||
|
||||
/// The target corners which will be applied rounding.
|
||||
public var roundingCorners: RectCorner
|
||||
|
||||
public init(
|
||||
color: KFCrossPlatformColor = .black,
|
||||
lineWidth: CGFloat = 4,
|
||||
radius: Radius = .point(0),
|
||||
roundingCorners: RectCorner = .all
|
||||
) {
|
||||
self.color = color
|
||||
self.lineWidth = lineWidth
|
||||
self.radius = radius
|
||||
self.roundingCorners = roundingCorners
|
||||
}
|
||||
|
||||
var identifier: String {
|
||||
"\(color.rgbaDescription)_\(lineWidth)_\(radius.radiusIdentifier)_\(roundingCorners.cornerIdentifier)"
|
||||
}
|
||||
}
|
||||
|
||||
public struct BorderImageProcessor: ImageProcessor {
|
||||
public var identifier: String { "com.onevcat.Kingfisher.RoundCornerImageProcessor(\(border)" }
|
||||
public let border: Border
|
||||
|
||||
public init(border: Border) {
|
||||
self.border = border
|
||||
}
|
||||
|
||||
public func process(item: ImageProcessItem, options: KingfisherParsedOptionsInfo) -> KFCrossPlatformImage? {
|
||||
switch item {
|
||||
case .image(let image):
|
||||
return image.kf.addingBorder(border)
|
||||
case .data:
|
||||
return (DefaultImageProcessor.default |> self).process(item: item, options: options)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents how a size adjusts itself to fit a target size.
|
||||
///
|
||||
/// - none: Not scale the content.
|
||||
/// - aspectFit: Scales the content to fit the size of the view by maintaining the aspect ratio.
|
||||
/// - aspectFill: Scales the content to fill the size of the view.
|
||||
public enum ContentMode {
|
||||
/// Not scale the content.
|
||||
case none
|
||||
/// Scales the content to fit the size of the view by maintaining the aspect ratio.
|
||||
case aspectFit
|
||||
/// Scales the content to fill the size of the view.
|
||||
case aspectFill
|
||||
}
|
||||
|
||||
/// Processor for resizing images.
|
||||
/// If you need to resize a data represented image to a smaller size, use `DownsamplingImageProcessor`
|
||||
/// instead, which is more efficient and uses less memory.
|
||||
public struct ResizingImageProcessor: ImageProcessor {
|
||||
|
||||
/// Identifier of the processor.
|
||||
/// - Note: See documentation of `ImageProcessor` protocol for more.
|
||||
public let identifier: String
|
||||
|
||||
/// The reference size for resizing operation in point.
|
||||
public let referenceSize: CGSize
|
||||
|
||||
/// Target content mode of output image should be.
|
||||
/// Default is `.none`.
|
||||
public let targetContentMode: ContentMode
|
||||
|
||||
/// Creates a `ResizingImageProcessor`.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - referenceSize: The reference size for resizing operation in point.
|
||||
/// - mode: Target content mode of output image should be.
|
||||
///
|
||||
/// - Note:
|
||||
/// The instance of `ResizingImageProcessor` will follow its `mode` property
|
||||
/// and try to resizing the input images to fit or fill the `referenceSize`.
|
||||
/// That means if you are using a `mode` besides of `.none`, you may get an
|
||||
/// image with its size not be the same as the `referenceSize`.
|
||||
///
|
||||
/// **Example**: With input image size: {100, 200},
|
||||
/// `referenceSize`: {100, 100}, `mode`: `.aspectFit`,
|
||||
/// you will get an output image with size of {50, 100}, which "fit"s
|
||||
/// the `referenceSize`.
|
||||
///
|
||||
/// If you need an output image exactly to be a specified size, append or use
|
||||
/// a `CroppingImageProcessor`.
|
||||
public init(referenceSize: CGSize, mode: ContentMode = .none) {
|
||||
self.referenceSize = referenceSize
|
||||
self.targetContentMode = mode
|
||||
|
||||
if mode == .none {
|
||||
self.identifier = "com.onevcat.Kingfisher.ResizingImageProcessor(\(referenceSize))"
|
||||
} else {
|
||||
self.identifier = "com.onevcat.Kingfisher.ResizingImageProcessor(\(referenceSize), \(mode))"
|
||||
}
|
||||
}
|
||||
|
||||
/// Processes the input `ImageProcessItem` with this processor.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - item: Input item which will be processed by `self`.
|
||||
/// - options: Options when processing the item.
|
||||
/// - Returns: The processed image.
|
||||
///
|
||||
/// - Note: See documentation of `ImageProcessor` protocol for more.
|
||||
public func process(item: ImageProcessItem, options: KingfisherParsedOptionsInfo) -> KFCrossPlatformImage? {
|
||||
switch item {
|
||||
case .image(let image):
|
||||
return image.kf.scaled(to: options.scaleFactor)
|
||||
.kf.resize(to: referenceSize, for: targetContentMode)
|
||||
case .data:
|
||||
return (DefaultImageProcessor.default |> self).process(item: item, options: options)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Processor for adding blur effect to images. `Accelerate.framework` is used underhood for
|
||||
/// a better performance. A simulated Gaussian blur with specified blur radius will be applied.
|
||||
public struct BlurImageProcessor: ImageProcessor {
|
||||
|
||||
/// Identifier of the processor.
|
||||
/// - Note: See documentation of `ImageProcessor` protocol for more.
|
||||
public let identifier: String
|
||||
|
||||
/// Blur radius for the simulated Gaussian blur.
|
||||
public let blurRadius: CGFloat
|
||||
|
||||
/// Creates a `BlurImageProcessor`
|
||||
///
|
||||
/// - parameter blurRadius: Blur radius for the simulated Gaussian blur.
|
||||
public init(blurRadius: CGFloat) {
|
||||
self.blurRadius = blurRadius
|
||||
self.identifier = "com.onevcat.Kingfisher.BlurImageProcessor(\(blurRadius))"
|
||||
}
|
||||
|
||||
/// Processes the input `ImageProcessItem` with this processor.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - item: Input item which will be processed by `self`.
|
||||
/// - options: Options when processing the item.
|
||||
/// - Returns: The processed image.
|
||||
///
|
||||
/// - Note: See documentation of `ImageProcessor` protocol for more.
|
||||
public func process(item: ImageProcessItem, options: KingfisherParsedOptionsInfo) -> KFCrossPlatformImage? {
|
||||
switch item {
|
||||
case .image(let image):
|
||||
let radius = blurRadius * options.scaleFactor
|
||||
return image.kf.scaled(to: options.scaleFactor)
|
||||
.kf.blurred(withRadius: radius)
|
||||
case .data:
|
||||
return (DefaultImageProcessor.default |> self).process(item: item, options: options)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Processor for adding an overlay to images. Only CG-based images are supported in macOS.
|
||||
public struct OverlayImageProcessor: ImageProcessor {
|
||||
|
||||
/// Identifier of the processor.
|
||||
/// - Note: See documentation of `ImageProcessor` protocol for more.
|
||||
public let identifier: String
|
||||
|
||||
/// Overlay color will be used to overlay the input image.
|
||||
public let overlay: KFCrossPlatformColor
|
||||
|
||||
/// Fraction will be used when overlay the color to image.
|
||||
public let fraction: CGFloat
|
||||
|
||||
/// Creates an `OverlayImageProcessor`
|
||||
///
|
||||
/// - parameter overlay: Overlay color will be used to overlay the input image.
|
||||
/// - parameter fraction: Fraction will be used when overlay the color to image.
|
||||
/// From 0.0 to 1.0. 0.0 means solid color, 1.0 means transparent overlay.
|
||||
public init(overlay: KFCrossPlatformColor, fraction: CGFloat = 0.5) {
|
||||
self.overlay = overlay
|
||||
self.fraction = fraction
|
||||
self.identifier = "com.onevcat.Kingfisher.OverlayImageProcessor(\(overlay.rgbaDescription)_\(fraction))"
|
||||
}
|
||||
|
||||
/// Processes the input `ImageProcessItem` with this processor.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - item: Input item which will be processed by `self`.
|
||||
/// - options: Options when processing the item.
|
||||
/// - Returns: The processed image.
|
||||
///
|
||||
/// - Note: See documentation of `ImageProcessor` protocol for more.
|
||||
public func process(item: ImageProcessItem, options: KingfisherParsedOptionsInfo) -> KFCrossPlatformImage? {
|
||||
switch item {
|
||||
case .image(let image):
|
||||
return image.kf.scaled(to: options.scaleFactor)
|
||||
.kf.overlaying(with: overlay, fraction: fraction)
|
||||
case .data:
|
||||
return (DefaultImageProcessor.default |> self).process(item: item, options: options)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Processor for tint images with color. Only CG-based images are supported.
|
||||
public struct TintImageProcessor: ImageProcessor {
|
||||
|
||||
/// Identifier of the processor.
|
||||
/// - Note: See documentation of `ImageProcessor` protocol for more.
|
||||
public let identifier: String
|
||||
|
||||
/// Tint color will be used to tint the input image.
|
||||
public let tint: KFCrossPlatformColor
|
||||
|
||||
/// Creates a `TintImageProcessor`
|
||||
///
|
||||
/// - parameter tint: Tint color will be used to tint the input image.
|
||||
public init(tint: KFCrossPlatformColor) {
|
||||
self.tint = tint
|
||||
self.identifier = "com.onevcat.Kingfisher.TintImageProcessor(\(tint.rgbaDescription))"
|
||||
}
|
||||
|
||||
/// Processes the input `ImageProcessItem` with this processor.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - item: Input item which will be processed by `self`.
|
||||
/// - options: Options when processing the item.
|
||||
/// - Returns: The processed image.
|
||||
///
|
||||
/// - Note: See documentation of `ImageProcessor` protocol for more.
|
||||
public func process(item: ImageProcessItem, options: KingfisherParsedOptionsInfo) -> KFCrossPlatformImage? {
|
||||
switch item {
|
||||
case .image(let image):
|
||||
return image.kf.scaled(to: options.scaleFactor)
|
||||
.kf.tinted(with: tint)
|
||||
case .data:
|
||||
return (DefaultImageProcessor.default |> self).process(item: item, options: options)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Processor for applying some color control to images. Only CG-based images are supported.
|
||||
/// watchOS is not supported.
|
||||
public struct ColorControlsProcessor: ImageProcessor {
|
||||
|
||||
/// Identifier of the processor.
|
||||
/// - Note: See documentation of `ImageProcessor` protocol for more.
|
||||
public let identifier: String
|
||||
|
||||
/// Brightness changing to image.
|
||||
public let brightness: CGFloat
|
||||
|
||||
/// Contrast changing to image.
|
||||
public let contrast: CGFloat
|
||||
|
||||
/// Saturation changing to image.
|
||||
public let saturation: CGFloat
|
||||
|
||||
/// InputEV changing to image.
|
||||
public let inputEV: CGFloat
|
||||
|
||||
/// Creates a `ColorControlsProcessor`
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - brightness: Brightness changing to image.
|
||||
/// - contrast: Contrast changing to image.
|
||||
/// - saturation: Saturation changing to image.
|
||||
/// - inputEV: InputEV changing to image.
|
||||
public init(brightness: CGFloat, contrast: CGFloat, saturation: CGFloat, inputEV: CGFloat) {
|
||||
self.brightness = brightness
|
||||
self.contrast = contrast
|
||||
self.saturation = saturation
|
||||
self.inputEV = inputEV
|
||||
self.identifier = "com.onevcat.Kingfisher.ColorControlsProcessor(\(brightness)_\(contrast)_\(saturation)_\(inputEV))"
|
||||
}
|
||||
|
||||
/// Processes the input `ImageProcessItem` with this processor.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - item: Input item which will be processed by `self`.
|
||||
/// - options: Options when processing the item.
|
||||
/// - Returns: The processed image.
|
||||
///
|
||||
/// - Note: See documentation of `ImageProcessor` protocol for more.
|
||||
public func process(item: ImageProcessItem, options: KingfisherParsedOptionsInfo) -> KFCrossPlatformImage? {
|
||||
switch item {
|
||||
case .image(let image):
|
||||
return image.kf.scaled(to: options.scaleFactor)
|
||||
.kf.adjusted(brightness: brightness, contrast: contrast, saturation: saturation, inputEV: inputEV)
|
||||
case .data:
|
||||
return (DefaultImageProcessor.default |> self).process(item: item, options: options)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Processor for applying black and white effect to images. Only CG-based images are supported.
|
||||
/// watchOS is not supported.
|
||||
public struct BlackWhiteProcessor: ImageProcessor {
|
||||
|
||||
/// Identifier of the processor.
|
||||
/// - Note: See documentation of `ImageProcessor` protocol for more.
|
||||
public let identifier = "com.onevcat.Kingfisher.BlackWhiteProcessor"
|
||||
|
||||
/// Creates a `BlackWhiteProcessor`
|
||||
public init() {}
|
||||
|
||||
/// Processes the input `ImageProcessItem` with this processor.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - item: Input item which will be processed by `self`.
|
||||
/// - options: Options when processing the item.
|
||||
/// - Returns: The processed image.
|
||||
///
|
||||
/// - Note: See documentation of `ImageProcessor` protocol for more.
|
||||
public func process(item: ImageProcessItem, options: KingfisherParsedOptionsInfo) -> KFCrossPlatformImage? {
|
||||
return ColorControlsProcessor(brightness: 0.0, contrast: 1.0, saturation: 0.0, inputEV: 0.7)
|
||||
.process(item: item, options: options)
|
||||
}
|
||||
}
|
||||
|
||||
/// Processor for cropping an image. Only CG-based images are supported.
|
||||
/// watchOS is not supported.
|
||||
public struct CroppingImageProcessor: ImageProcessor {
|
||||
|
||||
/// Identifier of the processor.
|
||||
/// - Note: See documentation of `ImageProcessor` protocol for more.
|
||||
public let identifier: String
|
||||
|
||||
/// Target size of output image should be.
|
||||
public let size: CGSize
|
||||
|
||||
/// Anchor point from which the output size should be calculate.
|
||||
/// The anchor point is consisted by two values between 0.0 and 1.0.
|
||||
/// It indicates a related point in current image.
|
||||
/// See `CroppingImageProcessor.init(size:anchor:)` for more.
|
||||
public let anchor: CGPoint
|
||||
|
||||
/// Creates a `CroppingImageProcessor`.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - size: Target size of output image should be.
|
||||
/// - anchor: The anchor point from which the size should be calculated.
|
||||
/// Default is `CGPoint(x: 0.5, y: 0.5)`, which means the center of input image.
|
||||
/// - Note:
|
||||
/// The anchor point is consisted by two values between 0.0 and 1.0.
|
||||
/// It indicates a related point in current image, eg: (0.0, 0.0) for top-left
|
||||
/// corner, (0.5, 0.5) for center and (1.0, 1.0) for bottom-right corner.
|
||||
/// The `size` property of `CroppingImageProcessor` will be used along with
|
||||
/// `anchor` to calculate a target rectangle in the size of image.
|
||||
///
|
||||
/// The target size will be automatically calculated with a reasonable behavior.
|
||||
/// For example, when you have an image size of `CGSize(width: 100, height: 100)`,
|
||||
/// and a target size of `CGSize(width: 20, height: 20)`:
|
||||
/// - with a (0.0, 0.0) anchor (top-left), the crop rect will be `{0, 0, 20, 20}`;
|
||||
/// - with a (0.5, 0.5) anchor (center), it will be `{40, 40, 20, 20}`
|
||||
/// - while with a (1.0, 1.0) anchor (bottom-right), it will be `{80, 80, 20, 20}`
|
||||
public init(size: CGSize, anchor: CGPoint = CGPoint(x: 0.5, y: 0.5)) {
|
||||
self.size = size
|
||||
self.anchor = anchor
|
||||
self.identifier = "com.onevcat.Kingfisher.CroppingImageProcessor(\(size)_\(anchor))"
|
||||
}
|
||||
|
||||
/// Processes the input `ImageProcessItem` with this processor.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - item: Input item which will be processed by `self`.
|
||||
/// - options: Options when processing the item.
|
||||
/// - Returns: The processed image.
|
||||
///
|
||||
/// - Note: See documentation of `ImageProcessor` protocol for more.
|
||||
public func process(item: ImageProcessItem, options: KingfisherParsedOptionsInfo) -> KFCrossPlatformImage? {
|
||||
switch item {
|
||||
case .image(let image):
|
||||
return image.kf.scaled(to: options.scaleFactor)
|
||||
.kf.crop(to: size, anchorOn: anchor)
|
||||
case .data: return (DefaultImageProcessor.default |> self).process(item: item, options: options)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Processor for downsampling an image. Compared to `ResizingImageProcessor`, this processor
|
||||
/// does not render the images to resize. Instead, it downsamples the input data directly to an
|
||||
/// image. It is a more efficient than `ResizingImageProcessor`. Prefer to use `DownsamplingImageProcessor` as possible
|
||||
/// as you can than the `ResizingImageProcessor`.
|
||||
///
|
||||
/// Only CG-based images are supported. Animated images (like GIF) is not supported.
|
||||
public struct DownsamplingImageProcessor: ImageProcessor {
|
||||
|
||||
/// Target size of output image should be. It should be smaller than the size of
|
||||
/// input image. If it is larger, the result image will be the same size of input
|
||||
/// data without downsampling.
|
||||
public let size: CGSize
|
||||
|
||||
/// Identifier of the processor.
|
||||
/// - Note: See documentation of `ImageProcessor` protocol for more.
|
||||
public let identifier: String
|
||||
|
||||
/// Creates a `DownsamplingImageProcessor`.
|
||||
///
|
||||
/// - Parameter size: The target size of the downsample operation.
|
||||
public init(size: CGSize) {
|
||||
self.size = size
|
||||
self.identifier = "com.onevcat.Kingfisher.DownsamplingImageProcessor(\(size))"
|
||||
}
|
||||
|
||||
/// Processes the input `ImageProcessItem` with this processor.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - item: Input item which will be processed by `self`.
|
||||
/// - options: Options when processing the item.
|
||||
/// - Returns: The processed image.
|
||||
///
|
||||
/// - Note: See documentation of `ImageProcessor` protocol for more.
|
||||
public func process(item: ImageProcessItem, options: KingfisherParsedOptionsInfo) -> KFCrossPlatformImage? {
|
||||
switch item {
|
||||
case .image(let image):
|
||||
guard let data = image.kf.data(format: .unknown) else {
|
||||
return nil
|
||||
}
|
||||
return KingfisherWrapper.downsampledImage(data: data, to: size, scale: options.scaleFactor)
|
||||
case .data(let data):
|
||||
return KingfisherWrapper.downsampledImage(data: data, to: size, scale: options.scaleFactor)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
infix operator |>: AdditionPrecedence
|
||||
public func |>(left: ImageProcessor, right: ImageProcessor) -> ImageProcessor {
|
||||
return left.append(another: right)
|
||||
}
|
||||
|
||||
extension KFCrossPlatformColor {
|
||||
|
||||
var rgba: (r: CGFloat, g: CGFloat, b: CGFloat, a: CGFloat) {
|
||||
var r: CGFloat = 0
|
||||
var g: CGFloat = 0
|
||||
var b: CGFloat = 0
|
||||
var a: CGFloat = 0
|
||||
|
||||
#if os(macOS)
|
||||
(usingColorSpace(.extendedSRGB) ?? self).getRed(&r, green: &g, blue: &b, alpha: &a)
|
||||
#else
|
||||
getRed(&r, green: &g, blue: &b, alpha: &a)
|
||||
#endif
|
||||
|
||||
return (r, g, b, a)
|
||||
}
|
||||
|
||||
var rgbaDescription: String {
|
||||
let components = self.rgba
|
||||
return String(format: "(%.2f,%.2f,%.2f,%.2f)", components.r, components.g, components.b, components.a)
|
||||
}
|
||||
}
|
||||
348
Pods/Kingfisher/Sources/Image/ImageProgressive.swift
generated
Normal file
348
Pods/Kingfisher/Sources/Image/ImageProgressive.swift
generated
Normal file
@@ -0,0 +1,348 @@
|
||||
//
|
||||
// ImageProgressive.swift
|
||||
// Kingfisher
|
||||
//
|
||||
// Created by lixiang on 2019/5/10.
|
||||
//
|
||||
// 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.
|
||||
|
||||
import Foundation
|
||||
import CoreGraphics
|
||||
|
||||
private let sharedProcessingQueue: CallbackQueue =
|
||||
.dispatch(DispatchQueue(label: "com.onevcat.Kingfisher.ImageDownloader.Process"))
|
||||
|
||||
public struct ImageProgressive {
|
||||
|
||||
/// The updating strategy when an intermediate progressive image is generated and about to be set to the hosting view.
|
||||
///
|
||||
/// - default: Use the progressive image as it is. It is the standard behavior when handling the progressive image.
|
||||
/// - keepCurrent: Discard this progressive image and keep the current displayed one.
|
||||
/// - replace: Replace the image to a new one. If the progressive loading is initialized by a view extension in
|
||||
/// Kingfisher, the replacing image will be used to update the view.
|
||||
public enum UpdatingStrategy {
|
||||
case `default`
|
||||
case keepCurrent
|
||||
case replace(KFCrossPlatformImage?)
|
||||
}
|
||||
|
||||
/// A default `ImageProgressive` could be used across. It blurs the progressive loading with the fastest
|
||||
/// scan enabled and scan interval as 0.
|
||||
@available(*, deprecated, message: "Getting a default `ImageProgressive` is deprecated due to its syntax symatic is not clear. Use `ImageProgressive.init` instead.", renamed: "init()")
|
||||
public static let `default` = ImageProgressive(
|
||||
isBlur: true,
|
||||
isFastestScan: true,
|
||||
scanInterval: 0
|
||||
)
|
||||
|
||||
/// Whether to enable blur effect processing
|
||||
let isBlur: Bool
|
||||
/// Whether to enable the fastest scan
|
||||
let isFastestScan: Bool
|
||||
/// Minimum time interval for each scan
|
||||
let scanInterval: TimeInterval
|
||||
|
||||
/// Called when an intermediate image is prepared and about to be set to the image view. The return value of this
|
||||
/// delegate will be used to update the hosting view, if any. Otherwise, if there is no hosting view (a.k.a the
|
||||
/// image retrieving is not happening from a view extension method), the returned `UpdatingStrategy` is ignored.
|
||||
public let onImageUpdated = Delegate<KFCrossPlatformImage, UpdatingStrategy>()
|
||||
|
||||
/// Creates an `ImageProgressive` value with default sets. It blurs the progressive loading with the fastest
|
||||
/// scan enabled and scan interval as 0.
|
||||
public init() {
|
||||
self.init(isBlur: true, isFastestScan: true, scanInterval: 0)
|
||||
}
|
||||
|
||||
/// Creates an `ImageProgressive` value the given values.
|
||||
/// - Parameters:
|
||||
/// - isBlur: Whether to enable blur effect processing.
|
||||
/// - isFastestScan: Whether to enable the fastest scan.
|
||||
/// - scanInterval: Minimum time interval for each scan.
|
||||
public init(isBlur: Bool,
|
||||
isFastestScan: Bool,
|
||||
scanInterval: TimeInterval
|
||||
)
|
||||
{
|
||||
self.isBlur = isBlur
|
||||
self.isFastestScan = isFastestScan
|
||||
self.scanInterval = scanInterval
|
||||
}
|
||||
}
|
||||
|
||||
final class ImageProgressiveProvider: DataReceivingSideEffect {
|
||||
|
||||
var onShouldApply: () -> Bool = { return true }
|
||||
|
||||
func onDataReceived(_ session: URLSession, task: SessionDataTask, data: Data) {
|
||||
|
||||
DispatchQueue.main.async {
|
||||
guard self.onShouldApply() else { return }
|
||||
self.update(data: task.mutableData, with: task.callbacks)
|
||||
}
|
||||
}
|
||||
|
||||
private let option: ImageProgressive
|
||||
private let refresh: (KFCrossPlatformImage) -> Void
|
||||
|
||||
private let decoder: ImageProgressiveDecoder
|
||||
private let queue = ImageProgressiveSerialQueue()
|
||||
|
||||
init?(_ options: KingfisherParsedOptionsInfo,
|
||||
refresh: @escaping (KFCrossPlatformImage) -> Void) {
|
||||
guard let option = options.progressiveJPEG else { return nil }
|
||||
|
||||
self.option = option
|
||||
self.refresh = refresh
|
||||
self.decoder = ImageProgressiveDecoder(
|
||||
option,
|
||||
processingQueue: options.processingQueue ?? sharedProcessingQueue,
|
||||
creatingOptions: options.imageCreatingOptions
|
||||
)
|
||||
}
|
||||
|
||||
func update(data: Data, with callbacks: [SessionDataTask.TaskCallback]) {
|
||||
guard !data.isEmpty else { return }
|
||||
|
||||
queue.add(minimum: option.scanInterval) { completion in
|
||||
|
||||
func decode(_ data: Data) {
|
||||
self.decoder.decode(data, with: callbacks) { image in
|
||||
defer { completion() }
|
||||
guard self.onShouldApply() else { return }
|
||||
guard let image = image else { return }
|
||||
self.refresh(image)
|
||||
}
|
||||
}
|
||||
|
||||
let semaphore = DispatchSemaphore(value: 0)
|
||||
var onShouldApply: Bool = false
|
||||
|
||||
CallbackQueue.mainAsync.execute {
|
||||
onShouldApply = self.onShouldApply()
|
||||
semaphore.signal()
|
||||
}
|
||||
semaphore.wait()
|
||||
guard onShouldApply else {
|
||||
self.queue.clean()
|
||||
completion()
|
||||
return
|
||||
}
|
||||
|
||||
if self.option.isFastestScan {
|
||||
decode(self.decoder.scanning(data) ?? Data())
|
||||
} else {
|
||||
self.decoder.scanning(data).forEach { decode($0) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final class ImageProgressiveDecoder {
|
||||
|
||||
private let option: ImageProgressive
|
||||
private let processingQueue: CallbackQueue
|
||||
private let creatingOptions: ImageCreatingOptions
|
||||
private(set) var scannedCount = 0
|
||||
private(set) var scannedIndex = -1
|
||||
|
||||
init(_ option: ImageProgressive,
|
||||
processingQueue: CallbackQueue,
|
||||
creatingOptions: ImageCreatingOptions) {
|
||||
self.option = option
|
||||
self.processingQueue = processingQueue
|
||||
self.creatingOptions = creatingOptions
|
||||
}
|
||||
|
||||
func scanning(_ data: Data) -> [Data] {
|
||||
guard data.kf.contains(jpeg: .SOF2) else {
|
||||
return []
|
||||
}
|
||||
guard scannedIndex + 1 < data.count else {
|
||||
return []
|
||||
}
|
||||
|
||||
var datas: [Data] = []
|
||||
var index = scannedIndex + 1
|
||||
var count = scannedCount
|
||||
|
||||
while index < data.count - 1 {
|
||||
scannedIndex = index
|
||||
// 0xFF, 0xDA - Start Of Scan
|
||||
let SOS = ImageFormat.JPEGMarker.SOS.bytes
|
||||
if data[index] == SOS[0], data[index + 1] == SOS[1] {
|
||||
if count > 0 {
|
||||
datas.append(data[0 ..< index])
|
||||
}
|
||||
count += 1
|
||||
}
|
||||
index += 1
|
||||
}
|
||||
|
||||
// Found more scans this the previous time
|
||||
guard count > scannedCount else { return [] }
|
||||
scannedCount = count
|
||||
|
||||
// `> 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 count > 1 else { return [] }
|
||||
return datas
|
||||
}
|
||||
|
||||
func scanning(_ data: Data) -> Data? {
|
||||
guard data.kf.contains(jpeg: .SOF2) else {
|
||||
return nil
|
||||
}
|
||||
guard scannedIndex + 1 < data.count else {
|
||||
return nil
|
||||
}
|
||||
|
||||
var index = scannedIndex + 1
|
||||
var count = scannedCount
|
||||
var lastSOSIndex = 0
|
||||
|
||||
while index < data.count - 1 {
|
||||
scannedIndex = index
|
||||
// 0xFF, 0xDA - Start Of Scan
|
||||
let SOS = ImageFormat.JPEGMarker.SOS.bytes
|
||||
if data[index] == SOS[0], data[index + 1] == SOS[1] {
|
||||
lastSOSIndex = index
|
||||
count += 1
|
||||
}
|
||||
index += 1
|
||||
}
|
||||
|
||||
// Found more scans this the previous time
|
||||
guard count > scannedCount else { return nil }
|
||||
scannedCount = count
|
||||
|
||||
// `> 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 count > 1 && lastSOSIndex > 0 else { return nil }
|
||||
return data[0 ..< lastSOSIndex]
|
||||
}
|
||||
|
||||
func decode(_ data: Data,
|
||||
with callbacks: [SessionDataTask.TaskCallback],
|
||||
completion: @escaping (KFCrossPlatformImage?) -> Void) {
|
||||
guard data.kf.contains(jpeg: .SOF2) else {
|
||||
CallbackQueue.mainCurrentOrAsync.execute { completion(nil) }
|
||||
return
|
||||
}
|
||||
|
||||
func processing(_ data: Data) {
|
||||
let processor = ImageDataProcessor(
|
||||
data: data,
|
||||
callbacks: callbacks,
|
||||
processingQueue: processingQueue
|
||||
)
|
||||
processor.onImageProcessed.delegate(on: self) { (self, result) in
|
||||
guard let image = try? result.0.get() else {
|
||||
CallbackQueue.mainCurrentOrAsync.execute { completion(nil) }
|
||||
return
|
||||
}
|
||||
|
||||
CallbackQueue.mainCurrentOrAsync.execute { completion(image) }
|
||||
}
|
||||
processor.process()
|
||||
}
|
||||
|
||||
// Blur partial images.
|
||||
let count = scannedCount
|
||||
|
||||
if option.isBlur, count < 6 {
|
||||
processingQueue.execute {
|
||||
// Progressively reduce blur as we load more scans.
|
||||
let image = KingfisherWrapper<KFCrossPlatformImage>.image(
|
||||
data: data,
|
||||
options: self.creatingOptions
|
||||
)
|
||||
let radius = max(2, 14 - count * 4)
|
||||
let temp = image?.kf.blurred(withRadius: CGFloat(radius))
|
||||
processing(temp?.kf.data(format: .JPEG) ?? data)
|
||||
}
|
||||
|
||||
} else {
|
||||
processing(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final class ImageProgressiveSerialQueue {
|
||||
typealias ClosureCallback = ((@escaping () -> Void)) -> Void
|
||||
|
||||
private let queue: DispatchQueue
|
||||
private var items: [DispatchWorkItem] = []
|
||||
private var notify: (() -> Void)?
|
||||
private var lastTime: TimeInterval?
|
||||
|
||||
init() {
|
||||
self.queue = DispatchQueue(label: "com.onevcat.Kingfisher.ImageProgressive.SerialQueue")
|
||||
}
|
||||
|
||||
func add(minimum interval: TimeInterval, closure: @escaping ClosureCallback) {
|
||||
let completion = { [weak self] in
|
||||
guard let self = self else { return }
|
||||
|
||||
self.queue.async { [weak self] in
|
||||
guard let self = self else { return }
|
||||
guard !self.items.isEmpty else { return }
|
||||
|
||||
self.items.removeFirst()
|
||||
|
||||
if let next = self.items.first {
|
||||
self.queue.asyncAfter(
|
||||
deadline: .now() + interval,
|
||||
execute: next
|
||||
)
|
||||
|
||||
} else {
|
||||
self.lastTime = Date().timeIntervalSince1970
|
||||
self.notify?()
|
||||
self.notify = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
queue.async { [weak self] in
|
||||
guard let self = self else { return }
|
||||
|
||||
let item = DispatchWorkItem {
|
||||
closure(completion)
|
||||
}
|
||||
if self.items.isEmpty {
|
||||
let difference = Date().timeIntervalSince1970 - (self.lastTime ?? 0)
|
||||
let delay = difference < interval ? interval - difference : 0
|
||||
self.queue.asyncAfter(deadline: .now() + delay, execute: item)
|
||||
}
|
||||
self.items.append(item)
|
||||
}
|
||||
}
|
||||
|
||||
func clean() {
|
||||
queue.async { [weak self] in
|
||||
guard let self = self else { return }
|
||||
self.items.forEach { $0.cancel() }
|
||||
self.items.removeAll()
|
||||
}
|
||||
}
|
||||
}
|
||||
118
Pods/Kingfisher/Sources/Image/ImageTransition.swift
generated
Normal file
118
Pods/Kingfisher/Sources/Image/ImageTransition.swift
generated
Normal file
@@ -0,0 +1,118 @@
|
||||
//
|
||||
// ImageTransition.swift
|
||||
// Kingfisher
|
||||
//
|
||||
// Created by Wei Wang on 15/9/18.
|
||||
//
|
||||
// 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.
|
||||
|
||||
import Foundation
|
||||
#if os(iOS) || os(tvOS)
|
||||
import UIKit
|
||||
|
||||
/// Transition effect which will be used when an image downloaded and set by `UIImageView`
|
||||
/// extension API in Kingfisher. You can assign an enum value with transition duration as
|
||||
/// an item in `KingfisherOptionsInfo` to enable the animation transition.
|
||||
///
|
||||
/// Apple's UIViewAnimationOptions is used under the hood.
|
||||
/// For custom transition, you should specified your own transition options, animations and
|
||||
/// completion handler as well.
|
||||
///
|
||||
/// - none: No animation transition.
|
||||
/// - fade: Fade in the loaded image in a given duration.
|
||||
/// - flipFromLeft: Flip from left transition.
|
||||
/// - flipFromRight: Flip from right transition.
|
||||
/// - flipFromTop: Flip from top transition.
|
||||
/// - flipFromBottom: Flip from bottom transition.
|
||||
/// - custom: Custom transition.
|
||||
public enum ImageTransition {
|
||||
/// No animation transition.
|
||||
case none
|
||||
/// Fade in the loaded image in a given duration.
|
||||
case fade(TimeInterval)
|
||||
/// Flip from left transition.
|
||||
case flipFromLeft(TimeInterval)
|
||||
/// Flip from right transition.
|
||||
case flipFromRight(TimeInterval)
|
||||
/// Flip from top transition.
|
||||
case flipFromTop(TimeInterval)
|
||||
/// Flip from bottom transition.
|
||||
case flipFromBottom(TimeInterval)
|
||||
/// Custom transition defined by a general animation block.
|
||||
/// - duration: The time duration of this custom transition.
|
||||
/// - options: `UIView.AnimationOptions` should be used in the transition.
|
||||
/// - animations: The animation block will be applied when setting image.
|
||||
/// - completion: A block called when the transition animation finishes.
|
||||
case custom(duration: TimeInterval,
|
||||
options: UIView.AnimationOptions,
|
||||
animations: ((UIImageView, UIImage) -> Void)?,
|
||||
completion: ((Bool) -> Void)?)
|
||||
|
||||
var duration: TimeInterval {
|
||||
switch self {
|
||||
case .none: return 0
|
||||
case .fade(let duration): return duration
|
||||
|
||||
case .flipFromLeft(let duration): return duration
|
||||
case .flipFromRight(let duration): return duration
|
||||
case .flipFromTop(let duration): return duration
|
||||
case .flipFromBottom(let duration): return duration
|
||||
|
||||
case .custom(let duration, _, _, _): return duration
|
||||
}
|
||||
}
|
||||
|
||||
var animationOptions: UIView.AnimationOptions {
|
||||
switch self {
|
||||
case .none: return []
|
||||
case .fade: return .transitionCrossDissolve
|
||||
|
||||
case .flipFromLeft: return .transitionFlipFromLeft
|
||||
case .flipFromRight: return .transitionFlipFromRight
|
||||
case .flipFromTop: return .transitionFlipFromTop
|
||||
case .flipFromBottom: return .transitionFlipFromBottom
|
||||
|
||||
case .custom(_, let options, _, _): return options
|
||||
}
|
||||
}
|
||||
|
||||
var animations: ((UIImageView, UIImage) -> Void)? {
|
||||
switch self {
|
||||
case .custom(_, _, let animations, _): return animations
|
||||
default: return { $0.image = $1 }
|
||||
}
|
||||
}
|
||||
|
||||
var completion: ((Bool) -> Void)? {
|
||||
switch self {
|
||||
case .custom(_, _, _, let completion): return completion
|
||||
default: return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
// Just a placeholder for compiling on macOS.
|
||||
public enum ImageTransition {
|
||||
case none
|
||||
/// This is a placeholder on macOS now. It is for SwiftUI (KFImage) to identify the fade option only.
|
||||
case fade(TimeInterval)
|
||||
}
|
||||
#endif
|
||||
82
Pods/Kingfisher/Sources/Image/Placeholder.swift
generated
Normal file
82
Pods/Kingfisher/Sources/Image/Placeholder.swift
generated
Normal file
@@ -0,0 +1,82 @@
|
||||
//
|
||||
// Placeholder.swift
|
||||
// Kingfisher
|
||||
//
|
||||
// Created by Tieme van Veen on 28/08/2017.
|
||||
//
|
||||
// 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(watchOS)
|
||||
|
||||
#if canImport(AppKit) && !targetEnvironment(macCatalyst)
|
||||
import AppKit
|
||||
#endif
|
||||
|
||||
#if canImport(UIKit)
|
||||
import UIKit
|
||||
#endif
|
||||
|
||||
/// Represents a placeholder type which could be set while loading as well as
|
||||
/// loading finished without getting an image.
|
||||
public protocol Placeholder {
|
||||
|
||||
/// How the placeholder should be added to a given image view.
|
||||
func add(to imageView: KFCrossPlatformImageView)
|
||||
|
||||
/// How the placeholder should be removed from a given image view.
|
||||
func remove(from imageView: KFCrossPlatformImageView)
|
||||
}
|
||||
|
||||
/// Default implementation of an image placeholder. The image will be set or
|
||||
/// reset directly for `image` property of the image view.
|
||||
extension KFCrossPlatformImage: Placeholder {
|
||||
/// How the placeholder should be added to a given image view.
|
||||
public func add(to imageView: KFCrossPlatformImageView) { imageView.image = self }
|
||||
|
||||
/// How the placeholder should be removed from a given image view.
|
||||
public func remove(from imageView: KFCrossPlatformImageView) { imageView.image = nil }
|
||||
}
|
||||
|
||||
/// Default implementation of an arbitrary view as placeholder. The view will be
|
||||
/// added as a subview when adding and be removed from its super view when removing.
|
||||
///
|
||||
/// To use your customize View type as placeholder, simply let it conforming to
|
||||
/// `Placeholder` by `extension MyView: Placeholder {}`.
|
||||
extension Placeholder where Self: KFCrossPlatformView {
|
||||
|
||||
/// How the placeholder should be added to a given image view.
|
||||
public func add(to imageView: KFCrossPlatformImageView) {
|
||||
imageView.addSubview(self)
|
||||
translatesAutoresizingMaskIntoConstraints = false
|
||||
|
||||
centerXAnchor.constraint(equalTo: imageView.centerXAnchor).isActive = true
|
||||
centerYAnchor.constraint(equalTo: imageView.centerYAnchor).isActive = true
|
||||
heightAnchor.constraint(equalTo: imageView.heightAnchor).isActive = true
|
||||
widthAnchor.constraint(equalTo: imageView.widthAnchor).isActive = true
|
||||
}
|
||||
|
||||
/// How the placeholder should be removed from a given image view.
|
||||
public func remove(from imageView: KFCrossPlatformImageView) {
|
||||
removeFromSuperview()
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user