aboutsummaryrefslogtreecommitdiff
path: root/Sources
diff options
context:
space:
mode:
Diffstat (limited to 'Sources')
-rw-r--r--Sources/SwiftyCrop/Models/CropViewModel.swift87
-rw-r--r--Sources/SwiftyCrop/Models/SwiftyCropConfiguration.swift18
-rw-r--r--Sources/SwiftyCrop/View/CropView.swift10
3 files changed, 102 insertions, 13 deletions
diff --git a/Sources/SwiftyCrop/Models/CropViewModel.swift b/Sources/SwiftyCrop/Models/CropViewModel.swift
index e27f1ad..761478a 100644
--- a/Sources/SwiftyCrop/Models/CropViewModel.swift
+++ b/Sources/SwiftyCrop/Models/CropViewModel.swift
@@ -40,15 +40,91 @@ class CropViewModel: ObservableObject {
}
/**
- Crops the image to the part that is dragged/zoomed inside the view. Cropped image will **always** be a square, no matter what mask shape is used.
+ Crops the image to the part that is dragged/zoomed inside the view. Cropped image will be a square.
- Parameters:
- image: The UIImage to crop
- Returns: A cropped UIImage if the cropping operation is successful; otherwise nil.
*/
- func crop(_ image: UIImage) -> UIImage? {
+ func cropToSquare(_ image: UIImage) -> UIImage? {
guard let orientedImage = image.correctlyOriented else {
return nil
}
+
+ let cropRect = calculateCropRect(orientedImage)
+
+ guard let cgImage = orientedImage.cgImage,
+ let result = cgImage.cropping(to: cropRect) else {
+ return nil
+ }
+
+ return UIImage(cgImage: result)
+ }
+
+ /**
+ Crops the image to the part that is dragged/zoomed inside the view. Cropped image will be a circle.
+ - Parameters:
+ - image: The UIImage to crop
+ - Returns: A cropped UIImage if the cropping operation is successful; otherwise nil.
+ */
+ func cropToCircle(_ image: UIImage) -> UIImage? {
+ guard let orientedImage = image.correctlyOriented else {
+ return nil
+ }
+
+ let cropRect = calculateCropRect(orientedImage)
+
+ // 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
+ let imageRendererFormat = orientedImage.imageRendererFormat
+ imageRendererFormat.opaque = false
+
+ // UIGraphicsImageRenderer().image provides a block
+ // interface to draw into in a new UIImage
+ let circleCroppedImage = UIGraphicsImageRenderer(
+ // The cropRect.size is the size of
+ // the resulting circleCroppedImage
+ size: cropRect.size,
+ format: imageRendererFormat).image { context in
+
+ // The drawRect is the cropRect starting at (0,0)
+ let drawRect = CGRect(
+ origin: .zero,
+ size: cropRect.size
+ )
+
+ // addClip on a UIBezierPath will clip all contents
+ // outside of the UIBezierPath drawn after addClip
+ // is called, in this case, drawRect is a circle so
+ // the UIBezierPath clips drawing to the circle
+ UIBezierPath(ovalIn: drawRect).addClip()
+
+ // The drawImageRect is offsets the image’s bounds
+ // such that the circular clip is at the center of
+ // the image
+ let drawImageRect = CGRect(
+ origin: CGPoint(
+ x: -cropRect.origin.x,
+ y: -cropRect.origin.y
+ ),
+ size: orientedImage.size
+ )
+
+ // Draws the orientedImage inside of the
+ // circular clip
+ orientedImage.draw(in: drawImageRect)
+ }
+
+ return circleCroppedImage
+ }
+
+ /**
+ Calculates the rectangle to crop.
+ - Parameters:
+ - image: The UIImage to calculate the rectangle to crop for
+ - Returns: A CGRect representing the rectangle to crop.
+ */
+ private func calculateCropRect(_ orientedImage: UIImage) -> 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((orientedImage.size.width / imageSizeInView.width), (orientedImage.size.height / imageSizeInView.height))
let centerInOriginalImage = CGPoint(x: orientedImage.size.width / 2, y: orientedImage.size.height / 2)
@@ -73,12 +149,7 @@ class CropViewModel: ObservableObject {
height: cropRectDimension
)
- guard let cgImage = orientedImage.cgImage,
- let result = cgImage.cropping(to: cropRect) else {
- return nil
- }
-
- return UIImage(cgImage: result)
+ return cropRect
}
}
diff --git a/Sources/SwiftyCrop/Models/SwiftyCropConfiguration.swift b/Sources/SwiftyCrop/Models/SwiftyCropConfiguration.swift
index cc3d47a..73566be 100644
--- a/Sources/SwiftyCrop/Models/SwiftyCropConfiguration.swift
+++ b/Sources/SwiftyCrop/Models/SwiftyCropConfiguration.swift
@@ -4,14 +4,24 @@ import CoreGraphics
public struct SwiftyCropConfiguration {
let maxMagnificationScale: CGFloat
let maskRadius: CGFloat
-
+ let cropImageCircular: Bool
+
/// Creates a new instance of `SwiftyCropConfiguration`.
///
/// - Parameters:
- /// - maxMagnificationScale: The maximum scale factor that the image can be magnified while cropping. Defaults to `4.0`.
- /// - maskRadius: The radius of the mask used for cropping. Defaults to `130`.
- public init(maxMagnificationScale: CGFloat = 4.0, maskRadius: CGFloat = 130) {
+ /// - maxMagnificationScale: The maximum scale factor that the image can be magnified while cropping.
+ /// Defaults to `4.0`.
+ /// - maskRadius: The radius of the mask used for cropping.
+ /// Defaults to `130`.
+ /// - cropImageCircular: Option to enable circular crop.
+ /// Defaults to `false`.
+ public init(
+ maxMagnificationScale: CGFloat = 4.0,
+ maskRadius: CGFloat = 130,
+ cropImageCircular: Bool = false
+ ) {
self.maxMagnificationScale = maxMagnificationScale
self.maskRadius = maskRadius
+ self.cropImageCircular = cropImageCircular
}
}
diff --git a/Sources/SwiftyCrop/View/CropView.swift b/Sources/SwiftyCrop/View/CropView.swift
index 3cb453a..d593c5d 100644
--- a/Sources/SwiftyCrop/View/CropView.swift
+++ b/Sources/SwiftyCrop/View/CropView.swift
@@ -107,7 +107,7 @@ struct CropView: View {
Spacer()
Button {
- onComplete(viewModel.crop(image))
+ onComplete(cropImage())
dismiss()
} label: {
Text("save_button", tableName: localizableTableName, bundle: .module)
@@ -120,6 +120,14 @@ struct CropView: View {
.background(.black)
}
+ private func cropImage() -> UIImage? {
+ if maskShape == .circle && configuration.cropImageCircular {
+ viewModel.cropToCircle(image)
+ } else {
+ viewModel.cropToSquare(image)
+ }
+ }
+
private struct MaskShapeView: View {
let maskShape: MaskShape