aboutsummaryrefslogtreecommitdiff
path: root/Sources
diff options
context:
space:
mode:
authorBen <31181527+benedom@users.noreply.github.com>2024-02-27 09:08:15 +0100
committerGitHub <noreply@github.com>2024-02-27 09:08:15 +0100
commitb3dbe07734759677e3ce2d6d090249738fdbc882 (patch)
tree44101318b0622cce4c8791b282962118d437e073 /Sources
parent29a39f48c6d81eb66df008699259cc7fead3b0a1 (diff)
parentb1f3174b0a363e30ddda0d15f819dc9b0bef5fc9 (diff)
Merge pull request #4 from leoz/master
Rotate image
Diffstat (limited to 'Sources')
-rw-r--r--Sources/SwiftyCrop/Models/CropViewModel.swift64
-rw-r--r--Sources/SwiftyCrop/Models/SwiftyCropConfiguration.swift7
-rw-r--r--Sources/SwiftyCrop/View/CropView.swift100
3 files changed, 129 insertions, 42 deletions
diff --git a/Sources/SwiftyCrop/Models/CropViewModel.swift b/Sources/SwiftyCrop/Models/CropViewModel.swift
index d470d29..9d5e16d 100644
--- a/Sources/SwiftyCrop/Models/CropViewModel.swift
+++ b/Sources/SwiftyCrop/Models/CropViewModel.swift
@@ -10,7 +10,8 @@ class CropViewModel: ObservableObject {
@Published var lastScale: CGFloat = 1.0
@Published var offset: CGSize = .zero
@Published var lastOffset: CGSize = .zero
- @Published var circleSize: CGSize = .zero
+ @Published var angle: Angle = Angle(degrees: 0)
+ @Published var lastAngle: Angle = Angle(degrees: 0)
init(
maskRadius: CGFloat,
@@ -122,6 +123,47 @@ class CropViewModel: ObservableObject {
}
/**
+ 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:
- image: The UIImage to calculate the rectangle to crop for
@@ -177,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/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
}
}
diff --git a/Sources/SwiftyCrop/View/CropView.swift b/Sources/SwiftyCrop/View/CropView.swift
index 389047e..a61aa47 100644
--- a/Sources/SwiftyCrop/View/CropView.swift
+++ b/Sources/SwiftyCrop/View/CropView.swift
@@ -30,6 +30,49 @@ 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 rotationGesture = RotationGesture()
+ .onChanged { value in
+ viewModel.angle = value
+ }
+ .onEnded { _ in
+ viewModel.lastAngle = viewModel.angle
+ }
+
VStack {
Text("interaction_instructions", tableName: localizableTableName, bundle: .module)
.font(.system(size: 16, weight: .regular))
@@ -41,6 +84,7 @@ struct CropView: View {
Image(uiImage: image)
.resizable()
.scaledToFit()
+ .rotationEffect(viewModel.angle)
.scaleEffect(viewModel.scale)
.offset(viewModel.offset)
.opacity(0.5)
@@ -56,6 +100,7 @@ struct CropView: View {
Image(uiImage: image)
.resizable()
.scaledToFit()
+ .rotationEffect(viewModel.angle)
.scaleEffect(viewModel.scale)
.offset(viewModel.offset)
.mask(
@@ -64,43 +109,9 @@ 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
- }
- )
- )
+ .simultaneousGesture(magnificationGesture)
+ .simultaneousGesture(dragGesture)
+ .simultaneousGesture(configuration.rotateImage ? rotationGesture : nil)
HStack {
Button {
@@ -127,10 +138,19 @@ struct CropView: View {
}
private func cropImage() -> UIImage? {
- if maskShape == .circle && configuration.cropImageCircular {
- viewModel.cropToCircle(image)
+ var editedImage: UIImage = image
+ if configuration.rotateImage {
+ if let rotatedImage: UIImage = viewModel.rotate(
+ editedImage,
+ viewModel.lastAngle
+ ) {
+ editedImage = rotatedImage
+ }
+ }
+ if configuration.cropImageCircular && maskShape == .circle {
+ return viewModel.cropToCircle(editedImage)
} else {
- viewModel.cropToSquare(image)
+ return viewModel.cropToSquare(editedImage)
}
}