2018-08-16 11:18:27 +02:00
|
|
|
import Foundation
|
|
|
|
import UIKit
|
|
|
|
|
2019-07-17 11:10:07 +02:00
|
|
|
|
2018-08-16 11:18:27 +02:00
|
|
|
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)
|
|
|
|
|
2022-06-10 21:20:49 +02:00
|
|
|
UIGraphicsBeginImageContextWithOptions(targetSize, false, scale)
|
2018-08-16 11:18:27 +02:00
|
|
|
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 {
|
2022-06-10 21:20:49 +02:00
|
|
|
let width = size.width * scale * factor
|
2018-08-16 11:18:27 +02:00
|
|
|
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 {
|
2022-06-10 21:20:49 +02:00
|
|
|
let rect = CGRect(
|
|
|
|
x: (self.size.height * scale - size) / 2,
|
|
|
|
y: (self.size.width * scale - size) / 2,
|
|
|
|
width: size * scale,
|
|
|
|
height: size * scale)
|
2018-08-16 11:18:27 +02:00
|
|
|
|
|
|
|
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()
|
2022-06-10 21:20:49 +02:00
|
|
|
UIImage(cgImage: cgImage, scale: scale, orientation: imageOrientation).draw(in: breadthRect)
|
2018-08-16 11:18:27 +02:00
|
|
|
return UIGraphicsGetImageFromCurrentImageContext()
|
|
|
|
}
|
2022-06-10 21:20:49 +02:00
|
|
|
|
2020-05-16 11:21:55 +02:00
|
|
|
var averageColor: UIColor? {
|
|
|
|
let image = ciImage ?? CIImage(cgImage: cgImage!)
|
|
|
|
return image.averageColor
|
|
|
|
}
|
2018-08-16 11:18:27 +02:00
|
|
|
}
|
2020-05-16 11:21:55 +02:00
|
|
|
|
|
|
|
extension CIImage {
|
2022-06-10 21:20:49 +02:00
|
|
|
|
2020-06-18 22:55:51 +02:00
|
|
|
func averageColor(context: CIContext) -> UIColor? {
|
|
|
|
let extentVector = CIVector(
|
|
|
|
x: extent.origin.x,
|
|
|
|
y: extent.origin.y,
|
|
|
|
z: extent.size.width,
|
|
|
|
w: extent.size.height)
|
2022-06-10 21:20:49 +02:00
|
|
|
|
2020-06-18 22:55:51 +02:00
|
|
|
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
|
|
|
|
}
|
2022-06-10 21:20:49 +02:00
|
|
|
|
2020-06-18 22:55:51 +02:00
|
|
|
var bitmap = [UInt8](repeating: 0, count: 4)
|
2022-06-10 21:20:49 +02:00
|
|
|
|
2020-06-18 22:55:51 +02:00
|
|
|
context.render(outputImage, toBitmap: &bitmap, rowBytes: 4,
|
|
|
|
bounds: CGRect(x: 0, y: 0, width: 1, height: 1),
|
|
|
|
format: .RGBA8, colorSpace: nil)
|
2022-06-10 21:20:49 +02:00
|
|
|
|
2020-06-18 22:55:51 +02:00
|
|
|
return UIColor(
|
|
|
|
red: saturate(bitmap[0]),
|
|
|
|
green: saturate(bitmap[1]),
|
|
|
|
blue: saturate(bitmap[2]),
|
|
|
|
alpha: CGFloat(bitmap[3]) / 255)
|
|
|
|
}
|
2022-06-10 21:20:49 +02:00
|
|
|
|
2020-05-16 11:21:55 +02:00
|
|
|
var averageColor: UIColor? {
|
|
|
|
let extentVector = CIVector(
|
|
|
|
x: extent.origin.x,
|
|
|
|
y: extent.origin.y,
|
|
|
|
z: extent.size.width,
|
|
|
|
w: extent.size.height)
|
2022-06-10 21:20:49 +02:00
|
|
|
|
2020-05-16 11:21:55 +02:00
|
|
|
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
|
|
|
|
}
|
2022-06-10 21:20:49 +02:00
|
|
|
|
2020-05-16 11:21:55 +02:00
|
|
|
var bitmap = [UInt8](repeating: 0, count: 4)
|
2020-06-18 22:55:51 +02:00
|
|
|
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)
|
2022-06-10 21:20:49 +02:00
|
|
|
|
2020-05-16 11:21:55 +02:00
|
|
|
let color = UIColor(
|
|
|
|
red: saturate(bitmap[0]),
|
|
|
|
green: saturate(bitmap[1]),
|
|
|
|
blue: saturate(bitmap[2]),
|
|
|
|
alpha: CGFloat(bitmap[3]) / 255)
|
2022-06-10 21:20:49 +02:00
|
|
|
|
2020-05-16 11:21:55 +02:00
|
|
|
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 { }
|
2022-06-21 19:38:51 +02:00
|
|
|
|
|
|
|
|
|
|
|
extension UIImage {
|
|
|
|
|
|
|
|
convenience init?(at url: URL) {
|
|
|
|
guard let data = try? Data(contentsOf: url) else {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
self.init(data: data, scale: UIScreen.main.scale)
|
|
|
|
}
|
|
|
|
}
|