diff options
author | Ben <31181527+benedom@users.noreply.github.com> | 2024-01-23 16:41:23 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-01-23 16:41:23 +0100 |
commit | d128aa7d0f09d474921b2cd26f228324ee6ea3d9 (patch) | |
tree | 7db1b5ea333e785c04ee11bd42a61052713b538c | |
parent | 7b9aa6b60e0914a5fba44fa2ba78dfaa566924e4 (diff) | |
parent | c59a4cf49bfd0e663bbe77bd4e4ae9bf073356a7 (diff) |
Merge pull request #1 from leoz/master
Crop image to circle
-rw-r--r-- | .gitignore | 3 | ||||
-rw-r--r-- | Sources/SwiftyCrop/Models/CropViewModel.swift | 87 | ||||
-rw-r--r-- | Sources/SwiftyCrop/Models/SwiftyCropConfiguration.swift | 18 | ||||
-rw-r--r-- | Sources/SwiftyCrop/View/CropView.swift | 10 |
4 files changed, 105 insertions, 13 deletions
@@ -49,4 +49,7 @@ xcuserdata/ /*.gcno **/xcshareddata/WorkspaceSettings.xcsettings +### Visual Studio Code ### +.build + # End of https://www.toptal.com/developers/gitignore/api/macos,xcode
\ No newline at end of file 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 |