| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106 |
- import Cocoa
- import CoreImage
- enum PhotoImageEditor {
- static func apply(
- to image: NSImage,
- saturation: Float = 1,
- whitening: Float = 0,
- contrast: Float = 1,
- autoEnhance: Bool = false
- ) -> NSImage {
- guard let ciImage = ciImage(from: image) else { return image }
- var output = ciImage
- if autoEnhance {
- for filter in output.autoAdjustmentFilters(options: [.enhance: true]) {
- filter.setValue(output, forKey: kCIInputImageKey)
- if let result = filter.outputImage {
- output = result
- }
- }
- }
- if whitening > 0 {
- if let controls = CIFilter(name: "CIColorControls") {
- controls.setValue(output, forKey: kCIInputImageKey)
- controls.setValue(whitening * 0.22, forKey: kCIInputBrightnessKey)
- controls.setValue(1 - whitening * 0.2, forKey: kCIInputSaturationKey)
- if let result = controls.outputImage {
- output = result
- }
- }
- if let exposure = CIFilter(name: "CIExposureAdjust") {
- exposure.setValue(output, forKey: kCIInputImageKey)
- exposure.setValue(whitening * 0.4, forKey: kCIInputEVKey)
- if let result = exposure.outputImage {
- output = result
- }
- }
- }
- if saturation != 1 || contrast != 1, let controls = CIFilter(name: "CIColorControls") {
- controls.setValue(output, forKey: kCIInputImageKey)
- controls.setValue(saturation, forKey: kCIInputSaturationKey)
- controls.setValue(contrast, forKey: kCIInputContrastKey)
- if let result = controls.outputImage {
- output = result
- }
- }
- return render(output, fallback: image)
- }
- static func flipHorizontal(_ image: NSImage) -> NSImage {
- transform(image) { extent in
- CGAffineTransform(scaleX: -1, y: 1).translatedBy(x: -extent.width, y: 0)
- }
- }
- static func flipVertical(_ image: NSImage) -> NSImage {
- transform(image) { extent in
- CGAffineTransform(scaleX: 1, y: -1).translatedBy(x: 0, y: -extent.height)
- }
- }
- static func rotateClockwise(_ image: NSImage) -> NSImage {
- transform(image) { extent in
- CGAffineTransform(translationX: extent.height, y: 0)
- .rotated(by: .pi / 2)
- }
- }
- static func crop(_ image: NSImage, normalizedRect: CGRect) -> NSImage {
- guard let ciImage = ciImage(from: image) else { return image }
- let extent = ciImage.extent
- let cropRect = CGRect(
- x: extent.origin.x + normalizedRect.origin.x * extent.width,
- y: extent.origin.y + normalizedRect.origin.y * extent.height,
- width: normalizedRect.width * extent.width,
- height: normalizedRect.height * extent.height
- ).integral
- guard cropRect.width > 1, cropRect.height > 1 else { return image }
- let cropped = ciImage.cropped(to: cropRect)
- return render(cropped, fallback: image)
- }
- private static func transform(_ image: NSImage, makeTransform: (CGRect) -> CGAffineTransform) -> NSImage {
- guard let ciImage = ciImage(from: image) else { return image }
- let extent = ciImage.extent
- let transformed = ciImage.transformed(by: makeTransform(extent))
- return render(transformed, fallback: image)
- }
- private static func ciImage(from image: NSImage) -> CIImage? {
- guard let cgImage = image.cgImage(forProposedRect: nil, context: nil, hints: nil) else { return nil }
- return CIImage(cgImage: cgImage)
- }
- private static func render(_ ciImage: CIImage, fallback: NSImage) -> NSImage {
- let context = CIContext(options: [.useSoftwareRenderer: false])
- let extent = ciImage.extent.integral
- guard let cgImage = context.createCGImage(ciImage, from: extent) else { return fallback }
- return NSImage(cgImage: cgImage, size: NSSize(width: extent.width, height: extent.height))
- }
- }
|