Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: output image processing errors #305

Merged
merged 3 commits into from May 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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