aboutsummaryrefslogtreecommitdiff
path: root/Sources
diff options
context:
space:
mode:
authorNavan Chauhan <navanchauhan@gmail.com>2024-08-12 23:59:40 -0600
committerNavan Chauhan <navanchauhan@gmail.com>2024-08-13 00:00:24 -0600
commitfb3e562581a8367e0c83bf5b4c1548a5216281cf (patch)
treefcc7ada95f409807863bf29b0ee2e435712d6fbb /Sources
parent24a67bf73595994ccf4b60cdad0021b37ca45c45 (diff)
add support for macOSHEADmaster
Diffstat (limited to 'Sources')
-rw-r--r--Sources/SwiftyCrop/Models/CropViewModel.swift90
-rw-r--r--Sources/SwiftyCrop/SwiftyCrop.swift21
-rw-r--r--Sources/SwiftyCrop/View/CropView.swift38
3 files changed, 111 insertions, 38 deletions
diff --git a/Sources/SwiftyCrop/Models/CropViewModel.swift b/Sources/SwiftyCrop/Models/CropViewModel.swift
index 869d4f8..1e9914b 100644
--- a/Sources/SwiftyCrop/Models/CropViewModel.swift
+++ b/Sources/SwiftyCrop/Models/CropViewModel.swift
@@ -1,5 +1,15 @@
import SwiftUI
+#if canImport(UIKit)
import UIKit
+#elseif canImport(AppKit)
+import AppKit
+#endif
+
+#if canImport(UIKit)
+typealias PlatformImage = UIImage
+#elseif canImport(AppKit)
+typealias PlatformImage = NSImage
+#endif
class CropViewModel: ObservableObject {
private let maxMagnificationScale: CGFloat
@@ -53,19 +63,28 @@ class CropViewModel: ObservableObject {
- image: The UIImage to crop
- Returns: A cropped UIImage if the cropping operation is successful; otherwise nil.
*/
- func cropToSquare(_ image: UIImage) -> UIImage? {
+ func cropToSquare(_ image: PlatformImage) -> PlatformImage? {
guard let orientedImage = image.correctlyOriented else {
return nil
}
let cropRect = calculateCropRect(orientedImage)
+ #if canImport(UIKit)
guard let cgImage = orientedImage.cgImage,
let result = cgImage.cropping(to: cropRect) else {
return nil
}
-
return UIImage(cgImage: result)
+ #elseif canImport(AppKit)
+ guard let cgImage = orientedImage.cgImage(forProposedRect: nil, context: nil, hints: nil) else {
+ return nil
+ }
+ guard let croppedCGImage = cgImage.cropping(to: cropRect) else {
+ return nil
+ }
+ return NSImage(cgImage: croppedCGImage, size: cropRect.size)
+ #endif
}
/**
@@ -74,13 +93,14 @@ class CropViewModel: ObservableObject {
- image: The UIImage to crop
- Returns: A cropped UIImage if the cropping operation is successful; otherwise nil.
*/
- func cropToCircle(_ image: UIImage) -> UIImage? {
+ func cropToCircle(_ image: PlatformImage) -> PlatformImage? {
guard let orientedImage = image.correctlyOriented else {
return nil
}
let cropRect = calculateCropRect(orientedImage)
-
+
+ #if canImport(UIKit)
// A circular crop results in some transparency in the
// cropped image, so set opaque to false to ensure the
// cropped image does not include a background fill
@@ -124,6 +144,21 @@ class CropViewModel: ObservableObject {
}
return circleCroppedImage
+ #elseif canImport(AppKit)
+ let circleCroppedImage = NSImage(size: cropRect.size)
+ circleCroppedImage.lockFocus()
+ let drawRect = NSRect(origin: .zero, size: cropRect.size)
+ NSBezierPath(ovalIn: drawRect).addClip()
+ let drawImageRect = NSRect(
+ origin: NSPoint(x: -cropRect.origin.x, y: -cropRect.origin.y),
+ size: orientedImage.size
+ )
+ orientedImage.draw(in: drawImageRect)
+ circleCroppedImage.unlockFocus()
+ return circleCroppedImage
+ #endif
+
+
}
/**
@@ -133,39 +168,42 @@ class CropViewModel: ObservableObject {
- angle: The Angle to rotate to
- Returns: A rotated UIImage if the rotating operation is successful; otherwise nil.
*/
- func rotate(_ image: UIImage, _ angle: Angle) -> UIImage? {
+ func rotate(_ image: PlatformImage, _ angle: Angle) -> PlatformImage? {
guard let orientedImage = image.correctlyOriented else {
return nil
}
+ #if canImport(UIKit)
guard let cgImage = orientedImage.cgImage else {
return nil
}
+ #elseif canImport(AppKit)
+ guard let cgImage = orientedImage.cgImage(forProposedRect: nil, context: nil, hints: nil) else {
+ return nil
+ }
+ #endif
let ciImage = CIImage(cgImage: cgImage)
// Prepare filter
- let filter = CIFilter.straightenFilter(
- image: ciImage,
- radians: angle.radians
- )
-
- // Get output image
- guard let output = filter?.outputImage else {
- return nil
- }
+ guard let filter = CIFilter.straightenFilter(image: ciImage, radians: angle.radians),
+ // Get output image
+ let output = filter.outputImage else {
+ return nil
+ }
// Create resulting image
let context = CIContext()
- guard let result = context.createCGImage(
- output,
- from: output.extent
- ) else {
+ guard let result = context.createCGImage(output, from: output.extent) else {
return nil
}
+ #if canImport(UIKit)
return UIImage(cgImage: result)
- }
+ #elseif canImport(AppKit)
+ return NSImage(cgImage: result, size: NSSize(width: result.width, height: result.height))
+ #endif
+ }
/**
Calculates the rectangle to crop.
@@ -173,7 +211,7 @@ class CropViewModel: ObservableObject {
- image: The UIImage to calculate the rectangle to crop for
- Returns: A CGRect representing the rectangle to crop.
*/
- private func calculateCropRect(_ orientedImage: UIImage) -> CGRect {
+ private func calculateCropRect(_ orientedImage: PlatformImage) -> CGRect {
// The relation factor of the originals image width/height
// and the width/height of the image displayed in the view (initial)
let factor = min(
@@ -206,21 +244,25 @@ class CropViewModel: ObservableObject {
}
}
-private extension UIImage {
+extension PlatformImage {
/**
- A UIImage instance with corrected orientation.
+ For iOS, A UIImage instance with corrected orientation.
If the instance's orientation is already `.up`, it simply returns the original.
- Returns: An optional UIImage that represents the correctly oriented image.
*/
- var correctlyOriented: UIImage? {
+ var correctlyOriented: PlatformImage? {
+ #if canImport(UIKit)
if imageOrientation == .up { return self }
-
+
UIGraphicsBeginImageContextWithOptions(size, false, scale)
draw(in: CGRect(origin: .zero, size: size))
let normalizedImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return normalizedImage
+ #elseif canImport(AppKit)
+ return self
+ #endif
}
}
diff --git a/Sources/SwiftyCrop/SwiftyCrop.swift b/Sources/SwiftyCrop/SwiftyCrop.swift
index 22268a9..bb6164c 100644
--- a/Sources/SwiftyCrop/SwiftyCrop.swift
+++ b/Sources/SwiftyCrop/SwiftyCrop.swift
@@ -11,11 +11,17 @@ import SwiftUI
/// - onComplete: A closure that's called when the cropping is complete. This closure returns the cropped `UIImage?`.
/// If an error occurs the return value is nil.
public struct SwiftyCropView: View {
- private let imageToCrop: UIImage
private let maskShape: MaskShape
private let configuration: SwiftyCropConfiguration
+ #if canImport(UIKit)
+ private let imageToCrop: UIImage
private let onComplete: (UIImage?) -> Void
+ #elseif canImport(AppKit)
+ private let imageToCrop: NSImage
+ private let onComplete: (NSImage?) -> Void
+ #endif
+ #if canImport(UIKit)
public init(
imageToCrop: UIImage,
maskShape: MaskShape,
@@ -27,6 +33,19 @@ public struct SwiftyCropView: View {
self.configuration = configuration
self.onComplete = onComplete
}
+ #elseif canImport(AppKit)
+ public init(
+ imageToCrop: NSImage,
+ maskShape: MaskShape,
+ configuration: SwiftyCropConfiguration = SwiftyCropConfiguration(),
+ onComplete: @escaping (NSImage?) -> Void
+ ) {
+ self.imageToCrop = imageToCrop
+ self.maskShape = maskShape
+ self.configuration = configuration
+ self.onComplete = onComplete
+ }
+ #endif
public var body: some View {
CropView(
diff --git a/Sources/SwiftyCrop/View/CropView.swift b/Sources/SwiftyCrop/View/CropView.swift
index b8ca961..a587281 100644
--- a/Sources/SwiftyCrop/View/CropView.swift
+++ b/Sources/SwiftyCrop/View/CropView.swift
@@ -4,17 +4,17 @@ struct CropView: View {
@Environment(\.dismiss) private var dismiss
@StateObject private var viewModel: CropViewModel
- private let image: UIImage
+ private let image: PlatformImage
private let maskShape: MaskShape
private let configuration: SwiftyCropConfiguration
- private let onComplete: (UIImage?) -> Void
+ private let onComplete: (PlatformImage?) -> Void
private let localizableTableName: String
init(
- image: UIImage,
+ image: PlatformImage,
maskShape: MaskShape,
configuration: SwiftyCropConfiguration,
- onComplete: @escaping (UIImage?) -> Void
+ onComplete: @escaping (PlatformImage?) -> Void
) {
self.image = image
self.maskShape = maskShape
@@ -81,9 +81,7 @@ struct CropView: View {
.zIndex(1)
ZStack {
- Image(uiImage: image)
- .resizable()
- .scaledToFit()
+ PlatformImageView(image: image)
.rotationEffect(viewModel.angle)
.scaleEffect(viewModel.scale)
.offset(viewModel.offset)
@@ -97,9 +95,7 @@ struct CropView: View {
}
)
- Image(uiImage: image)
- .resizable()
- .scaledToFit()
+ PlatformImageView(image: image)
.rotationEffect(viewModel.angle)
.scaleEffect(viewModel.scale)
.offset(viewModel.offset)
@@ -137,10 +133,10 @@ struct CropView: View {
.background(.black)
}
- private func cropImage() -> UIImage? {
- var editedImage: UIImage = image
+ private func cropImage() -> PlatformImage? {
+ var editedImage: PlatformImage = image
if configuration.rotateImage {
- if let rotatedImage: UIImage = viewModel.rotate(
+ if let rotatedImage: PlatformImage = viewModel.rotate(
editedImage,
viewModel.lastAngle
) {
@@ -170,3 +166,19 @@ struct CropView: View {
}
}
}
+
+struct PlatformImageView: View {
+ let image: PlatformImage
+
+ var body: some View {
+ #if canImport(UIKit)
+ Image(uiImage: image)
+ .resizable()
+ .scaledToFit()
+ #elseif canImport(AppKit)
+ Image(nsImage: image)
+ .resizable()
+ .scaledToFit()
+ #endif
+ }
+}