Skip to content

Commit

Permalink
Merge pull request WeTransfer#165 from konciergeMD/Yang/FixTheOrienta…
Browse files Browse the repository at this point in the history
…tion

Camera Roll support + camera capturing optimization + Added a Cancel Button in Edit Image VC
  • Loading branch information
AvdLee authored Jul 24, 2019
2 parents 4068ad2 + 2cbe3a4 commit f80553d
Show file tree
Hide file tree
Showing 8 changed files with 83 additions and 28 deletions.
8 changes: 6 additions & 2 deletions WeScan.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
objects = {

/* Begin PBXBuildFile section */
362967782294C23700B9FC4A /* CGImagePropertyOrientation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 362967772294C23700B9FC4A /* CGImagePropertyOrientation.swift */; };
74E27858215446C900361812 /* FBSnapshotTestCase.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 74E27850215446C200361812 /* FBSnapshotTestCase.framework */; };
74F7D034211ACBD90046AF7E /* CIRectangleDetectorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74F7D033211ACBD90046AF7E /* CIRectangleDetectorTests.swift */; };
74F7D036211ACBEE0046AF7E /* CaptureSessionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74F7D035211ACBEE0046AF7E /* CaptureSessionTests.swift */; };
Expand Down Expand Up @@ -184,6 +185,7 @@

/* Begin PBXFileReference section */
274DF35922D363BC0095CE49 /* hu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hu; path = hu.lproj/Localizable.strings; sourceTree = "<group>"; };
362967772294C23700B9FC4A /* CGImagePropertyOrientation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CGImagePropertyOrientation.swift; sourceTree = "<group>"; };
4A644E2221A73C2B00B20839 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = "<group>"; };
74E27848215446C200361812 /* FBSnapshotTestCase.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = FBSnapshotTestCase.xcodeproj; path = "Submodules/ios-snapshot-test-case/FBSnapshotTestCase.xcodeproj"; sourceTree = "<group>"; };
74E2785B2154528300361812 /* FBSnapshotTestCase.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = FBSnapshotTestCase.xcodeproj; path = "Submodules/ios-snapshot-test-case/FBSnapshotTestCase.xcodeproj"; sourceTree = SOURCE_ROOT; };
Expand Down Expand Up @@ -437,6 +439,7 @@
B9274D39219B951000F9FCD1 /* CIImage+Utils.swift */,
C3E2EB8D20B8970800A42E58 /* UIImage+Utils.swift */,
A1DF90F52037187500841A11 /* UIImage+Orientation.swift */,
362967772294C23700B9FC4A /* CGImagePropertyOrientation.swift */,
);
path = Extensions;
sourceTree = "<group>";
Expand Down Expand Up @@ -815,6 +818,7 @@
C3E2EB8E20B8970800A42E58 /* UIImage+Utils.swift in Sources */,
A165F67E2044741B002D5ED6 /* ShutterButton.swift in Sources */,
B940E3AD21A829EE003B3C0B /* CaptureSession+Orientation.swift in Sources */,
362967782294C23700B9FC4A /* CGImagePropertyOrientation.swift in Sources */,
B992E831210C36A400C33A21 /* VisionRectangleDetector.swift in Sources */,
B940E3D821AE0B42003B3C0B /* CaptureDevice.swift in Sources */,
A1F22EA5202DB3AA001723AD /* CGPoint+Utils.swift in Sources */,
Expand Down Expand Up @@ -1033,7 +1037,7 @@
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = M5DJ4YC39W;
DEVELOPMENT_TEAM = 7P4LQNMZS5;
INFOPLIST_FILE = WeScanSampleProject/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
Expand All @@ -1050,7 +1054,7 @@
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = M5DJ4YC39W;
DEVELOPMENT_TEAM = 7P4LQNMZS5;
INFOPLIST_FILE = WeScanSampleProject/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
Expand Down
7 changes: 7 additions & 0 deletions WeScan/Common/VisionRectangleDetector.swift
Original file line number Diff line number Diff line change
Expand Up @@ -78,4 +78,11 @@ struct VisionRectangleDetector {
height: image.extent.height, completion: completion)
}

static func rectangle(forImage image: CIImage, orientation: CGImagePropertyOrientation, completion: @escaping ((Quadrilateral?) -> Void)) {
let imageRequestHandler = VNImageRequestHandler(ciImage: image, orientation: orientation, options: [:])
let orientedImage = image.oriented(orientation)
VisionRectangleDetector.completeImageRequest(
for: imageRequestHandler, width: orientedImage.extent.width,
height: orientedImage.extent.height, completion: completion)
}
}
26 changes: 21 additions & 5 deletions WeScan/Edit/EditScanViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,13 @@ final class EditScanViewController: UIViewController {
button.tintColor = navigationController?.navigationBar.tintColor
return button
}()

