PhotoImageEditor.swift 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106
  1. import Cocoa
  2. import CoreImage
  3. enum PhotoImageEditor {
  4. static func apply(
  5. to image: NSImage,
  6. saturation: Float = 1,
  7. whitening: Float = 0,
  8. contrast: Float = 1,
  9. autoEnhance: Bool = false
  10. ) -> NSImage {
  11. guard let ciImage = ciImage(from: image) else { return image }
  12. var output = ciImage
  13. if autoEnhance {
  14. for filter in output.autoAdjustmentFilters(options: [.enhance: true]) {
  15. filter.setValue(output, forKey: kCIInputImageKey)
  16. if let result = filter.outputImage {
  17. output = result
  18. }
  19. }
  20. }
  21. if whitening > 0 {
  22. if let controls = CIFilter(name: "CIColorControls") {
  23. controls.setValue(output, forKey: kCIInputImageKey)
  24. controls.setValue(whitening * 0.22, forKey: kCIInputBrightnessKey)
  25. controls.setValue(1 - whitening * 0.2, forKey: kCIInputSaturationKey)
  26. if let result = controls.outputImage {
  27. output = result
  28. }
  29. }
  30. if let exposure = CIFilter(name: "CIExposureAdjust") {
  31. exposure.setValue(output, forKey: kCIInputImageKey)
  32. exposure.setValue(whitening * 0.4, forKey: kCIInputEVKey)
  33. if let result = exposure.outputImage {
  34. output = result
  35. }
  36. }
  37. }
  38. if saturation != 1 || contrast != 1, let controls = CIFilter(name: "CIColorControls") {
  39. controls.setValue(output, forKey: kCIInputImageKey)
  40. controls.setValue(saturation, forKey: kCIInputSaturationKey)
  41. controls.setValue(contrast, forKey: kCIInputContrastKey)
  42. if let result = controls.outputImage {
  43. output = result
  44. }
  45. }
  46. return render(output, fallback: image)
  47. }
  48. static func flipHorizontal(_ image: NSImage) -> NSImage {
  49. transform(image) { extent in
  50. CGAffineTransform(scaleX: -1, y: 1).translatedBy(x: -extent.width, y: 0)
  51. }
  52. }
  53. static func flipVertical(_ image: NSImage) -> NSImage {
  54. transform(image) { extent in
  55. CGAffineTransform(scaleX: 1, y: -1).translatedBy(x: 0, y: -extent.height)
  56. }
  57. }
  58. static func rotateClockwise(_ image: NSImage) -> NSImage {
  59. transform(image) { extent in
  60. CGAffineTransform(translationX: extent.height, y: 0)
  61. .rotated(by: .pi / 2)
  62. }
  63. }
  64. static func crop(_ image: NSImage, normalizedRect: CGRect) -> NSImage {
  65. guard let ciImage = ciImage(from: image) else { return image }
  66. let extent = ciImage.extent
  67. let cropRect = CGRect(
  68. x: extent.origin.x + normalizedRect.origin.x * extent.width,
  69. y: extent.origin.y + normalizedRect.origin.y * extent.height,
  70. width: normalizedRect.width * extent.width,
  71. height: normalizedRect.height * extent.height
  72. ).integral
  73. guard cropRect.width > 1, cropRect.height > 1 else { return image }
  74. let cropped = ciImage.cropped(to: cropRect)
  75. return render(cropped, fallback: image)
  76. }
  77. private static func transform(_ image: NSImage, makeTransform: (CGRect) -> CGAffineTransform) -> NSImage {
  78. guard let ciImage = ciImage(from: image) else { return image }
  79. let extent = ciImage.extent
  80. let transformed = ciImage.transformed(by: makeTransform(extent))
  81. return render(transformed, fallback: image)
  82. }
  83. private static func ciImage(from image: NSImage) -> CIImage? {
  84. guard let cgImage = image.cgImage(forProposedRect: nil, context: nil, hints: nil) else { return nil }
  85. return CIImage(cgImage: cgImage)
  86. }
  87. private static func render(_ ciImage: CIImage, fallback: NSImage) -> NSImage {
  88. let context = CIContext(options: [.useSoftwareRenderer: false])
  89. let extent = ciImage.extent.integral
  90. guard let cgImage = context.createCGImage(ciImage, from: extent) else { return fallback }
  91. return NSImage(cgImage: cgImage, size: NSSize(width: extent.width, height: extent.height))
  92. }
  93. }