X-Git-Url: https://git.mdrn.pl/wl-app.git/blobdiff_plain/53b27422d140022594fc241cca91c3183be57bca..48b2fe9f7c2dc3d9aeaaa6dbfb27c7da4f3235ff:/iOS/Pods/Kingfisher/Sources/ImageProcessor.swift diff --git a/iOS/Pods/Kingfisher/Sources/ImageProcessor.swift b/iOS/Pods/Kingfisher/Sources/ImageProcessor.swift new file mode 100644 index 0000000..3e1458e --- /dev/null +++ b/iOS/Pods/Kingfisher/Sources/ImageProcessor.swift @@ -0,0 +1,713 @@ +// +// ImageProcessor.swift +// Kingfisher +// +// Created by Wei Wang on 2016/08/26. +// +// Copyright (c) 2018 Wei Wang +// +// 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 os(macOS) +import AppKit +#endif + +/// The item which could be processed by an `ImageProcessor` +/// +/// - image: Input image +/// - data: Input data +public enum ImageProcessItem { + case image(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 retrived with proper key. + /// + /// - Note: Do not supply an empty string for a customized processor, which is already taken by + /// the `DefaultImageProcessor`. It is recommended to use a reverse domain name notation + /// string of your own for the identifier. + var identifier: String { get } + + /// Process an input `ImageProcessItem` item to an image for this processor. + /// + /// - parameter item: Input item which will be processed by `self` + /// - parameter options: Options when processing the item. + /// + /// - returns: The processed image. + /// + /// - Note: The return value will be `nil` if processing failed while converting data to image. + /// If input item is already an image and there is any errors in processing, the input + /// image itself will be returned. + /// - Note: Most processor only supports CG-based images. + /// watchOS is not supported for processers containing filter, the input image will be returned directly on watchOS. + func process(item: ImageProcessItem, options: KingfisherOptionsInfo) -> Image? +} + +typealias ProcessorImp = ((ImageProcessItem, KingfisherOptionsInfo) -> Image?) + +public extension ImageProcessor { + + /// Append 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) +} + +fileprivate struct GeneralProcessor: ImageProcessor { + let identifier: String + let p: ProcessorImp + func process(item: ImageProcessItem, options: KingfisherOptionsInfo) -> Image? { + return p(item, options) + } +} + +/// The default processor. It convert the input data to a valid image. +/// Images of .PNG, .JPEG and .GIF format are supported. +/// If an image is given, `DefaultImageProcessor` will do nothing on it and just return that 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 = "" + + /// Initialize a `DefaultImageProcessor` + public init() {} + + /// Process an input `ImageProcessItem` item to an image for this processor. + /// + /// - parameter item: Input item which will be processed by `self` + /// - parameter options: Options when processing the item. + /// + /// - returns: The processed image. + /// + /// - Note: See documentation of `ImageProcessor` protocol for more. + public func process(item: ImageProcessItem, options: KingfisherOptionsInfo) -> Image? { + switch item { + case .image(let image): + return image.kf.scaled(to: options.scaleFactor) + case .data(let data): + return Kingfisher.image( + data: data, + scale: options.scaleFactor, + preloadAllAnimationData: options.preloadAllAnimationData, + onlyFirstFrame: options.onlyLoadFirstFrame) + } + } +} + +public struct RectCorner: OptionSet { + public let rawValue: Int + public static let topLeft = RectCorner(rawValue: 1 << 0) + public static let topRight = RectCorner(rawValue: 1 << 1) + public static let bottomLeft = RectCorner(rawValue: 1 << 2) + public static let bottomRight = RectCorner(rawValue: 1 << 3) + public static let all: RectCorner = [.topLeft, .topRight, .bottomLeft, .bottomRight] + + 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: Color? + + /// Initialize an `BlendImageProcessor` + /// + /// - parameter blendMode: Blend Mode will be used to blend the input image. + /// - parameter alpha: Alpha will be used when blend image. + /// From 0.0 to 1.0. 1.0 means solid image, 0.0 means transparent image. + /// Default is 1.0. + /// - parameter backgroundColor: Backgroud color to apply for the output image. Default is `nil`. + public init(blendMode: CGBlendMode, alpha: CGFloat = 1.0, backgroundColor: Color? = 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.hex)") + } + self.identifier = identifier + } + + /// Process an input `ImageProcessItem` item to an image for this processor. + /// + /// - parameter item: Input item which will be processed by `self` + /// - parameter options: Options when processing the item. + /// + /// - returns: The processed image. + /// + /// - Note: See documentation of `ImageProcessor` protocol for more. + public func process(item: ImageProcessItem, options: KingfisherOptionsInfo) -> Image? { + 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: Color? + + /// Initialize an `CompositingImageProcessor` + /// + /// - parameter compositingOperation: Compositing operation will be used to the input image. + /// - parameter 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. + /// - parameter backgroundColor: Backgroud color to apply for the output image. Default is `nil`. + public init(compositingOperation: NSCompositingOperation, + alpha: CGFloat = 1.0, + backgroundColor: Color? = 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.hex)") + } + self.identifier = identifier + } + + /// Process an input `ImageProcessItem` item to an image for this processor. + /// + /// - parameter item: Input item which will be processed by `self` + /// - parameter options: Options when processing the item. + /// + /// - returns: The processed image. + /// + /// - Note: See documentation of `ImageProcessor` protocol for more. + public func process(item: ImageProcessItem, options: KingfisherOptionsInfo) -> Image? { + 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 + +/// 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. +public struct RoundCornerImageProcessor: ImageProcessor { + + /// Identifier of the processor. + /// - Note: See documentation of `ImageProcessor` protocol for more. + public let identifier: String + + /// Corner radius will be applied in processing. + public let cornerRadius: CGFloat + + /// 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 stay transparent. + public let backgroundColor: Color? + + /// Initialize a `RoundCornerImageProcessor` + /// + /// - parameter cornerRadius: Corner radius will be applied in processing. + /// - parameter targetSize: Target size of output image should be. If `nil`, + /// the image will keep its original size after processing. + /// Default is `nil`. + /// - parameter corners: The target corners which will be applied rounding. Default is `.all`. + /// - parameter backgroundColor: Backgroud color to apply for the output image. Default is `nil`. + public init(cornerRadius: CGFloat, targetSize: CGSize? = nil, roundingCorners corners: RectCorner = .all, backgroundColor: Color? = nil) { + self.cornerRadius = cornerRadius + self.targetSize = targetSize + self.roundingCorners = corners + self.backgroundColor = backgroundColor + + self.identifier = { + var identifier = "" + + if let size = targetSize { + identifier = "com.onevcat.Kingfisher.RoundCornerImageProcessor(\(cornerRadius)_\(size)\(corners.cornerIdentifier))" + } else { + identifier = "com.onevcat.Kingfisher.RoundCornerImageProcessor(\(cornerRadius)\(corners.cornerIdentifier))" + } + if let backgroundColor = backgroundColor { + identifier += "_\(backgroundColor)" + } + + return identifier + }() + } + + /// Process an input `ImageProcessItem` item to an image for this processor. + /// + /// - parameter item: Input item which will be processed by `self` + /// - parameter options: Options when processing the item. + /// + /// - returns: The processed image. + /// + /// - Note: See documentation of `ImageProcessor` protocol for more. + public func process(item: ImageProcessItem, options: KingfisherOptionsInfo) -> Image? { + switch item { + case .image(let image): + let size = targetSize ?? image.kf.size + return image.kf.scaled(to: options.scaleFactor) + .kf.image(withRoundRadius: cornerRadius, fit: size, roundingCorners: roundingCorners, backgroundColor: backgroundColor) + case .data(_): + return (DefaultImageProcessor.default >> self).process(item: item, options: options) + } + } +} + + +/// Specify how a size adjusts itself to fit a target size. +/// +/// - none: Not scale the content. +/// - aspectFit: Scale the content to fit the size of the view by maintaining the aspect ratio. +/// - aspectFill: Scale the content to fill the size of the view +public enum ContentMode { + case none + case aspectFit + case aspectFill +} + +/// Processor for resizing images. Only CG-based images are supported in macOS. +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. + public let referenceSize: CGSize + + /// Target content mode of output image should be. + /// Default to ContentMode.none + public let targetContentMode: ContentMode + + /// Initialize a `ResizingImageProcessor`. + /// + /// - Parameters: + /// - referenceSize: The reference size for resizing operation. + /// - 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))" + } + } + + /// Process an input `ImageProcessItem` item to an image for this processor. + /// + /// - parameter item: Input item which will be processed by `self` + /// - parameter options: Options when processing the item. + /// + /// - returns: The processed image. + /// + /// - Note: See documentation of `ImageProcessor` protocol for more. + public func process(item: ImageProcessItem, options: KingfisherOptionsInfo) -> Image? { + 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 + + /// Initialize 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))" + } + + /// Process an input `ImageProcessItem` item to an image for this processor. + /// + /// - parameter item: Input item which will be processed by `self` + /// - parameter options: Options when processing the item. + /// + /// - returns: The processed image. + /// + /// - Note: See documentation of `ImageProcessor` protocol for more. + public func process(item: ImageProcessItem, options: KingfisherOptionsInfo) -> Image? { + 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: Color + + /// Fraction will be used when overlay the color to image. + public let fraction: CGFloat + + /// Initialize 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: Color, fraction: CGFloat = 0.5) { + self.overlay = overlay + self.fraction = fraction + self.identifier = "com.onevcat.Kingfisher.OverlayImageProcessor(\(overlay.hex)_\(fraction))" + } + + /// Process an input `ImageProcessItem` item to an image for this processor. + /// + /// - parameter item: Input item which will be processed by `self` + /// - parameter options: Options when processing the item. + /// + /// - returns: The processed image. + /// + /// - Note: See documentation of `ImageProcessor` protocol for more. + public func process(item: ImageProcessItem, options: KingfisherOptionsInfo) -> Image? { + 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: Color + + /// Initialize a `TintImageProcessor` + /// + /// - parameter tint: Tint color will be used to tint the input image. + public init(tint: Color) { + self.tint = tint + self.identifier = "com.onevcat.Kingfisher.TintImageProcessor(\(tint.hex))" + } + + /// Process an input `ImageProcessItem` item to an image for this processor. + /// + /// - parameter item: Input item which will be processed by `self` + /// - parameter options: Options when processing the item. + /// + /// - returns: The processed image. + /// + /// - Note: See documentation of `ImageProcessor` protocol for more. + public func process(item: ImageProcessItem, options: KingfisherOptionsInfo) -> Image? { + 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 + + /// Initialize a `ColorControlsProcessor` + /// + /// - parameter brightness: Brightness changing to image. + /// - parameter contrast: Contrast changing to image. + /// - parameter saturation: Saturation changing to image. + /// - parameter 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))" + } + + /// Process an input `ImageProcessItem` item to an image for this processor. + /// + /// - parameter item: Input item which will be processed by `self` + /// - parameter options: Options when processing the item. + /// + /// - returns: The processed image. + /// + /// - Note: See documentation of `ImageProcessor` protocol for more. + public func process(item: ImageProcessItem, options: KingfisherOptionsInfo) -> Image? { + 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" + + /// Initialize a `BlackWhiteProcessor` + public init() {} + + /// Process an input `ImageProcessItem` item to an image for this processor. + /// + /// - parameter item: Input item which will be processed by `self` + /// - parameter options: Options when processing the item. + /// + /// - returns: The processed image. + /// + /// - Note: See documentation of `ImageProcessor` protocol for more. + public func process(item: ImageProcessItem, options: KingfisherOptionsInfo) -> Image? { + 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 + + /// Initialize 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 rectange 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))" + } + + /// Process an input `ImageProcessItem` item to an image for this processor. + /// + /// - parameter item: Input item which will be processed by `self` + /// - parameter options: Options when processing the item. + /// + /// - returns: The processed image. + /// + /// - Note: See documentation of `ImageProcessor` protocol for more. + public func process(item: ImageProcessItem, options: KingfisherOptionsInfo) -> Image? { + 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) + } + } +} + +/// Concatenate two `ImageProcessor`s. `ImageProcessor.appen(another:)` is used internally. +/// +/// - parameter left: First processor. +/// - parameter right: Second processor. +/// +/// - returns: The concatenated processor. +public func >>(left: ImageProcessor, right: ImageProcessor) -> ImageProcessor { + return left.append(another: right) +} + +extension Color { + var hex: String { + var r: CGFloat = 0 + var g: CGFloat = 0 + var b: CGFloat = 0 + var a: CGFloat = 0 + + #if os(macOS) + (usingColorSpace(.sRGB) ?? self).getRed(&r, green: &g, blue: &b, alpha: &a) + #else + getRed(&r, green: &g, blue: &b, alpha: &a) + #endif + + let rInt = Int(r * 255) << 24 + let gInt = Int(g * 255) << 16 + let bInt = Int(b * 255) << 8 + let aInt = Int(a * 255) + + let rgba = rInt | gInt | bInt | aInt + + return String(format:"#%08x", rgba) + } +}