lazy private var cancelButton: UIBarButtonItem = {
let title = NSLocalizedString("wescan.edit.button.cancel", tableName: nil, bundle: Bundle(for: EditScanViewController.self), value: "Cancel", comment: "A generic cancel button")
let button = UIBarButtonItem(title: title, style: .plain, target: self, action: #selector(cancelButtonTapped))
button.tintColor = navigationController?.navigationBar.tintColor
return button
}()

/// The image the quadrilateral was detected on.
private let image: UIImage
Expand Down Expand Up @@ -67,7 +74,12 @@ final class EditScanViewController: UIViewController {
setupConstraints()
title = NSLocalizedString("wescan.edit.title", tableName: nil, bundle: Bundle(for: EditScanViewController.self), value: "Edit Scan", comment: "The title of the EditScanViewController")
navigationItem.rightBarButtonItem = nextButton

if let firstVC = self.navigationController?.viewControllers.first, firstVC == self {
navigationItem.leftBarButtonItem = cancelButton
} else {
navigationItem.leftBarButtonItem = nil
}

zoomGestureController = ZoomGestureController(image: image, quadView: quadView)

let touchDown = UILongPressGestureRecognizer(target: zoomGestureController, action: #selector(zoomGestureController.handle(pan:)))
Expand Down Expand Up @@ -118,6 +130,11 @@ final class EditScanViewController: UIViewController {
}

// MARK: - Actions
@objc func cancelButtonTapped() {
if let imageScannerController = navigationController as? ImageScannerController {
imageScannerController.imageScannerDelegate?.imageScannerControllerDidCancel(imageScannerController)
}
}

@objc func pushReviewController() {
guard let quad = quadView.quad,
Expand All @@ -128,24 +145,23 @@ final class EditScanViewController: UIViewController {
}
return
}

// Detected Quadrilateral
let cgOrientation = CGImagePropertyOrientation(image.imageOrientation)
let orientedImage = ciImage.oriented(forExifOrientation: Int32(cgOrientation.rawValue))
let scaledQuad = quad.scale(quadView.bounds.size, image.size)
self.quad = scaledQuad

// Cropped Image
var cartesianScaledQuad = scaledQuad.toCartesian(withHeight: image.size.height)
cartesianScaledQuad.reorganize()

let filteredImage = ciImage.applyingFilter("CIPerspectiveCorrection", parameters: [
let filteredImage = orientedImage.applyingFilter("CIPerspectiveCorrection", parameters: [
"inputTopLeft": CIVector(cgPoint: cartesianScaledQuad.bottomLeft),
"inputTopRight": CIVector(cgPoint: cartesianScaledQuad.bottomRight),
"inputBottomLeft": CIVector(cgPoint: cartesianScaledQuad.topLeft),
"inputBottomRight": CIVector(cgPoint: cartesianScaledQuad.topRight)
])

let croppedImage = UIImage.from(ciImage: filteredImage)

// Enhanced Image
let enhancedImage = filteredImage.applyingAdaptiveThreshold()?.withFixedOrientation()
let enhancedScan = enhancedImage.flatMap { ImageScannerScan(image: $0) }
Expand Down
34 changes: 34 additions & 0 deletions WeScan/Extensions/CGImagePropertyOrientation.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
//
// CGImagePropertyOrientation.swift
// WeScan
//
// Created by Yang Chen on 5/21/19.
// Copyright © 2019 WeTransfer. All rights reserved.
//

import Foundation
extension CGImagePropertyOrientation {
init(_ uiOrientation: UIImage.Orientation) {
switch uiOrientation {
case .up:
self = .up
case .upMirrored:
self = .upMirrored
case .down:
self = .down
case .downMirrored:
self = .downMirrored
case .left:
self = .left
case .leftMirrored:
self = .leftMirrored
case .right:
self = .right
case .rightMirrored:
self = .rightMirrored
@unknown default:
assertionFailure("Unknow orientation, falling to default")
self = .right
}
}
}
4 changes: 2 additions & 2 deletions WeScan/Extensions/UIImage+Utils.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ extension UIImage {
/// Creates a UIImage from the specified CIImage.
static func from(ciImage: CIImage) -> UIImage {
if let cgImage = CIContext(options: nil).createCGImage(ciImage, from: ciImage.extent) {
return UIImage(cgImage: cgImage).withFixedOrientation()
return UIImage(cgImage: cgImage)
} else {
return UIImage(ciImage: ciImage, scale: 1.0, orientation: .up).withFixedOrientation()
return UIImage(ciImage: ciImage, scale: 1.0, orientation: .up)
}
}

Expand Down
22 changes: 10 additions & 12 deletions WeScan/ImageScannerController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -74,26 +74,24 @@ public final class ImageScannerController: UINavigationController {
// Whether or not we detect a quad, present the edit view controller after attempting to detect a quad.
// *** Vision *requires* a completion block to detect rectangles, but it's instant.
// *** When using Vision, we'll present the normal edit view controller first, then present the updated edit view controller later.
defer {
let editViewController = EditScanViewController(image: image, quad: detectedQuad, rotateImage: true)
setViewControllers([editViewController], animated: false)
}


guard let ciImage = CIImage(image: image) else { return }

let orientation = CGImagePropertyOrientation(image.imageOrientation)
let orientedImage = ciImage.oriented(forExifOrientation: Int32(orientation.rawValue))
if #available(iOS 11.0, *) {

// Use the VisionRectangleDetector on iOS 11 to attempt to find a rectangle from the initial image.
VisionRectangleDetector.rectangle(forImage: ciImage) { (quad) in
detectedQuad = quad
detectedQuad?.reorganize()

let editViewController = EditScanViewController(image: image, quad: detectedQuad, rotateImage: true)
VisionRectangleDetector.rectangle(forImage: ciImage, orientation: orientation) { (quad) in
detectedQuad = quad?.toCartesian(withHeight: orientedImage.extent.height)
let editViewController = EditScanViewController(image: image, quad: detectedQuad, rotateImage: false)
self.setViewControllers([editViewController], animated: true)
}
} else {
// Use the CIRectangleDetector on iOS 10 to attempt to find a rectangle from the initial image.
detectedQuad = CIRectangleDetector.rectangle(forImage: ciImage)
detectedQuad?.reorganize()
detectedQuad = CIRectangleDetector.rectangle(forImage: ciImage)?.toCartesian(withHeight: orientedImage.extent.height)
let editViewController = EditScanViewController(image: image, quad: detectedQuad, rotateImage: false)
setViewControllers([editViewController], animated: false)
}
}
}
Expand Down
7 changes: 1 addition & 6 deletions WeScan/Scan/CaptureSessionManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -148,11 +148,10 @@ final class CaptureSessionManager: NSObject, AVCaptureVideoDataOutputSampleBuffe
delegate?.captureSessionManager(self, didFailWithError: error)
return
}

CaptureSession.current.setImageOrientation()
let photoSettings = AVCapturePhotoSettings()
photoSettings.isHighResolutionPhotoEnabled = true
photoSettings.isAutoStillImageStabilizationEnabled = true

photoOutput.capturePhoto(with: photoSettings, delegate: self)
}

Expand Down Expand Up @@ -243,8 +242,6 @@ extension CaptureSessionManager: AVCapturePhotoCaptureDelegate {
return
}

CaptureSession.current.setImageOrientation()

isDetecting = false
rectangleFunnel.currentAutoScanPassCount = 0
delegate?.didStartCapturingPicture(for: self)
Expand All @@ -267,8 +264,6 @@ extension CaptureSessionManager: AVCapturePhotoCaptureDelegate {
return
}

CaptureSession.current.setImageOrientation()

isDetecting = false
rectangleFunnel.currentAutoScanPassCount = 0
delegate?.didStartCapturingPicture(for: self)
Expand Down
3 changes: 2 additions & 1 deletion WeScan/Scan/ScannerViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ final class ScannerViewController: UIViewController {
visualEffectView.removeFromSuperview()
navigationController?.navigationBar.isTranslucent = false
navigationController?.navigationBar.barStyle = originalBarStyle ?? .default

captureSessionManager?.stop()
guard let device = AVCaptureDevice.default(for: AVMediaType.video) else { return }
if device.torchMode == .on {
toggleFlash()
Expand Down Expand Up @@ -296,6 +296,7 @@ extension ScannerViewController: RectangleDetectionDelegateProtocol {

func didStartCapturingPicture(for captureSessionManager: CaptureSessionManager) {
activityIndicator.startAnimating()
captureSessionManager.stop()
shutterButton.isUserInteractionEnabled = false
}

Expand Down

0 comments on commit f80553d

Please sign in to comment.