import Foundation import UIKit extension UIImage { static func templateImage(named: String) -> UIImage { return UIImage(named: named)!.withRenderingMode(.alwaysTemplate) } /** Resize an image to the target size */ func resize(to targetSize: CGSize) -> UIImage { let rect = CGRect(x: 0, y: 0, width: targetSize.width, height: targetSize.height) UIGraphicsBeginImageContextWithOptions(targetSize, false, scale) self.draw(in: rect) let newImage = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() return newImage! } /** Crop an image to a square, centered around the middle of the frame - parameter factor: The crop factor of the resulting image - returns: The cropped image */ func crop(factor: CGFloat) -> UIImage { let width = size.width * scale * factor return crop(to: width) } /** Crop an image to a square, centered around the middle of the frame - parameter size: The height and width of the resulting image - returns: The cropped image */ func crop(to size: CGFloat) -> UIImage { let rect = CGRect( x: (self.size.height * scale - size) / 2, y: (self.size.width * scale - size) / 2, width: size * scale, height: size * scale) let imageRef = cgImage!.cropping(to: rect) return UIImage(cgImage: imageRef!, scale: scale, orientation: imageOrientation) } /// The (circular) image masked by a circle var circleMasked: UIImage? { let width = size.width let height = size.height let isPortrait = height > width let breadth = min(width, height) let breadthSize = CGSize(width: breadth, height: breadth) let breadthRect = CGRect(origin: .zero, size: breadthSize) UIGraphicsBeginImageContextWithOptions(breadthSize, false, scale) defer { UIGraphicsEndImageContext() } let x = isPortrait ? 0 : floor((width - height) / 2) let y = isPortrait ? floor((height - width) / 2) : 0 let rect = CGRect(origin: CGPoint( x: x, y: y), size: breadthSize) guard let cgImage = cgImage?.cropping(to: rect) else { return nil } UIBezierPath(ovalIn: breadthRect).addClip() UIImage(cgImage: cgImage, scale: scale, orientation: imageOrientation).draw(in: breadthRect) return UIGraphicsGetImageFromCurrentImageContext() } var averageColor: UIColor? { let image = ciImage ?? CIImage(cgImage: cgImage!) return image.averageColor } } extension CIImage { func averageColor(context: CIContext) -> UIColor? { let extentVector = CIVector( x: extent.origin.x, y: extent.origin.y, z: extent.size.width, w: extent.size.height) guard let filter = CIFilter(name: "CIAreaAverage", parameters: [kCIInputImageKey: self, kCIInputExtentKey: extentVector]) else { log("Failed to create filter") return nil } guard let outputImage = filter.outputImage else { log("Failed get filter output") return nil } var bitmap = [UInt8](repeating: 0, count: 4) context.render(outputImage, toBitmap: &bitmap, rowBytes: 4, bounds: CGRect(x: 0, y: 0, width: 1, height: 1), format: .RGBA8, colorSpace: nil) return UIColor( red: saturate(bitmap[0]), green: saturate(bitmap[1]), blue: saturate(bitmap[2]), alpha: CGFloat(bitmap[3]) / 255) } var averageColor: UIColor? { let extentVector = CIVector( x: extent.origin.x, y: extent.origin.y, z: extent.size.width, w: extent.size.height) guard let filter = CIFilter(name: "CIAreaAverage", parameters: [kCIInputImageKey: self, kCIInputExtentKey: extentVector]) else { log("Failed to create filter") return nil } guard let outputImage = filter.outputImage else { log("Failed get filter output") return nil } var bitmap = [UInt8](repeating: 0, count: 4) guard let null = kCFNull else { return nil } let context = CIContext(options: [.workingColorSpace: null]) context.render(outputImage, toBitmap: &bitmap, rowBytes: 4, bounds: CGRect(x: 0, y: 0, width: 1, height: 1), format: .RGBA8, colorSpace: nil) let color = UIColor( red: saturate(bitmap[0]), green: saturate(bitmap[1]), blue: saturate(bitmap[2]), alpha: CGFloat(bitmap[3]) / 255) return color } } /// Map expected range 75-200 to 0-255 private func saturate(_ component: UInt8) -> CGFloat { return max(min(CGFloat(component) * 2 - 150, 255), 0) / 255 } extension CIImage: Logger { }