Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
feat: output image processing errors (#305)
* feat: throw ImageProcessError when failing to crop image

fix logic of cropping image

* chore: update script to make SwiftLint work

remove resetAngle from RotationDialProtocol

* chore: fix SwiftLint warnings
  • Loading branch information
guoyingtao committed May 21, 2023
1 parent 337a95b commit cdb7117
Show file tree
Hide file tree
Showing 15 changed files with 79 additions and 42 deletions.
1 change: 0 additions & 1 deletion Example/CustomWaitingIndicator.swift
Expand Up @@ -50,4 +50,3 @@ class CustomWaitingIndicator: UIView, Mantis.ActivityIndicatorProtocol {
circleLayer.removeAnimation(forKey: "rotationAnimation")
}
}

2 changes: 1 addition & 1 deletion Mantis.xcodeproj/project.pbxproj
Expand Up @@ -634,7 +634,7 @@
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "alias swiftlint=\"/opt/homebrew/bin/swiftlint\"\nif swiftlint >/dev/null; then\n swiftlint\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n";
shellScript = "export PATH=\"$PATH:/opt/homebrew/bin\"\nif which swiftlint >/dev/null; then\n swiftlint\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n";
};
/* End PBXShellScriptBuildPhase section */

Expand Down
Expand Up @@ -52,7 +52,8 @@ extension CropAuxiliaryIndicatorView {
break
}

helperView.accessibilityHint = LocalizedHelper.getString("Mantis.Double tap and hold to adjust crop area", value: "Double tap and hold to adjust crop area")
helperView.accessibilityHint = LocalizedHelper.getString("Mantis.Double tap and hold to adjust crop area",
value: "Double tap and hold to adjust crop area")
}
}

