From d6cde55868136d2d8e73a00d912e61e672df57d9 Mon Sep 17 00:00:00 2001 From: leoz Date: Wed, 21 Feb 2024 10:49:24 -0500 Subject: Remove unused variable --- Sources/SwiftyCrop/Models/CropViewModel.swift | 1 - 1 file changed, 1 deletion(-) (limited to 'Sources') diff --git a/Sources/SwiftyCrop/Models/CropViewModel.swift b/Sources/SwiftyCrop/Models/CropViewModel.swift index d470d29..f597fd4 100644 --- a/Sources/SwiftyCrop/Models/CropViewModel.swift +++ b/Sources/SwiftyCrop/Models/CropViewModel.swift @@ -10,7 +10,6 @@ class CropViewModel: ObservableObject { @Published var lastScale: CGFloat = 1.0 @Published var offset: CGSize = .zero @Published var lastOffset: CGSize = .zero - @Published var circleSize: CGSize = .zero init( maskRadius: CGFloat, -- cgit v1.2.3 From 2947fb73c8aec82a7e1d589842d69439a7c76164 Mon Sep 17 00:00:00 2001 From: leoz Date: Wed, 21 Feb 2024 10:51:20 -0500 Subject: Return value explicitly --- Sources/SwiftyCrop/View/CropView.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'Sources') diff --git a/Sources/SwiftyCrop/View/CropView.swift b/Sources/SwiftyCrop/View/CropView.swift index 389047e..a5abde2 100644 --- a/Sources/SwiftyCrop/View/CropView.swift +++ b/Sources/SwiftyCrop/View/CropView.swift @@ -128,9 +128,9 @@ struct CropView: View { private func cropImage() -> UIImage? { if maskShape == .circle && configuration.cropImageCircular { - viewModel.cropToCircle(image) + return viewModel.cropToCircle(image) } else { - viewModel.cropToSquare(image) + return viewModel.cropToSquare(image) } } -- cgit v1.2.3 From a505b7725bed2d45f9e9566bfe3f719b60d7171c Mon Sep 17 00:00:00 2001 From: leoz Date: Wed, 21 Feb 2024 11:23:55 -0500 Subject: Refactor gestures --- Sources/SwiftyCrop/View/CropView.swift | 75 +++++++++++++++++----------------- 1 file changed, 38 insertions(+), 37 deletions(-) (limited to 'Sources') diff --git a/Sources/SwiftyCrop/View/CropView.swift b/Sources/SwiftyCrop/View/CropView.swift index a5abde2..73caaf1 100644 --- a/Sources/SwiftyCrop/View/CropView.swift +++ b/Sources/SwiftyCrop/View/CropView.swift @@ -30,6 +30,43 @@ struct CropView: View { } var body: some View { + let magnificationGesture = MagnificationGesture() + .onChanged { value in + let sensitivity: CGFloat = 0.2 + let scaledValue = (value.magnitude - 1) * sensitivity + 1 + + let maxScaleValues = viewModel.calculateMagnificationGestureMaxValues() + viewModel.scale = min(max(scaledValue * viewModel.scale, maxScaleValues.0), maxScaleValues.1) + + let maxOffsetPoint = viewModel.calculateDragGestureMax() + let newX = min(max(viewModel.lastOffset.width, -maxOffsetPoint.x), maxOffsetPoint.x) + let newY = min(max(viewModel.lastOffset.height, -maxOffsetPoint.y), maxOffsetPoint.y) + viewModel.offset = CGSize(width: newX, height: newY) + } + .onEnded { _ in + viewModel.lastScale = viewModel.scale + viewModel.lastOffset = viewModel.offset + } + + let dragGesture = DragGesture() + .onChanged { value in + let maxOffsetPoint = viewModel.calculateDragGestureMax() + let newX = min( + max(value.translation.width + viewModel.lastOffset.width, -maxOffsetPoint.x), + maxOffsetPoint.x + ) + let newY = min( + max(value.translation.height + viewModel.lastOffset.height, -maxOffsetPoint.y), + maxOffsetPoint.y + ) + viewModel.offset = CGSize(width: newX, height: newY) + } + .onEnded { _ in + viewModel.lastOffset = viewModel.offset + } + + let combinedGesture = magnificationGesture.simultaneously(with: dragGesture) + VStack { Text("interaction_instructions", tableName: localizableTableName, bundle: .module) .font(.system(size: 16, weight: .regular)) @@ -64,43 +101,7 @@ struct CropView: View { ) } .frame(maxWidth: .infinity, maxHeight: .infinity) - .gesture( - MagnificationGesture() - .onChanged { value in - let sensitivity: CGFloat = 0.2 - let scaledValue = (value.magnitude - 1) * sensitivity + 1 - - let maxScaleValues = viewModel.calculateMagnificationGestureMaxValues() - viewModel.scale = min(max(scaledValue * viewModel.scale, maxScaleValues.0), maxScaleValues.1) - - let maxOffsetPoint = viewModel.calculateDragGestureMax() - let newX = min(max(viewModel.lastOffset.width, -maxOffsetPoint.x), maxOffsetPoint.x) - let newY = min(max(viewModel.lastOffset.height, -maxOffsetPoint.y), maxOffsetPoint.y) - viewModel.offset = CGSize(width: newX, height: newY) - } - .onEnded { _ in - viewModel.lastScale = viewModel.scale - viewModel.lastOffset = viewModel.offset - } - .simultaneously( - with: DragGesture() - .onChanged { value in - let maxOffsetPoint = viewModel.calculateDragGestureMax() - let newX = min( - max(value.translation.width + viewModel.lastOffset.width, -maxOffsetPoint.x), - maxOffsetPoint.x - ) - let newY = min( - max(value.translation.height + viewModel.lastOffset.height, -maxOffsetPoint.y), - maxOffsetPoint.y - ) - viewModel.offset = CGSize(width: newX, height: newY) - } - .onEnded { _ in - viewModel.lastOffset = viewModel.offset - } - ) - ) + .gesture(combinedGesture) HStack { Button { -- cgit v1.2.3 From 64f8dbbcc3a46b614dbf9ed82f463b522957880e Mon Sep 17 00:00:00 2001 From: leoz Date: Wed, 21 Feb 2024 11:25:37 -0500 Subject: Lint fixes --- Sources/SwiftyCrop/View/CropView.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'Sources') diff --git a/Sources/SwiftyCrop/View/CropView.swift b/Sources/SwiftyCrop/View/CropView.swift index 73caaf1..92b1138 100644 --- a/Sources/SwiftyCrop/View/CropView.swift +++ b/Sources/SwiftyCrop/View/CropView.swift @@ -47,7 +47,7 @@ struct CropView: View { viewModel.lastScale = viewModel.scale viewModel.lastOffset = viewModel.offset } - + let dragGesture = DragGesture() .onChanged { value in let maxOffsetPoint = viewModel.calculateDragGestureMax() @@ -64,9 +64,9 @@ struct CropView: View { .onEnded { _ in viewModel.lastOffset = viewModel.offset } - + let combinedGesture = magnificationGesture.simultaneously(with: dragGesture) - + VStack { Text("interaction_instructions", tableName: localizableTableName, bundle: .module) .font(.system(size: 16, weight: .regular)) -- cgit v1.2.3 From c2502f4cf0ffa2f2680021d3a55105527eceb4f0 Mon Sep 17 00:00:00 2001 From: leoz Date: Wed, 21 Feb 2024 11:42:58 -0500 Subject: Use simultaneousGesture --- Sources/SwiftyCrop/View/CropView.swift | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'Sources') diff --git a/Sources/SwiftyCrop/View/CropView.swift b/Sources/SwiftyCrop/View/CropView.swift index 92b1138..03de910 100644 --- a/Sources/SwiftyCrop/View/CropView.swift +++ b/Sources/SwiftyCrop/View/CropView.swift @@ -65,8 +65,6 @@ struct CropView: View { viewModel.lastOffset = viewModel.offset } - let combinedGesture = magnificationGesture.simultaneously(with: dragGesture) - VStack { Text("interaction_instructions", tableName: localizableTableName, bundle: .module) .font(.system(size: 16, weight: .regular)) @@ -101,7 +99,8 @@ struct CropView: View { ) } .frame(maxWidth: .infinity, maxHeight: .infinity) - .gesture(combinedGesture) + .simultaneousGesture(magnificationGesture) + .simultaneousGesture(dragGesture) HStack { Button { -- cgit v1.2.3 From f76d234da71a5c2c8d23ed6a6516b809337f5bfb Mon Sep 17 00:00:00 2001 From: leoz Date: Wed, 21 Feb 2024 11:57:12 -0500 Subject: Add rotation gesture --- Sources/SwiftyCrop/Models/CropViewModel.swift | 2 ++ Sources/SwiftyCrop/View/CropView.swift | 11 +++++++++++ 2 files changed, 13 insertions(+) (limited to 'Sources') diff --git a/Sources/SwiftyCrop/Models/CropViewModel.swift b/Sources/SwiftyCrop/Models/CropViewModel.swift index f597fd4..a9557ac 100644 --- a/Sources/SwiftyCrop/Models/CropViewModel.swift +++ b/Sources/SwiftyCrop/Models/CropViewModel.swift @@ -10,6 +10,8 @@ class CropViewModel: ObservableObject { @Published var lastScale: CGFloat = 1.0 @Published var offset: CGSize = .zero @Published var lastOffset: CGSize = .zero + @Published var angle: Angle = Angle(degrees: 0) + @Published var lastAngle: Angle = Angle(degrees: 0) init( maskRadius: CGFloat, diff --git a/Sources/SwiftyCrop/View/CropView.swift b/Sources/SwiftyCrop/View/CropView.swift index 03de910..c76f7f8 100644 --- a/Sources/SwiftyCrop/View/CropView.swift +++ b/Sources/SwiftyCrop/View/CropView.swift @@ -64,6 +64,14 @@ struct CropView: View { .onEnded { _ in viewModel.lastOffset = viewModel.offset } + + let rotationGesture = RotationGesture() + .onChanged { value in + viewModel.angle = value + } + .onEnded { _ in + viewModel.lastAngle = viewModel.angle + } VStack { Text("interaction_instructions", tableName: localizableTableName, bundle: .module) @@ -76,6 +84,7 @@ struct CropView: View { Image(uiImage: image) .resizable() .scaledToFit() + .rotationEffect(viewModel.angle) .scaleEffect(viewModel.scale) .offset(viewModel.offset) .opacity(0.5) @@ -91,6 +100,7 @@ struct CropView: View { Image(uiImage: image) .resizable() .scaledToFit() + .rotationEffect(viewModel.angle) .scaleEffect(viewModel.scale) .offset(viewModel.offset) .mask( @@ -101,6 +111,7 @@ struct CropView: View { .frame(maxWidth: .infinity, maxHeight: .infinity) .simultaneousGesture(magnificationGesture) .simultaneousGesture(dragGesture) + .simultaneousGesture(rotationGesture) HStack { Button { -- cgit v1.2.3 From 92d61886ad1381a6133167d0a5f770a43c14523d Mon Sep 17 00:00:00 2001 From: leoz Date: Wed, 21 Feb 2024 15:12:18 -0500 Subject: Rotate image --- Sources/SwiftyCrop/Models/CropViewModel.swift | 61 +++++++++++++++++++++++++++ Sources/SwiftyCrop/View/CropView.swift | 13 ++++-- 2 files changed, 70 insertions(+), 4 deletions(-) (limited to 'Sources') diff --git a/Sources/SwiftyCrop/Models/CropViewModel.swift b/Sources/SwiftyCrop/Models/CropViewModel.swift index a9557ac..9d5e16d 100644 --- a/Sources/SwiftyCrop/Models/CropViewModel.swift +++ b/Sources/SwiftyCrop/Models/CropViewModel.swift @@ -122,6 +122,47 @@ class CropViewModel: ObservableObject { return circleCroppedImage } + /** + Rotates the image to the angle that is rotated inside the view. + - Parameters: + - image: The UIImage to rotate + - 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? { + guard let orientedImage = image.correctlyOriented else { + return nil + } + + guard let cgImage = orientedImage.cgImage else { + return nil + } + + 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 + } + + // Create resulting image + let context = CIContext() + guard let result = context.createCGImage( + output, + from: output.extent + ) else { + return nil + } + + return UIImage(cgImage: result) + } + /** Calculates the rectangle to crop. - Parameters: @@ -178,3 +219,23 @@ private extension UIImage { return normalizedImage } } + +private extension CIFilter { + /** + Creates the straighten filter. + - Parameters: + - inputImage: The CIImage to use as an input image + - radians: An angle in radians + - Returns: A generated CIFilter. + */ + static func straightenFilter(image: CIImage, radians: Double) -> CIFilter? { + let angle: Double = radians != 0 ? -radians : 0 + guard let filter = CIFilter(name: "CIStraightenFilter") else { + return nil + } + filter.setDefaults() + filter.setValue(image, forKey: kCIInputImageKey) + filter.setValue(angle, forKey: kCIInputAngleKey) + return filter + } +} diff --git a/Sources/SwiftyCrop/View/CropView.swift b/Sources/SwiftyCrop/View/CropView.swift index c76f7f8..9619efb 100644 --- a/Sources/SwiftyCrop/View/CropView.swift +++ b/Sources/SwiftyCrop/View/CropView.swift @@ -64,7 +64,7 @@ struct CropView: View { .onEnded { _ in viewModel.lastOffset = viewModel.offset } - + let rotationGesture = RotationGesture() .onChanged { value in viewModel.angle = value @@ -138,10 +138,15 @@ struct CropView: View { } private func cropImage() -> UIImage? { - if maskShape == .circle && configuration.cropImageCircular { - return viewModel.cropToCircle(image) + if let rotatedImage: UIImage = viewModel.rotate(image, viewModel.lastAngle) { + if maskShape == .circle && configuration.cropImageCircular { + return viewModel.cropToCircle(rotatedImage) + } else { + return viewModel.cropToSquare(rotatedImage) + } + return rotatedImage } else { - return viewModel.cropToSquare(image) + return nil } } -- cgit v1.2.3 From 9301acdc650cf6b06465ef0fb90c6451ecbf4444 Mon Sep 17 00:00:00 2001 From: leoz Date: Sun, 25 Feb 2024 09:48:56 -0500 Subject: Update configuration --- Sources/SwiftyCrop/Models/SwiftyCropConfiguration.swift | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'Sources') diff --git a/Sources/SwiftyCrop/Models/SwiftyCropConfiguration.swift b/Sources/SwiftyCrop/Models/SwiftyCropConfiguration.swift index 73566be..b41ea53 100644 --- a/Sources/SwiftyCrop/Models/SwiftyCropConfiguration.swift +++ b/Sources/SwiftyCrop/Models/SwiftyCropConfiguration.swift @@ -5,6 +5,7 @@ public struct SwiftyCropConfiguration { let maxMagnificationScale: CGFloat let maskRadius: CGFloat let cropImageCircular: Bool + let rotateImage: Bool /// Creates a new instance of `SwiftyCropConfiguration`. /// @@ -15,13 +16,17 @@ public struct SwiftyCropConfiguration { /// Defaults to `130`. /// - cropImageCircular: Option to enable circular crop. /// Defaults to `false`. + /// - rotateImage: Option to rotate image. + /// Defaults to `true`. public init( maxMagnificationScale: CGFloat = 4.0, maskRadius: CGFloat = 130, - cropImageCircular: Bool = false + cropImageCircular: Bool = false, + rotateImage: Bool = true ) { self.maxMagnificationScale = maxMagnificationScale self.maskRadius = maskRadius self.cropImageCircular = cropImageCircular + self.rotateImage = rotateImage } } -- cgit v1.2.3 From 7eb8df7f203857748d84f325dd82a443326f174b Mon Sep 17 00:00:00 2001 From: leoz Date: Sun, 25 Feb 2024 09:58:40 -0500 Subject: Use configuration for image rotation --- Sources/SwiftyCrop/View/CropView.swift | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) (limited to 'Sources') diff --git a/Sources/SwiftyCrop/View/CropView.swift b/Sources/SwiftyCrop/View/CropView.swift index 9619efb..a61aa47 100644 --- a/Sources/SwiftyCrop/View/CropView.swift +++ b/Sources/SwiftyCrop/View/CropView.swift @@ -111,7 +111,7 @@ struct CropView: View { .frame(maxWidth: .infinity, maxHeight: .infinity) .simultaneousGesture(magnificationGesture) .simultaneousGesture(dragGesture) - .simultaneousGesture(rotationGesture) + .simultaneousGesture(configuration.rotateImage ? rotationGesture : nil) HStack { Button { @@ -138,15 +138,19 @@ struct CropView: View { } private func cropImage() -> UIImage? { - if let rotatedImage: UIImage = viewModel.rotate(image, viewModel.lastAngle) { - if maskShape == .circle && configuration.cropImageCircular { - return viewModel.cropToCircle(rotatedImage) - } else { - return viewModel.cropToSquare(rotatedImage) + var editedImage: UIImage = image + if configuration.rotateImage { + if let rotatedImage: UIImage = viewModel.rotate( + editedImage, + viewModel.lastAngle + ) { + editedImage = rotatedImage } - return rotatedImage + } + if configuration.cropImageCircular && maskShape == .circle { + return viewModel.cropToCircle(editedImage) } else { - return nil + return viewModel.cropToSquare(editedImage) } } -- cgit v1.2.3