Expand All @@ -68,21 +69,45 @@ extension CropAuxiliaryIndicatorView {

switch handleType {
case .topLeft:
helperView.frame = CGRect(x: -cropBoxHotAreaUnit/2, y: -cropBoxHotAreaUnit/2, width: cropBoxHotAreaUnit, height: cropBoxHotAreaUnit)
helperView.frame = CGRect(x: -cropBoxHotAreaUnit/2,
y: -cropBoxHotAreaUnit/2,
width: cropBoxHotAreaUnit,
height: cropBoxHotAreaUnit)
case .top:
helperView.frame = CGRect(x: cropBoxHotAreaUnit/2, y: -cropBoxHotAreaUnit/2, width: bounds.width - cropBoxHotAreaUnit, height: cropBoxHotAreaUnit)
helperView.frame = CGRect(x: cropBoxHotAreaUnit/2,
y: -cropBoxHotAreaUnit/2,
width: bounds.width - cropBoxHotAreaUnit,
height: cropBoxHotAreaUnit)
case .topRight:
helperView.frame = CGRect(x: bounds.width - cropBoxHotAreaUnit/2, y: -cropBoxHotAreaUnit/2, width: cropBoxHotAreaUnit, height: cropBoxHotAreaUnit)
helperView.frame = CGRect(x: bounds.width - cropBoxHotAreaUnit/2,
y: -cropBoxHotAreaUnit/2,
width: cropBoxHotAreaUnit,
height: cropBoxHotAreaUnit)
case .right:
helperView.frame = CGRect(x: bounds.width - cropBoxHotAreaUnit/2, y: cropBoxHotAreaUnit/2, width: cropBoxHotAreaUnit, height: bounds.height - cropBoxHotAreaUnit)
helperView.frame = CGRect(x: bounds.width - cropBoxHotAreaUnit/2,
y: cropBoxHotAreaUnit/2,
width: cropBoxHotAreaUnit,
height: bounds.height - cropBoxHotAreaUnit)
case .bottomRight:
helperView.frame = CGRect(x: bounds.width - cropBoxHotAreaUnit/2, y: bounds.height - cropBoxHotAreaUnit/2, width: cropBoxHotAreaUnit, height: cropBoxHotAreaUnit)
helperView.frame = CGRect(x: bounds.width - cropBoxHotAreaUnit/2,
y: bounds.height - cropBoxHotAreaUnit/2,
width: cropBoxHotAreaUnit,
height: cropBoxHotAreaUnit)
case .bottom:
helperView.frame = CGRect(x: cropBoxHotAreaUnit/2, y: bounds.height - cropBoxHotAreaUnit/2, width: bounds.width - cropBoxHotAreaUnit, height: cropBoxHotAreaUnit)
helperView.frame = CGRect(x: cropBoxHotAreaUnit/2,
y: bounds.height - cropBoxHotAreaUnit/2,
width: bounds.width - cropBoxHotAreaUnit,
height: cropBoxHotAreaUnit)
case .bottomLeft:
helperView.frame = CGRect(x: -cropBoxHotAreaUnit/2, y: bounds.height - cropBoxHotAreaUnit/2, width: cropBoxHotAreaUnit, height: cropBoxHotAreaUnit)
helperView.frame = CGRect(x: -cropBoxHotAreaUnit/2,
y: bounds.height - cropBoxHotAreaUnit/2,
width: cropBoxHotAreaUnit,
height: cropBoxHotAreaUnit)
case .left:
helperView.frame = CGRect(x: -cropBoxHotAreaUnit/2, y: cropBoxHotAreaUnit/2, width: cropBoxHotAreaUnit, height: bounds.height - cropBoxHotAreaUnit)
helperView.frame = CGRect(x: -cropBoxHotAreaUnit/2,
y: cropBoxHotAreaUnit/2,
width: cropBoxHotAreaUnit,
height: bounds.height - cropBoxHotAreaUnit)
case .none:
break
}
Expand Down
1 change: 0 additions & 1 deletion Sources/Mantis/CropViewController/CropToolbar.swift
Expand Up @@ -281,7 +281,6 @@ extension CropToolbar {
button.setContentCompressionResistancePriority(UILayoutPriority(rawValue: compressionPriority), for: .horizontal)
button.setContentCompressionResistancePriority(UILayoutPriority(rawValue: compressionPriority), for: .vertical)


button.addTarget(self, action: action, for: .touchUpInside)
button.contentEdgeInsets = UIEdgeInsets(top: 4, left: 10, bottom: 4, right: 10)

Expand Down
21 changes: 15 additions & 6 deletions Sources/Mantis/Extensions/CGImageExtensions.swift
Expand Up @@ -11,13 +11,18 @@

import UIKit

enum ImageProcessError: Error {
case noColorSpace
case failedToBuildContext(colorSpaceModel: CGColorSpaceModel, bitsPerPixel: Int, bitsPerComponent: Int)
}

extension CGImage {
func transformedImage(_ transform: CGAffineTransform,
outputSize: CGSize,
cropSize: CGSize,
imageViewSize: CGSize) -> CGImage? {
imageViewSize: CGSize) throws -> CGImage? {
guard var colorSpaceRef = self.colorSpace else {
return self
throw ImageProcessError.noColorSpace
}

// If the color space does not allow output, default to the RGB color space
Expand All @@ -32,16 +37,18 @@ extension CGImage {
switch(bitsPerPixel, bitsPerComponent) {
case (16, 5):
return CGImageAlphaInfo.noneSkipFirst.rawValue
case (24, 8), (32, 8), (48, 16), (64, 16):
case (24, 8), (48, 16):
return CGImageAlphaInfo.none.rawValue
case (32, 8), (64, 16):
return CGImageAlphaInfo.premultipliedLast.rawValue
case (32, 10):
if #available(iOS 12, macOS 10.14, *) {
return CGImageAlphaInfo.alphaOnly.rawValue | CGImagePixelFormatInfo.RGBCIF10.rawValue
} else {
return bitmapInfo.rawValue
break
}
case (128, 32):
return CGImageAlphaInfo.premultipliedLast.rawValue | CGBitmapInfo.floatComponents.rawValue
return CGImageAlphaInfo.premultipliedLast.rawValue | (bitmapInfo.rawValue & CGBitmapInfo.floatComponents.rawValue)
default:
break
}
Expand All @@ -57,7 +64,9 @@ extension CGImage {
bytesPerRow: bitmapBytesPerRow,
space: colorSpaceRef,
bitmapInfo: getBitmapInfo()) else {
return self
throw ImageProcessError.failedToBuildContext(colorSpaceModel: colorSpaceRef.model,
bitsPerPixel: bitsPerPixel,
bitsPerComponent: bitsPerComponent)
}

context.setFillColor(UIColor.clear.cgColor)
Expand Down
23 changes: 17 additions & 6 deletions Sources/Mantis/Extensions/UIImageExtensions.swift
Expand Up @@ -100,14 +100,25 @@ extension UIImage {
transform.transformed(by: cropInfo)

let outputSize = getOutputCropImageSize(by: cropInfo)
guard let transformedCGImage = fixedOrientationImage.transformedImage(transform,
outputSize: outputSize,
cropSize: cropInfo.cropSize,
imageViewSize: cropInfo.imageViewSize) else {

do {
guard let transformedCGImage = try fixedOrientationImage.transformedImage(transform,
outputSize: outputSize,
cropSize: cropInfo.cropSize,
imageViewSize: cropInfo.imageViewSize) else {
return nil
}

return UIImage(cgImage: transformedCGImage)
} catch {
print("*** Failed to get transfromed image ***")

if let error = error as? ImageProcessError {
print("Failed reason: \(error)")
}

return nil
}

return UIImage(cgImage: transformedCGImage)
}

func getOutputCropImageSize(by cropInfo: CropInfo) -> CGSize {
Expand Down
1 change: 0 additions & 1 deletion Sources/Mantis/Protocols/RotationDialProtocol.swift
Expand Up @@ -17,7 +17,6 @@ protocol RotationDialProtocol: UIView {
func setup(with frame: CGRect)
@discardableResult func rotateDialPlate(by angle: Angle) -> Bool
func rotateDialPlate(to angle: Angle, animated: Bool)
func resetAngle(animated: Bool)
func getRotationAngle() -> Angle
func setRotationCenter(by point: CGPoint, of view: UIView)
func reset()
Expand Down
4 changes: 0 additions & 4 deletions Sources/Mantis/RotationDial/RotationDial.swift
Expand Up @@ -259,10 +259,6 @@ extension RotationDial: RotationDialProtocol {
}
}

func resetAngle(animated: Bool) {
rotateDialPlate(to: Angle(radians: 0), animated: animated)
}

func getRotationAngle() -> Angle {
guard let dialPlate = dialPlate else { return Angle(degrees: 0) }

Expand Down
2 changes: 1 addition & 1 deletion Sources/Mantis/RotationDial/RotationDialPlate.swift
Expand Up @@ -145,7 +145,7 @@ class RotationDialPlate: UIView {
private func setCenterPart() {
let layer = CAShapeLayer()
let radius: CGFloat = 4
layer.frame = CGRect(x: (self.layer.bounds.width - radius) / 2 ,
layer.frame = CGRect(x: (self.layer.bounds.width - radius) / 2,
y: (self.layer.bounds.height - radius) / 2,
width: radius,
height: radius)
Expand Down
3 changes: 1 addition & 2 deletions SwiftUIExample/MantisSwiftUIExample/CameraView.swift
Expand Up @@ -31,7 +31,7 @@ struct CameraView: UIViewControllerRepresentable {
_image = image
}

func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) {
if let selectedImage = info[.originalImage] as? UIImage {
self.image = selectedImage
}
Expand All @@ -43,4 +43,3 @@ struct CameraView: UIViewControllerRepresentable {
}
}
}

Expand Up @@ -41,4 +41,3 @@ class CustomViewController: Mantis.CropViewController {
crop()
}
}

2 changes: 1 addition & 1 deletion SwiftUIExample/MantisSwiftUIExample/ImagePickerView.swift
Expand Up @@ -19,7 +19,7 @@ struct ImagePickerView: UIViewControllerRepresentable {
self.parent = parent
}

func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) {
if let selectedImage = info[.originalImage] as? UIImage {
parent.image = selectedImage
}
Expand Down
Expand Up @@ -41,7 +41,6 @@ struct SourceTypeSelectionView: View {
}
}


struct SourceTypeSelectionView_Previews: PreviewProvider {
static var previews: some View {
SourceTypeSelectionView(showSourceTypeSelection: .constant(false), showCamera: .constant(false), showImagePicker: .constant(false))
Expand Down
12 changes: 9 additions & 3 deletions Tests/MantisTests/CropWorkbenchViewTests.swift
Expand Up @@ -21,20 +21,26 @@ final class CropWorkbenchViewTests: XCTestCase {
}

func testUpdateMinZoomScale() {
workbechView = CropWorkbenchView(frame: .zero, minimumZoomScale: 1.0, maximumZoomScale: 15, imageContainer: FakeImageContainer(frame: .init(x: 0, y: 0, width: 200, height: 100)))
let fakeImageContainer = FakeImageContainer(frame: .init(x: 0, y: 0, width: 200, height: 100))
workbechView = CropWorkbenchView(frame: .zero,
minimumZoomScale: 1.0,
maximumZoomScale: 15,
imageContainer: fakeImageContainer)
workbechView.bounds = CGRect(x: 0, y: 0, width: 400, height: 100)
workbechView.updateMinZoomScale()

XCTAssertEqual(workbechView.minimumZoomScale, 2)

workbechView = CropWorkbenchView(frame: .zero, minimumZoomScale: 1.0, maximumZoomScale: 15, imageContainer: FakeImageContainer(frame: .init(x: 0, y: 0, width: 200, height: 100)))
workbechView = CropWorkbenchView(frame: .zero,
minimumZoomScale: 1.0,
maximumZoomScale: 15,
imageContainer: fakeImageContainer)
workbechView.bounds = CGRect(x: 0, y: 0, width: 400, height: 300)
workbechView.updateMinZoomScale()

XCTAssertEqual(workbechView.minimumZoomScale, 3)
}


func testShouldScale() {
workbechView.bounds = CGRect(x: 0, y: 0, width: 200, height: 100)
workbechView.contentSize = CGSize(width: 200, height: 100)
Expand Down
4 changes: 0 additions & 4 deletions Tests/MantisTests/Mock/FakeRotationDialView.swift
Expand Up @@ -31,10 +31,6 @@ class FakeRotationDialView: UIView, RotationDialProtocol {

}

func resetAngle(animated: Bool) {

}

func getRotationAngle() -> Angle {
.init(degrees: 0)
}
Expand Down

0 comments on commit cdb7117

Please sign in to comment.