From 1adce3b94375fbf603b231102bcdb7c4fa071eb8 Mon Sep 17 00:00:00 2001 From: David-Okun Date: Mon, 11 Sep 2017 16:50:38 -0500 Subject: [PATCH 1/7] Working on re architecture, now working with separate camera from view controller --- Lumina/Lumina.xcodeproj/project.pbxproj | 16 +- .../xcshareddata/xcschemes/Lumina.xcscheme | 2 +- Lumina/Lumina/Info.plist | 2 +- Lumina/Lumina/LuminaButton.swift | 82 ++ Lumina/Lumina/LuminaCamera.swift | 102 +++ Lumina/Lumina/LuminaController.swift | 692 ---------------- Lumina/Lumina/LuminaViewController.swift | 748 ++++++++++++++++++ .../LuminaSample/ViewController.swift | 45 +- 8 files changed, 969 insertions(+), 720 deletions(-) create mode 100644 Lumina/Lumina/LuminaButton.swift create mode 100644 Lumina/Lumina/LuminaCamera.swift delete mode 100644 Lumina/Lumina/LuminaController.swift create mode 100644 Lumina/Lumina/LuminaViewController.swift diff --git a/Lumina/Lumina.xcodeproj/project.pbxproj b/Lumina/Lumina.xcodeproj/project.pbxproj index d5f897b..fb10106 100644 --- a/Lumina/Lumina.xcodeproj/project.pbxproj +++ b/Lumina/Lumina.xcodeproj/project.pbxproj @@ -7,12 +7,14 @@ objects = { /* Begin PBXBuildFile section */ + 53806FA41F6618230009A0E8 /* LuminaCamera.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53806FA31F6618230009A0E8 /* LuminaCamera.swift */; }; + 53806FA61F672F780009A0E8 /* LuminaButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53806FA51F672F780009A0E8 /* LuminaButton.swift */; }; 53883FD11EC4DE5B00D1DE40 /* LuminaMetadataBorderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53883FD01EC4DE5B00D1DE40 /* LuminaMetadataBorderView.swift */; }; 539848301EB8F14300F77AEA /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5398482F1EB8F14300F77AEA /* AVFoundation.framework */; }; 53B828DA1EAAA07F00E3A624 /* Lumina.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 53B828D01EAAA07F00E3A624 /* Lumina.framework */; }; 53B828DF1EAAA07F00E3A624 /* LuminaTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53B828DE1EAAA07F00E3A624 /* LuminaTests.swift */; }; 53B828E11EAAA07F00E3A624 /* Lumina.h in Headers */ = {isa = PBXBuildFile; fileRef = 53B828D31EAAA07F00E3A624 /* Lumina.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 53B8290F1EAAA24600E3A624 /* LuminaController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53B8290E1EAAA24600E3A624 /* LuminaController.swift */; }; + 53B8290F1EAAA24600E3A624 /* LuminaViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53B8290E1EAAA24600E3A624 /* LuminaViewController.swift */; }; 53B9CCFB1EBF6C62008E4A43 /* Media.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 53B9CCFA1EBF6C62008E4A43 /* Media.xcassets */; }; 53B9CCFD1EBFD22E008E4A43 /* LuminaTextPromptView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53B9CCFC1EBFD22E008E4A43 /* LuminaTextPromptView.swift */; }; /* End PBXBuildFile section */ @@ -28,6 +30,8 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 53806FA31F6618230009A0E8 /* LuminaCamera.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LuminaCamera.swift; sourceTree = ""; }; + 53806FA51F672F780009A0E8 /* LuminaButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LuminaButton.swift; sourceTree = ""; }; 53883FD01EC4DE5B00D1DE40 /* LuminaMetadataBorderView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LuminaMetadataBorderView.swift; sourceTree = ""; }; 5398482F1EB8F14300F77AEA /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = System/Library/Frameworks/AVFoundation.framework; sourceTree = SDKROOT; }; 53B828D01EAAA07F00E3A624 /* Lumina.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Lumina.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -36,7 +40,7 @@ 53B828D91EAAA07F00E3A624 /* LuminaTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = LuminaTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 53B828DE1EAAA07F00E3A624 /* LuminaTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LuminaTests.swift; sourceTree = ""; }; 53B828E01EAAA07F00E3A624 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 53B8290E1EAAA24600E3A624 /* LuminaController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LuminaController.swift; sourceTree = ""; }; + 53B8290E1EAAA24600E3A624 /* LuminaViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LuminaViewController.swift; sourceTree = ""; }; 53B9CCFA1EBF6C62008E4A43 /* Media.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Media.xcassets; sourceTree = ""; }; 53B9CCFC1EBFD22E008E4A43 /* LuminaTextPromptView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LuminaTextPromptView.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -93,9 +97,11 @@ children = ( 53B828D31EAAA07F00E3A624 /* Lumina.h */, 53B828D41EAAA07F00E3A624 /* Info.plist */, - 53B8290E1EAAA24600E3A624 /* LuminaController.swift */, + 53B8290E1EAAA24600E3A624 /* LuminaViewController.swift */, + 53806FA31F6618230009A0E8 /* LuminaCamera.swift */, 53B9CCFC1EBFD22E008E4A43 /* LuminaTextPromptView.swift */, 53883FD01EC4DE5B00D1DE40 /* LuminaMetadataBorderView.swift */, + 53806FA51F672F780009A0E8 /* LuminaButton.swift */, 53B9CCFA1EBF6C62008E4A43 /* Media.xcassets */, ); path = Lumina; @@ -225,7 +231,9 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 53B8290F1EAAA24600E3A624 /* LuminaController.swift in Sources */, + 53806FA41F6618230009A0E8 /* LuminaCamera.swift in Sources */, + 53B8290F1EAAA24600E3A624 /* LuminaViewController.swift in Sources */, + 53806FA61F672F780009A0E8 /* LuminaButton.swift in Sources */, 53B9CCFD1EBFD22E008E4A43 /* LuminaTextPromptView.swift in Sources */, 53883FD11EC4DE5B00D1DE40 /* LuminaMetadataBorderView.swift in Sources */, ); diff --git a/Lumina/Lumina.xcodeproj/xcshareddata/xcschemes/Lumina.xcscheme b/Lumina/Lumina.xcodeproj/xcshareddata/xcschemes/Lumina.xcscheme index e763e79..0ff8eff 100644 --- a/Lumina/Lumina.xcodeproj/xcshareddata/xcschemes/Lumina.xcscheme +++ b/Lumina/Lumina.xcodeproj/xcshareddata/xcschemes/Lumina.xcscheme @@ -53,7 +53,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 0.4.1 + 0.4.2 CFBundleVersion $(CURRENT_PROJECT_VERSION) NSPrincipalClass diff --git a/Lumina/Lumina/LuminaButton.swift b/Lumina/Lumina/LuminaButton.swift new file mode 100644 index 0000000..5573a24 --- /dev/null +++ b/Lumina/Lumina/LuminaButton.swift @@ -0,0 +1,82 @@ +// +// LuminaButton.swift +// Lumina +// +// Created by David Okun IBM on 9/11/17. +// Copyright © 2017 David Okun. All rights reserved. +// + +import UIKit + +enum SystemButtonType { + case torch + case cameraSwitch + case photoCapture + case cancel +} + +final class LuminaButton: UIButton { + private var squareSystemButtonWidth = 40 + private var squareSystemButtonHeight = 40 + private var cancelButtonWidth = 70 + private var cancelButtonHeight = 30 + + private var _image: UIImage? + var image: UIImage? { + get { + return _image + } + set { + self.setImage(newValue, for: UIControlState.normal) + _image = newValue + } + } + + private var _text: String? + var text: String? { + get { + return _text + } + set { + self.setTitle(newValue, for: UIControlState.normal) + _text = newValue + } + } + + required init() { + super.init(frame: CGRect.zero) + self.backgroundColor = UIColor.clear + if let titleLabel = self.titleLabel { + titleLabel.textColor = UIColor.white + titleLabel.font = UIFont.systemFont(ofSize: 20) + } + } + + init(with systemStyle: SystemButtonType) { + super.init(frame: CGRect.zero) + self.backgroundColor = UIColor.clear + if let titleLabel = self.titleLabel { + titleLabel.textColor = UIColor.white + titleLabel.font = UIFont.systemFont(ofSize: 20) + } + switch systemStyle { + case .torch: + self.image = UIImage(named: "cameraTorch", in: Bundle(for: LuminaViewController.self), compatibleWith: nil) + self.frame = CGRect(origin: CGPoint(x: 10, y: 10), size: CGSize(width: self.squareSystemButtonWidth, height: self.squareSystemButtonHeight)) + break + case .cameraSwitch: + self.image = UIImage(named: "cameraSwitch", in: Bundle(for: LuminaViewController.self), compatibleWith: nil) + self.frame = CGRect(origin: CGPoint(x: UIScreen.main.bounds.maxX - 50, y: 10), size: CGSize(width: self.squareSystemButtonWidth, height: self.squareSystemButtonHeight)) + break + case .cancel: + self.text = "Cancel" + self.frame = CGRect(origin: CGPoint(x: 10, y: UIScreen.main.bounds.maxY - 50), size: CGSize(width: self.cancelButtonWidth, height: self.cancelButtonHeight)) + default: + break + } + } + + required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + } +} diff --git a/Lumina/Lumina/LuminaCamera.swift b/Lumina/Lumina/LuminaCamera.swift new file mode 100644 index 0000000..fbf00cc --- /dev/null +++ b/Lumina/Lumina/LuminaCamera.swift @@ -0,0 +1,102 @@ +// +// LuminaCamera.swift +// Lumina +// +// Created by David Okun IBM on 9/10/17. +// Copyright © 2017 David Okun. All rights reserved. +// + +import UIKit +import AVFoundation + +protocol LuminaCameraDelegate { + func finishedUpdating() +} + +final class LuminaCamera: NSObject { + var controller: LuminaViewController? + fileprivate var delegate: LuminaCameraDelegate? + + var position: CameraDirection = .unspecified { + didSet { + if self.session.isRunning { + self.session.stopRunning() + } + do { + try update() + } catch { + print("could not update camera position") + } + } + } + + required init(with controller: LuminaViewController) { + self.controller = controller + } + + fileprivate var session = AVCaptureSession() + fileprivate var discoverySession: AVCaptureDevice.DiscoverySession? { + return AVCaptureDevice.DiscoverySession(deviceTypes: [AVCaptureDevice.DeviceType.builtInWideAngleCamera], mediaType: AVMediaType.video, position: AVCaptureDevice.Position.unspecified) + } + + fileprivate var videoInput: AVCaptureDeviceInput? + fileprivate var videoOutput = AVCaptureVideoDataOutput() + + private var _previewLayer: AVCaptureVideoPreviewLayer? + var previewLayer: AVCaptureVideoPreviewLayer? { + get { + if let existingLayer = self._previewLayer { + return existingLayer + } + guard let controller = self.controller else { + return nil + } + let previewLayer = AVCaptureVideoPreviewLayer(session: self.session) + previewLayer.frame = controller.view.bounds + previewLayer.videoGravity = AVLayerVideoGravity.resizeAspect + _previewLayer = previewLayer + return previewLayer + } + } + + func update() throws { + if let currentInput = self.videoInput { + self.session.removeInput(currentInput) + self.session.removeOutput(self.videoOutput) + } + do { + guard let device = getDevice(with: self.position == .front ? AVCaptureDevice.Position.front : AVCaptureDevice.Position.back) else { + print("could not find valid AVCaptureDevice") + return + } + let input = try AVCaptureDeviceInput(device: device) + if self.session.canAddInput(input) && self.session.canAddOutput(self.videoOutput) { + self.videoInput = input + self.session.addInput(input) + self.session.addOutput(self.videoOutput) + self.session.commitConfiguration() + self.session.startRunning() + if let delegate = self.delegate { + delegate.finishedUpdating() + } + } else { + print("could not add input") + } + } catch { + // TODO: add error handling here. + } + } + + private func getDevice(with position: AVCaptureDevice.Position) -> AVCaptureDevice? { + guard let discoverySession = self.discoverySession else { + return nil + } + for device in discoverySession.devices { + if device.position == position { + return device + } + } + return nil + } + +} diff --git a/Lumina/Lumina/LuminaController.swift b/Lumina/Lumina/LuminaController.swift deleted file mode 100644 index d62ec6e..0000000 --- a/Lumina/Lumina/LuminaController.swift +++ /dev/null @@ -1,692 +0,0 @@ -// -// LuminaController.swift -// Lumina -// -// Created by David Okun IBM on 4/21/17. -// Copyright © 2017 David Okun. All rights reserved. -// - -import Foundation -import AVFoundation - -public protocol LuminaDelegate { - func detected(camera: LuminaController, image: UIImage) - func detected(camera: LuminaController, data: [Any]) - func cancelled(camera: LuminaController) -} - -public enum CameraDirection: String { - case front = "Front" - case back = "Back" - case telephoto = "Telephoto" - case dual = "Dual" -} - -public final class LuminaController: UIViewController { - private var sessionPreset: String? - - fileprivate var previewLayer: AVCaptureVideoPreviewLayer? - private var previewView: UIView? - - private var metadataOutput: AVCaptureMetadataOutput? - private var videoBufferQueue = DispatchQueue(label: "com.lumina.videoBufferQueue") - private var metadataBufferQueue = DispatchQueue(label: "com.lumina.metadataBufferQueue") - - fileprivate var input: AVCaptureDeviceInput? - - fileprivate var videoOutput: AVCaptureVideoDataOutput { - let videoOutput = AVCaptureVideoDataOutput() - videoOutput.alwaysDiscardsLateVideoFrames = true - videoOutput.videoSettings = [kCVPixelBufferPixelFormatTypeKey as AnyHashable as! String : kCVPixelFormatType_32BGRA] - videoOutput.setSampleBufferDelegate(self, queue: videoBufferQueue) - return videoOutput - } - - fileprivate var textPromptView: LuminaTextPromptView? - fileprivate var initialPrompt: String? - - fileprivate var session: AVCaptureSession? - - fileprivate var cameraSwitchButton: UIButton? - fileprivate var cameraCancelButton: UIButton? - fileprivate var cameraTorchButton: UIButton? - fileprivate var currentCameraDirection: CameraDirection = .back - fileprivate var isUpdating = false - fileprivate var torchOn = false - fileprivate var metadataBordersCodes: [LuminaMetadataBorderView]? - fileprivate var metadataBordersFaces: [LuminaMetadataBorderView]? - fileprivate var metadataBordersCodesDestructionTimer: Timer? - fileprivate var metadataBordersFacesDestructionTimer: Timer? - - public var delegate: LuminaDelegate! = nil - public var trackImages = false - public var trackMetadata = false - public var improvedImageDetectionPerformance = false - public var drawMetadataBorders = false - - override public var prefersStatusBarHidden: Bool { - return true - } - - private var discoverySession: AVCaptureDevice.DiscoverySession? { - var deviceTypes: [AVCaptureDevice.DeviceType] = [] - deviceTypes.append(AVCaptureDevice.DeviceType.builtInWideAngleCamera) - if #available(iOS 10.2, *) { - deviceTypes.append(AVCaptureDevice.DeviceType.builtInDualCamera) - deviceTypes.append(AVCaptureDevice.DeviceType.builtInTelephotoCamera) - } - let discoverySession = AVCaptureDevice.DiscoverySession(deviceTypes: deviceTypes, mediaType: AVMediaType.video, position: AVCaptureDevice.Position.unspecified) - - return discoverySession - } - - private func getDevice(for cameraDirection: CameraDirection) -> AVCaptureDevice? { - var device: AVCaptureDevice? - guard let discoverySession = self.discoverySession else { - print("Could not get discovery session") - return nil - } - for discoveryDevice: AVCaptureDevice in discoverySession.devices { - if cameraDirection == .front { - if discoveryDevice.position == AVCaptureDevice.Position.front { - device = discoveryDevice - break - } - } else { - if discoveryDevice.position == AVCaptureDevice.Position.back { // TODO: add support for iPhone 7 plus dual cameras - device = discoveryDevice - break - } - } - } - return device - } - - public init?(camera: CameraDirection) { - super.init(nibName: nil, bundle: nil) - - let session = AVCaptureSession() - self.previewLayer = AVCaptureVideoPreviewLayer(session: session) - self.previewView = self.view - - guard let previewLayer = self.previewLayer else { - print("Could not access image preview layer") - return - } - previewLayer.videoGravity = AVLayerVideoGravity.resizeAspectFill - self.view.layer.addSublayer(previewLayer) - self.view.bounds = UIScreen.main.bounds - - previewLayer.frame = self.view.bounds - self.session = session - commitSession(for: camera) - createUI() - createTextPromptView() - } - - fileprivate func commitSession(for desiredCameraDirection: CameraDirection) { - guard let session = self.session else { - print("Error getting session") - return - } - self.currentCameraDirection = desiredCameraDirection - - session.sessionPreset = AVCaptureSession.Preset.high - - if let input = self.input { - session.removeInput(input) - } - - do { - guard let device = getDevice(for: desiredCameraDirection) else { - print("could not get desired camera direction") - return - } - try input = AVCaptureDeviceInput(device: device) - if session.canAddInput(input!) { - session.addInput(input!) - self.input = input! - } - } catch { - print("Error getting device input for \(desiredCameraDirection.rawValue)") - return - } - - let metadataOutput = AVCaptureMetadataOutput() - - let videoOutput = self.videoOutput - - if session.canAddOutput(videoOutput) { - session.addOutput(videoOutput) - } - - if session.canAddOutput(metadataOutput) { - session.addOutput(metadataOutput) - } - metadataOutput.setMetadataObjectsDelegate(self, queue: metadataBufferQueue) - metadataOutput.metadataObjectTypes = metadataOutput.availableMetadataObjectTypes - - self.metadataOutput = metadataOutput - - session.commitConfiguration() - session.startRunning() - - if let connection = videoOutput.connection(with: AVMediaType.video) { - connection.isEnabled = true - if connection.isVideoMirroringSupported && desiredCameraDirection == .front { - connection.isVideoMirrored = true - connection.preferredVideoStabilizationMode = .standard - } - } - - guard let cameraSwitchButton = self.cameraSwitchButton else { - print("Could not create camera switch button") - return - } - cameraSwitchButton.isEnabled = true - - } - - private func createUI() { - self.cameraSwitchButton = UIButton(frame: CGRect(x: self.view.frame.maxX - 50, y: self.view.frame.minY + 10, width: 40, height: 40)) - guard let cameraSwitchButton = self.cameraSwitchButton else { - print("Could not access camera switch button memory address") - return - } - cameraSwitchButton.backgroundColor = UIColor.clear - cameraSwitchButton.addTarget(self, action: #selector(cameraSwitchButtonTapped), for: UIControlEvents.touchUpInside) - self.view.addSubview(cameraSwitchButton) - - let image = UIImage(named: "cameraSwitch", in: Bundle(for: LuminaController.self), compatibleWith: nil) - cameraSwitchButton.setImage(image, for: .normal) - - self.cameraCancelButton = UIButton(frame: CGRect(origin: CGPoint(x: self.view.frame.minX + 10, y: self.view.frame.maxY - 40), size: CGSize(width: 70, height: 30))) - guard let cameraCancelButton = self.cameraCancelButton else { - return - } - cameraCancelButton.setTitle("Cancel", for: .normal) - cameraCancelButton.backgroundColor = UIColor.clear - guard let titleLabel = cameraCancelButton.titleLabel else { - print("Could not access cancel button label memory address") - return - } - titleLabel.textColor = UIColor.white - titleLabel.font = UIFont.systemFont(ofSize: 20) - cameraCancelButton.addTarget(self, action: #selector(cameraCancelButtonTapped), for: UIControlEvents.touchUpInside) - self.view.addSubview(cameraCancelButton) - - - - let cameraTorchButton = UIButton(frame: CGRect(origin: CGPoint(x: self.view.frame.minX + 10, y: self.view.frame.minY + 10), size: CGSize(width: 40, height: 40))) - cameraTorchButton.backgroundColor = UIColor.clear - cameraTorchButton.addTarget(self, action: #selector(cameraTorchButtonTapped), for: UIControlEvents.touchUpInside) - let torchImage = UIImage(named: "cameraTorch", in: Bundle(for: LuminaController.self), compatibleWith: nil) - cameraTorchButton.setImage(torchImage, for: .normal) - self.view.addSubview(cameraTorchButton) - self.cameraTorchButton = cameraTorchButton - } - - public required init?(coder aDecoder: NSCoder) { - return nil - } -} - -extension LuminaController { // MARK: Text prompt methods - fileprivate func createTextPromptView() { - let view = LuminaTextPromptView(frame: CGRect(origin: CGPoint(x: self.view.bounds.minX + 10, y: self.view.bounds.minY + 70), size: CGSize(width: self.view.bounds.size.width - 20, height: 80))) - if let prompt = self.initialPrompt { - view.updateText(to: prompt) - } - self.view.addSubview(view) - self.textPromptView = view - } - - public func updateTextPromptView(to text:String) { - DispatchQueue.main.async { - guard let view = self.textPromptView else { - print("No text prompt view to update!!") - return - } - view.updateText(to: text) - } - } - - public func hideTextPromptView(andEraseText: Bool) { - DispatchQueue.main.async { - guard let view = self.textPromptView else { - print("Could not find text prompt view to hide!!!") - return - } - view.hide(andErase: andEraseText) - } - } -} - -private extension LuminaController { //MARK: Button Tap Methods - @objc func cameraSwitchButtonTapped() { - if let cameraSwitchButton = self.cameraSwitchButton { - cameraSwitchButton.isEnabled = false - if let session = self.session { - session.stopRunning() - } - switch self.currentCameraDirection { - case .front: - commitSession(for: .back) - break - case .back: - commitSession(for: .front) - break - case .telephoto: - commitSession(for: .front) - break - case .dual: - commitSession(for: .front) - break - } - } else { - print("camera switch button not found!!!") - } - } - - @objc func cameraCancelButtonTapped() { - if let session = self.session { - session.stopRunning() - } - if let delegate = self.delegate { - delegate.cancelled(camera: self) - } - } - - @objc func cameraTorchButtonTapped() { - guard let input = self.input else { - print("Trying to update torch, but cannot detect device input!") - return - } - if self.torchOn == false { - do { - if input.device.isTorchModeSupported(.on) { - try input.device.lockForConfiguration() - try input.device.setTorchModeOn(level: 1.0) - self.torchOn = !self.torchOn - input.device.unlockForConfiguration() - } - } catch { - print("Could not turn torch on!!") - } - } else { - do { - if input.device.isTorchModeSupported(.off) { - try input.device.lockForConfiguration() - input.device.torchMode = .off - self.torchOn = !self.torchOn - input.device.unlockForConfiguration() - } - } catch { - print("Could not turn torch off!!") - } - } - } -} - -private extension CMSampleBuffer { // MARK: Extending CMSampleBuffer - var imageFromCoreImage: CGImage? { - guard let imageBuffer = CMSampleBufferGetImageBuffer(self) else { - print("Could not get image buffer from CMSampleBuffer") - return nil - } - let coreImage: CIImage = CIImage(cvPixelBuffer: imageBuffer) - let context: CIContext = CIContext() - guard let sample: CGImage = context.createCGImage(coreImage, from: coreImage.extent) else { - print("Could not create CoreGraphics image from context") - return nil - } - return sample - } - - var imageFromPixelBuffer: CGImage? { - guard let imageBuffer = CMSampleBufferGetImageBuffer(self) else { - print("Could not get image buffer from CMSampleBuffer") - return nil - } - if CVPixelBufferLockBaseAddress(imageBuffer, CVPixelBufferLockFlags(rawValue: 0)) == kCVReturnSuccess { - var colorSpace = CGColorSpaceCreateDeviceRGB() - var bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.noneSkipFirst.rawValue | CGBitmapInfo.byteOrder32Little.rawValue) - var width = CVPixelBufferGetWidth(imageBuffer) - var height = CVPixelBufferGetHeight(imageBuffer) - var bitsPerComponent: size_t = 8 - var bytesPerRow = CVPixelBufferGetBytesPerRow(imageBuffer) - var baseAddress = CVPixelBufferGetBaseAddress(imageBuffer) - - let format = CVPixelBufferGetPixelFormatType(imageBuffer) - if format == kCVPixelFormatType_420YpCbCr8BiPlanarFullRange { - baseAddress = CVPixelBufferGetBaseAddressOfPlane(imageBuffer, 0) - width = CVPixelBufferGetWidthOfPlane(imageBuffer, 0) - height = CVPixelBufferGetHeightOfPlane(imageBuffer, 0) - bitsPerComponent = 1 - bytesPerRow = CVPixelBufferGetBytesPerRowOfPlane(imageBuffer, 0) - colorSpace = CGColorSpaceCreateDeviceGray() - bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.none.rawValue | CGBitmapInfo.byteOrder32Big.rawValue) - } - - let context = CGContext(data: baseAddress, width: width, height: height, bitsPerComponent: bitsPerComponent, bytesPerRow: bytesPerRow, space: colorSpace, bitmapInfo: bitmapInfo.rawValue) - if let context = context { - guard let sample = context.makeImage() else { - print("Could not create CoreGraphics image from context") - return nil - } - CVPixelBufferUnlockBaseAddress(imageBuffer, CVPixelBufferLockFlags(rawValue: 0)) - return sample - } else { - print("Could not create CoreGraphics context") - return nil - } - } else { - print("Could not lock base address for pixel buffer") - return nil - } - } -} - -extension LuminaController: AVCaptureVideoDataOutputSampleBufferDelegate { // MARK: Image Tracking Output - public func captureOutput(_ captureOutput: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) { - guard case self.trackImages = true else { - return - } - guard let delegate = self.delegate else { - print("Warning!! No delegate set, but image tracking turned on") - return - } - -// guard let sampleBuffer = sampleBuffer else { -// print("No sample buffer detected") -// return -// } - let startTime = Date() - var sample: CGImage? = nil - if self.improvedImageDetectionPerformance { - sample = sampleBuffer.imageFromPixelBuffer - } else { - sample = sampleBuffer.imageFromCoreImage - } - guard let completedSample = sample else { - return - } - let orientation: UIImageOrientation = self.currentCameraDirection == .front ? .left : .right - let image = UIImage(cgImage: completedSample, scale: 1.0, orientation: orientation).fixOrientation - let end = Date() - print("Image tracking processing time: \(end.timeIntervalSince(startTime))") - delegate.detected(camera: self, image: image) - } -} - -extension LuminaController { // MARK: Tap to focus methods - override public func touchesBegan(_ touches: Set, with event: UIEvent?) { - if self.isUpdating == true { - return - } else { - self.isUpdating = true - } - for touch in touches { - let point = touch.location(in: touch.view) - let focusX = point.x/UIScreen.main.bounds.size.width - let focusY = point.y/UIScreen.main.bounds.size.height - guard let input = self.input else { - print("Trying to focus, but cannot detect device input!") - return - } - do { - if input.device.isFocusModeSupported(.autoFocus) && input.device.isFocusPointOfInterestSupported { - try input.device.lockForConfiguration() - input.device.focusMode = .autoFocus - input.device.focusPointOfInterest = CGPoint(x: focusX, y: focusY) - if input.device.isExposureModeSupported(.autoExpose) && input.device.isExposurePointOfInterestSupported { - input.device.exposureMode = .autoExpose - input.device.exposurePointOfInterest = CGPoint(x: focusX, y: focusY) - } - input.device.unlockForConfiguration() - showFocusView(at: point) - let deadlineTime = DispatchTime.now() + .seconds(1) - DispatchQueue.main.asyncAfter(deadline: deadlineTime) { - self.resetCameraToContinuousExposureAndFocus() - } - } else { - self.isUpdating = false - } - } catch { - print("could not lock for configuration! Not able to focus") - self.isUpdating = false - } - } - } - - func resetCameraToContinuousExposureAndFocus() { - do { - guard let input = self.input else { - print("Trying to focus, but cannot detect device input!") - return - } - if input.device.isFocusModeSupported(.continuousAutoFocus) { - try input.device.lockForConfiguration() - input.device.focusMode = .continuousAutoFocus - if input.device.isExposureModeSupported(.continuousAutoExposure) { - input.device.exposureMode = .continuousAutoExposure - } - input.device.unlockForConfiguration() - } - } catch { - print("could not reset to continuous auto focus and exposure!!") - } - } - - func showFocusView(at: CGPoint) { - let focusView: UIImageView = UIImageView(image: UIImage(named: "cameraFocus", in: Bundle(for: LuminaController.self), compatibleWith: nil)) - focusView.contentMode = .scaleAspectFit - focusView.frame = CGRect(x: 0, y: 0, width: 50, height: 50) - focusView.center = at - focusView.alpha = 0.0 - self.view.addSubview(focusView) - UIView.animate(withDuration: 0.2, animations: { - focusView.alpha = 1.0 - }, completion: { complete in - UIView.animate(withDuration: 1.0, animations: { - focusView.alpha = 0.0 - }, completion: { final in - focusView.removeFromSuperview() - self.isUpdating = false - }) - }) - } -} - -extension LuminaController: AVCaptureMetadataOutputObjectsDelegate { // MARK: Metadata output buffer - public func metadataOutput(_ captureOutput: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) { - guard case self.trackMetadata = true else { - return - } - guard let delegate = self.delegate else { - return - } - defer { - delegate.detected(camera: self, data: metadataObjects) - } - if self.drawMetadataBorders == true { - guard let previewLayer = self.previewLayer else { - return - } - guard let firstObject = metadataObjects.first else { - return - } - if let _: AVMetadataMachineReadableCodeObject = previewLayer.transformedMetadataObject(for: firstObject ) as? AVMetadataMachineReadableCodeObject { // TODO: Figure out exactly why Faces and Barcodes fire this method separately - if let oldBorders = self.metadataBordersCodes { - for oldBorder in oldBorders { - DispatchQueue.main.async { - oldBorder.removeFromSuperview() - } - } - } - self.metadataBordersCodes = nil - var newBorders = [LuminaMetadataBorderView]() - - for metadata in metadataObjects { - guard let transformed: AVMetadataMachineReadableCodeObject = previewLayer.transformedMetadataObject(for: metadata ) as? AVMetadataMachineReadableCodeObject else { - continue - } - var border = LuminaMetadataBorderView() - border.isHidden = true - border.frame = transformed.bounds - - let translatedCorners = translate(points: transformed.corners, fromView: self.view, toView: border) - border = LuminaMetadataBorderView(frame: transformed.bounds, corners: translatedCorners) - border.isHidden = false - newBorders.append(border) - DispatchQueue.main.async { - self.view.addSubview(border) - } - } - DispatchQueue.main.async { - self.drawingTimerCodes() - } - self.metadataBordersCodes = newBorders - } else { - if let oldBorders = self.metadataBordersFaces { - for oldBorder in oldBorders { - DispatchQueue.main.async { - oldBorder.removeFromSuperview() - } - } - } - self.metadataBordersFaces = nil - var newBorders = [LuminaMetadataBorderView]() - - for metadata in metadataObjects { - guard let face: AVMetadataFaceObject = previewLayer.transformedMetadataObject(for: metadata ) as? AVMetadataFaceObject else { - continue - } - let border = LuminaMetadataBorderView(frame: face.bounds) - border.boundsFace = true - newBorders.append(border) - DispatchQueue.main.async { - self.view.addSubview(border) - } - } - DispatchQueue.main.async { - self.drawingTimerFaces() - } - self.metadataBordersFaces = newBorders - } - } - } - - private func translate(points: [CGPoint], fromView: UIView, toView: UIView) -> [CGPoint] { - var translatedPoints = [CGPoint]() - for point in points { - let currentPoint = CGPoint(x: point.x, y: point.y) //CGPoint(x: point["X"] as! Double, y: point["Y"] as! Double) - let translatedPoint = fromView.convert(currentPoint, to: toView) - translatedPoints.append(translatedPoint) - } - return translatedPoints - } - - private func drawingTimerCodes() { - DispatchQueue.main.async { - if let _ = self.metadataBordersCodesDestructionTimer { - self.metadataBordersCodesDestructionTimer!.invalidate() - } - self.metadataBordersCodesDestructionTimer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(self.removeAllBordersCodes), userInfo: nil, repeats: false) - } - } - - private func drawingTimerFaces() { - DispatchQueue.main.async { - if let _ = self.metadataBordersFacesDestructionTimer { - self.metadataBordersFacesDestructionTimer!.invalidate() - } - self.metadataBordersFacesDestructionTimer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(self.removeAllBordersFaces), userInfo: nil, repeats: false) - } - } - - @objc private func removeAllBordersCodes() { - DispatchQueue.main.async { - for subview in self.view.subviews { - if let border = subview as? LuminaMetadataBorderView, border.boundsFace == false { - border.removeFromSuperview() - } - } - self.metadataBordersCodes = nil - } - } - - @objc private func removeAllBordersFaces() { - DispatchQueue.main.async { - for subview in self.view.subviews { - if let border = subview as? LuminaMetadataBorderView, border.boundsFace == true { - border.removeFromSuperview() - } - } - self.metadataBordersFaces = nil - } - } -} - -private extension UIImage { // MARK: Fix UIImage orientation - var fixOrientation: UIImage { - if imageOrientation == UIImageOrientation.up { - return self - } - - var transform: CGAffineTransform = CGAffineTransform.identity - - switch imageOrientation { - case UIImageOrientation.down, UIImageOrientation.downMirrored: - transform = transform.translatedBy(x: size.width, y: size.height) - transform = transform.rotated(by: CGFloat.pi) - break - case UIImageOrientation.left, UIImageOrientation.leftMirrored: - transform = transform.translatedBy(x: size.width, y: 0) - transform = transform.rotated(by: CGFloat.pi / 2) - break - case UIImageOrientation.right, UIImageOrientation.rightMirrored: - transform = transform.translatedBy(x: 0, y: size.height) - transform = transform.rotated(by: CGFloat.pi / -2) - break - case UIImageOrientation.up, UIImageOrientation.upMirrored: - break - } - - switch imageOrientation { - case UIImageOrientation.upMirrored, UIImageOrientation.downMirrored: - transform.translatedBy(x: size.width, y: 0) - transform.scaledBy(x: -1, y: 1) - break - case UIImageOrientation.leftMirrored, UIImageOrientation.rightMirrored: - transform.translatedBy(x: size.height, y: 0) - transform.scaledBy(x: -1, y: 1) - case UIImageOrientation.up, UIImageOrientation.down, UIImageOrientation.left, UIImageOrientation.right: - break - } - - guard let cgImage = self.cgImage, let colorspace = cgImage.colorSpace else { - return self - } - - guard let ctx: CGContext = CGContext(data: nil, width: Int(size.width), height: Int(size.height), bitsPerComponent: cgImage.bitsPerComponent, bytesPerRow: 0, space: colorspace, bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue) else { - return self - } - - ctx.concatenate(transform) - - switch imageOrientation { - case UIImageOrientation.left, UIImageOrientation.leftMirrored, UIImageOrientation.right, UIImageOrientation.rightMirrored: - ctx.draw(cgImage, in: CGRect(x: 0, y: 0, width: size.height, height: size.width)) - default: - ctx.draw(cgImage, in: CGRect(x: 0, y: 0, width: size.width, height: size.height)) - break - } - - if let convertedCGImage = ctx.makeImage() { - return UIImage(cgImage: convertedCGImage) - } else { - return self - } - } -} diff --git a/Lumina/Lumina/LuminaViewController.swift b/Lumina/Lumina/LuminaViewController.swift new file mode 100644 index 0000000..127958b --- /dev/null +++ b/Lumina/Lumina/LuminaViewController.swift @@ -0,0 +1,748 @@ +// +// LuminaController.swift +// Lumina +// +// Created by David Okun IBM on 4/21/17. +// Copyright © 2017 David Okun. All rights reserved. +// + +import Foundation +import AVFoundation + +public protocol LuminaDelegate { + func detected(camera: LuminaViewController, image: UIImage) + func detected(camera: LuminaViewController, data: [Any]) + func cancelled(camera: LuminaViewController) +} + +public enum CameraDirection: String { + case front = "Front" + case back = "Back" + case telephoto = "Telephoto" + case dual = "Dual" + case unspecified = "Unspecified" +} + +public final class LuminaViewController: UIViewController { + private var sessionPreset: String? + + fileprivate var previewLayer: AVCaptureVideoPreviewLayer? + private var previewView: UIView? + + private var metadataOutput: AVCaptureMetadataOutput? + private var videoBufferQueue = DispatchQueue(label: "com.lumina.videoBufferQueue") + private var metadataBufferQueue = DispatchQueue(label: "com.lumina.metadataBufferQueue") + + fileprivate var input: AVCaptureDeviceInput? + + fileprivate var videoOutput: AVCaptureVideoDataOutput { + let videoOutput = AVCaptureVideoDataOutput() + videoOutput.alwaysDiscardsLateVideoFrames = true + videoOutput.videoSettings = [kCVPixelBufferPixelFormatTypeKey as AnyHashable as! String : kCVPixelFormatType_32BGRA] + //videoOutput.setSampleBufferDelegate(self, queue: videoBufferQueue) + return videoOutput + } + + fileprivate var textPromptView: LuminaTextPromptView? + fileprivate var initialPrompt: String? + + fileprivate var session: AVCaptureSession? + + private var _cameraSwitchButton: LuminaButton? + fileprivate var cameraSwitchButton: LuminaButton { + get { + if let switchButton = _cameraSwitchButton { + return switchButton + } + let button = LuminaButton(with: .cameraSwitch) + button.addTarget(self, action: #selector(cameraSwitchButtonTapped), for: UIControlEvents.touchUpInside) + _cameraSwitchButton = button + return button + } + } + + private var _cameraCancelButton: LuminaButton? + fileprivate var cameraCancelButton: LuminaButton { + get { + if let cancelButton = _cameraCancelButton { + return cancelButton + } + let button = LuminaButton(with: .cancel) + button.addTarget(self, action: #selector(cameraCancelButtonTapped), for: UIControlEvents.touchUpInside) + _cameraCancelButton = button + return button + } + } + + private var _cameraTorchButton: LuminaButton? + fileprivate var cameraTorchButton: UIButton { + get { + if let torchButton = _cameraTorchButton { + return torchButton + } + let button = LuminaButton(with: .torch) + button.addTarget(self, action: #selector(cameraTorchButtonTapped), for: UIControlEvents.touchUpInside) + _cameraTorchButton = button + return button + } + } + + fileprivate var currentCameraDirection: CameraDirection = .back + fileprivate var isUpdating = false + fileprivate var torchOn = false + fileprivate var metadataBordersCodes: [LuminaMetadataBorderView]? + fileprivate var metadataBordersFaces: [LuminaMetadataBorderView]? + fileprivate var metadataBordersCodesDestructionTimer: Timer? + fileprivate var metadataBordersFacesDestructionTimer: Timer? + fileprivate var camera: LuminaCamera? + + public var delegate: LuminaDelegate! = nil + public var trackImages = false + public var trackMetadata = false + public var improvedImageDetectionPerformance = false + public var drawMetadataBorders = false + + open var position: CameraDirection = .unspecified { + didSet { + guard let camera = self.camera else { + return + } + if position == .unspecified { + self.position = .back + } else { + camera.position = position + } + } + } + + override public var prefersStatusBarHidden: Bool { + return true + } + +// private var discoverySession: AVCaptureDevice.DiscoverySession? { +// var deviceTypes: [AVCaptureDevice.DeviceType] = [] +// deviceTypes.append(AVCaptureDevice.DeviceType.builtInWideAngleCamera) +// if #available(iOS 10.2, *) { +// deviceTypes.append(AVCaptureDevice.DeviceType.builtInDualCamera) +// deviceTypes.append(AVCaptureDevice.DeviceType.builtInTelephotoCamera) +// } +// let discoverySession = AVCaptureDevice.DiscoverySession(deviceTypes: deviceTypes, mediaType: AVMediaType.video, position: AVCaptureDevice.Position.unspecified) +// +// return discoverySession +// } + +// private func getDevice(for cameraDirection: CameraDirection) -> AVCaptureDevice? { +// var device: AVCaptureDevice? +// guard let discoverySession = self.discoverySession else { +// print("Could not get discovery session") +// return nil +// } +// for discoveryDevice: AVCaptureDevice in discoverySession.devices { +// if cameraDirection == .front { +// if discoveryDevice.position == AVCaptureDevice.Position.front { +// device = discoveryDevice +// break +// } +// } else { +// if discoveryDevice.position == AVCaptureDevice.Position.back { // TODO: add support for iPhone 7 plus dual cameras +// device = discoveryDevice +// break +// } +// } +// } +// return device +// } + +// public init?(camera: CameraDirection) { +// super.init(nibName: nil, bundle: nil) +// +// let session = AVCaptureSession() +// self.previewLayer = AVCaptureVideoPreviewLayer(session: session) +// self.previewView = self.view +// +// guard let previewLayer = self.previewLayer else { +// print("Could not access image preview layer") +// return +// } +// previewLayer.videoGravity = AVLayerVideoGravity.resizeAspectFill +// self.view.layer.addSublayer(previewLayer) +// self.view.bounds = UIScreen.main.bounds +// +// previewLayer.frame = self.view.bounds +// self.session = session +// commitSession(for: camera) +// createUI() +// createTextPromptView() +// } + + public init() { + super.init(nibName: nil, bundle: nil) + self.camera = LuminaCamera(with: self) + } + + public override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + if let camera = self.camera { + createUI() + do { + try camera.update() + } + catch { + print("could not update camera") + } + } + } + +// fileprivate func commitSession(for desiredCameraDirection: CameraDirection) { +// guard let session = self.session else { +// print("Error getting session") +// return +// } +// self.currentCameraDirection = desiredCameraDirection +// +// session.sessionPreset = AVCaptureSession.Preset.high +// +// if let input = self.input { +// session.removeInput(input) +// } +// +// do { +// guard let device = getDevice(for: desiredCameraDirection) else { +// print("could not get desired camera direction") +// return +// } +// try input = AVCaptureDeviceInput(device: device) +// if session.canAddInput(input!) { +// session.addInput(input!) +// self.input = input! +// } +// } catch { +// print("Error getting device input for \(desiredCameraDirection.rawValue)") +// return +// } +// +//// let metadataOutput = AVCaptureMetadataOutput() +// +// let videoOutput = self.videoOutput +// +// if session.canAddOutput(videoOutput) { +// session.addOutput(videoOutput) +// } +// +//// if session.canAddOutput(metadataOutput) { +//// session.addOutput(metadataOutput) +//// } +//// metadataOutput.setMetadataObjectsDelegate(self, queue: metadataBufferQueue) +//// metadataOutput.metadataObjectTypes = metadataOutput.availableMetadataObjectTypes +//// +//// self.metadataOutput = metadataOutput +// +// session.commitConfiguration() +// session.startRunning() +// +// if let connection = videoOutput.connection(with: AVMediaType.video) { +// connection.isEnabled = true +// if connection.isVideoMirroringSupported && desiredCameraDirection == .front { +// connection.isVideoMirrored = true +// connection.preferredVideoStabilizationMode = .standard +// } +// } +// +// guard let cameraSwitchButton = self.cameraSwitchButton else { +// print("Could not create camera switch button") +// return +// } +// cameraSwitchButton.isEnabled = true +// +// } + + private func createUI() { + guard let camera = self.camera else { + return + } + guard let previewLayer = camera.previewLayer else { + return + } + self.previewLayer = previewLayer + self.view.layer.addSublayer(previewLayer) + + self.view.addSubview(self.cameraSwitchButton) + + self.view.addSubview(self.cameraSwitchButton) + self.view.addSubview(self.cameraCancelButton) + self.view.addSubview(self.cameraTorchButton) + } + + public required init?(coder aDecoder: NSCoder) { + return nil + } +} + +extension LuminaViewController: LuminaCameraDelegate { + func finishedUpdating() { + self.cameraTorchButton.isEnabled = true + self.cameraCancelButton.isEnabled = true + self.cameraSwitchButton.isEnabled = true + } +} + +extension LuminaViewController { // MARK: Text prompt methods + fileprivate func createTextPromptView() { + let view = LuminaTextPromptView(frame: CGRect(origin: CGPoint(x: self.view.bounds.minX + 10, y: self.view.bounds.minY + 70), size: CGSize(width: self.view.bounds.size.width - 20, height: 80))) + if let prompt = self.initialPrompt { + view.updateText(to: prompt) + } + self.view.addSubview(view) + self.textPromptView = view + } + + public func updateTextPromptView(to text:String) { + DispatchQueue.main.async { + guard let view = self.textPromptView else { + print("No text prompt view to update!!") + return + } + view.updateText(to: text) + } + } + + public func hideTextPromptView(andEraseText: Bool) { + DispatchQueue.main.async { + guard let view = self.textPromptView else { + print("Could not find text prompt view to hide!!!") + return + } + view.hide(andErase: andEraseText) + } + } +} + +private extension LuminaViewController { //MARK: Button Tap Methods + @objc func cameraSwitchButtonTapped() { + if let camera = self.camera { + switch self.position { + case .front: + self.position = .back + break + case .back: + self.position = .front + break + case .telephoto: + self.position = .front + break + case .dual: + self.position = .front + break + case .unspecified: + self.position = .back + break + } + do { + try camera.update() + } catch { + print("camera update error while tapping camera switch button") + } + } + } + + @objc func cameraCancelButtonTapped() { + if let delegate = self.delegate { + delegate.cancelled(camera: self) + } + } + + @objc func cameraTorchButtonTapped() { + // TODO: Update for new camera arch +// guard let input = self.input else { +// print("Trying to update torch, but cannot detect device input!") +// return +// } +// if self.torchOn == false { +// do { +// if input.device.isTorchModeSupported(.on) { +// try input.device.lockForConfiguration() +// try input.device.setTorchModeOn(level: 1.0) +// self.torchOn = !self.torchOn +// input.device.unlockForConfiguration() +// } +// } catch { +// print("Could not turn torch on!!") +// } +// } else { +// do { +// if input.device.isTorchModeSupported(.off) { +// try input.device.lockForConfiguration() +// input.device.torchMode = .off +// self.torchOn = !self.torchOn +// input.device.unlockForConfiguration() +// } +// } catch { +// print("Could not turn torch off!!") +// } +// } + } +} + +//private extension CMSampleBuffer { // MARK: Extending CMSampleBuffer +// var imageFromCoreImage: CGImage? { +// guard let imageBuffer = CMSampleBufferGetImageBuffer(self) else { +// print("Could not get image buffer from CMSampleBuffer") +// return nil +// } +// let coreImage: CIImage = CIImage(cvPixelBuffer: imageBuffer) +// let context: CIContext = CIContext() +// guard let sample: CGImage = context.createCGImage(coreImage, from: coreImage.extent) else { +// print("Could not create CoreGraphics image from context") +// return nil +// } +// return sample +// } +// +// var imageFromPixelBuffer: CGImage? { +// guard let imageBuffer = CMSampleBufferGetImageBuffer(self) else { +// print("Could not get image buffer from CMSampleBuffer") +// return nil +// } +// if CVPixelBufferLockBaseAddress(imageBuffer, CVPixelBufferLockFlags(rawValue: 0)) == kCVReturnSuccess { +// var colorSpace = CGColorSpaceCreateDeviceRGB() +// var bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.noneSkipFirst.rawValue | CGBitmapInfo.byteOrder32Little.rawValue) +// var width = CVPixelBufferGetWidth(imageBuffer) +// var height = CVPixelBufferGetHeight(imageBuffer) +// var bitsPerComponent: size_t = 8 +// var bytesPerRow = CVPixelBufferGetBytesPerRow(imageBuffer) +// var baseAddress = CVPixelBufferGetBaseAddress(imageBuffer) +// +// let format = CVPixelBufferGetPixelFormatType(imageBuffer) +// if format == kCVPixelFormatType_420YpCbCr8BiPlanarFullRange { +// baseAddress = CVPixelBufferGetBaseAddressOfPlane(imageBuffer, 0) +// width = CVPixelBufferGetWidthOfPlane(imageBuffer, 0) +// height = CVPixelBufferGetHeightOfPlane(imageBuffer, 0) +// bitsPerComponent = 1 +// bytesPerRow = CVPixelBufferGetBytesPerRowOfPlane(imageBuffer, 0) +// colorSpace = CGColorSpaceCreateDeviceGray() +// bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.none.rawValue | CGBitmapInfo.byteOrder32Big.rawValue) +// } +// +// let context = CGContext(data: baseAddress, width: width, height: height, bitsPerComponent: bitsPerComponent, bytesPerRow: bytesPerRow, space: colorSpace, bitmapInfo: bitmapInfo.rawValue) +// if let context = context { +// guard let sample = context.makeImage() else { +// print("Could not create CoreGraphics image from context") +// return nil +// } +// CVPixelBufferUnlockBaseAddress(imageBuffer, CVPixelBufferLockFlags(rawValue: 0)) +// return sample +// } else { +// print("Could not create CoreGraphics context") +// return nil +// } +// } else { +// print("Could not lock base address for pixel buffer") +// return nil +// } +// } +//} + +//extension LuminaViewController: AVCaptureVideoDataOutputSampleBufferDelegate { // MARK: Image Tracking Output +// public func captureOutput(_ captureOutput: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) { +// guard case self.trackImages = true else { +// return +// } +// guard let delegate = self.delegate else { +// print("Warning!! No delegate set, but image tracking turned on") +// return +// } +// +//// guard let sampleBuffer = sampleBuffer else { +//// print("No sample buffer detected") +//// return +//// } +// let startTime = Date() +// var sample: CGImage? = nil +// if self.improvedImageDetectionPerformance { +// sample = sampleBuffer.imageFromPixelBuffer +// } else { +// sample = sampleBuffer.imageFromCoreImage +// } +// guard let completedSample = sample else { +// return +// } +// let orientation: UIImageOrientation = self.currentCameraDirection == .front ? .left : .right +// let image = UIImage(cgImage: completedSample, scale: 1.0, orientation: orientation).fixOrientation +// let end = Date() +// print("Image tracking processing time: \(end.timeIntervalSince(startTime))") +// delegate.detected(camera: self, image: image) +// } +//} + +//extension LuminaViewController { // MARK: Tap to focus methods +// override public func touchesBegan(_ touches: Set, with event: UIEvent?) { +// if self.isUpdating == true { +// return +// } else { +// self.isUpdating = true +// } +// for touch in touches { +// let point = touch.location(in: touch.view) +// let focusX = point.x/UIScreen.main.bounds.size.width +// let focusY = point.y/UIScreen.main.bounds.size.height +// guard let input = self.input else { +// print("Trying to focus, but cannot detect device input!") +// return +// } +// do { +// if input.device.isFocusModeSupported(.autoFocus) && input.device.isFocusPointOfInterestSupported { +// try input.device.lockForConfiguration() +// input.device.focusMode = .autoFocus +// input.device.focusPointOfInterest = CGPoint(x: focusX, y: focusY) +// if input.device.isExposureModeSupported(.autoExpose) && input.device.isExposurePointOfInterestSupported { +// input.device.exposureMode = .autoExpose +// input.device.exposurePointOfInterest = CGPoint(x: focusX, y: focusY) +// } +// input.device.unlockForConfiguration() +// showFocusView(at: point) +// let deadlineTime = DispatchTime.now() + .seconds(1) +// DispatchQueue.main.asyncAfter(deadline: deadlineTime) { +// self.resetCameraToContinuousExposureAndFocus() +// } +// } else { +// self.isUpdating = false +// } +// } catch { +// print("could not lock for configuration! Not able to focus") +// self.isUpdating = false +// } +// } +// } +// +// func resetCameraToContinuousExposureAndFocus() { +// do { +// guard let input = self.input else { +// print("Trying to focus, but cannot detect device input!") +// return +// } +// if input.device.isFocusModeSupported(.continuousAutoFocus) { +// try input.device.lockForConfiguration() +// input.device.focusMode = .continuousAutoFocus +// if input.device.isExposureModeSupported(.continuousAutoExposure) { +// input.device.exposureMode = .continuousAutoExposure +// } +// input.device.unlockForConfiguration() +// } +// } catch { +// print("could not reset to continuous auto focus and exposure!!") +// } +// } +// +// func showFocusView(at: CGPoint) { +// let focusView: UIImageView = UIImageView(image: UIImage(named: "cameraFocus", in: Bundle(for: LuminaViewController.self), compatibleWith: nil)) +// focusView.contentMode = .scaleAspectFit +// focusView.frame = CGRect(x: 0, y: 0, width: 50, height: 50) +// focusView.center = at +// focusView.alpha = 0.0 +// self.view.addSubview(focusView) +// UIView.animate(withDuration: 0.2, animations: { +// focusView.alpha = 1.0 +// }, completion: { complete in +// UIView.animate(withDuration: 1.0, animations: { +// focusView.alpha = 0.0 +// }, completion: { final in +// focusView.removeFromSuperview() +// self.isUpdating = false +// }) +// }) +// } +//} + +//extension LuminaViewController: AVCaptureMetadataOutputObjectsDelegate { // MARK: Metadata output buffer +// public func metadataOutput(_ captureOutput: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) { +// guard case self.trackMetadata = true else { +// return +// } +// guard let delegate = self.delegate else { +// return +// } +// defer { +// delegate.detected(camera: self, data: metadataObjects) +// } +// if self.drawMetadataBorders == true { +// guard let previewLayer = self.previewLayer else { +// return +// } +// guard let firstObject = metadataObjects.first else { +// return +// } +// if let _: AVMetadataMachineReadableCodeObject = previewLayer.transformedMetadataObject(for: firstObject ) as? AVMetadataMachineReadableCodeObject { // TODO: Figure out exactly why Faces and Barcodes fire this method separately +// if let oldBorders = self.metadataBordersCodes { +// for oldBorder in oldBorders { +// DispatchQueue.main.async { +// oldBorder.removeFromSuperview() +// } +// } +// } +// self.metadataBordersCodes = nil +// var newBorders = [LuminaMetadataBorderView]() +// +// for metadata in metadataObjects { +// guard let transformed: AVMetadataMachineReadableCodeObject = previewLayer.transformedMetadataObject(for: metadata ) as? AVMetadataMachineReadableCodeObject else { +// continue +// } +// var border = LuminaMetadataBorderView() +// border.isHidden = true +// border.frame = transformed.bounds +// +// let translatedCorners = translate(points: transformed.corners, fromView: self.view, toView: border) +// border = LuminaMetadataBorderView(frame: transformed.bounds, corners: translatedCorners) +// border.isHidden = false +// newBorders.append(border) +// DispatchQueue.main.async { +// self.view.addSubview(border) +// } +// } +// DispatchQueue.main.async { +// self.drawingTimerCodes() +// } +// self.metadataBordersCodes = newBorders +// } else { +// if let oldBorders = self.metadataBordersFaces { +// for oldBorder in oldBorders { +// DispatchQueue.main.async { +// oldBorder.removeFromSuperview() +// } +// } +// } +// self.metadataBordersFaces = nil +// var newBorders = [LuminaMetadataBorderView]() +// +// for metadata in metadataObjects { +// guard let face: AVMetadataFaceObject = previewLayer.transformedMetadataObject(for: metadata ) as? AVMetadataFaceObject else { +// continue +// } +// let border = LuminaMetadataBorderView(frame: face.bounds) +// border.boundsFace = true +// newBorders.append(border) +// DispatchQueue.main.async { +// self.view.addSubview(border) +// } +// } +// DispatchQueue.main.async { +// self.drawingTimerFaces() +// } +// self.metadataBordersFaces = newBorders +// } +// } +// } +// +// private func translate(points: [CGPoint], fromView: UIView, toView: UIView) -> [CGPoint] { +// var translatedPoints = [CGPoint]() +// for point in points { +// let currentPoint = CGPoint(x: point.x, y: point.y) //CGPoint(x: point["X"] as! Double, y: point["Y"] as! Double) +// let translatedPoint = fromView.convert(currentPoint, to: toView) +// translatedPoints.append(translatedPoint) +// } +// return translatedPoints +// } +// +// private func drawingTimerCodes() { +// DispatchQueue.main.async { +// if let _ = self.metadataBordersCodesDestructionTimer { +// self.metadataBordersCodesDestructionTimer!.invalidate() +// } +// self.metadataBordersCodesDestructionTimer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(self.removeAllBordersCodes), userInfo: nil, repeats: false) +// } +// } +// +// private func drawingTimerFaces() { +// DispatchQueue.main.async { +// if let _ = self.metadataBordersFacesDestructionTimer { +// self.metadataBordersFacesDestructionTimer!.invalidate() +// } +// self.metadataBordersFacesDestructionTimer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(self.removeAllBordersFaces), userInfo: nil, repeats: false) +// } +// } +// +// @objc private func removeAllBordersCodes() { +// DispatchQueue.main.async { +// for subview in self.view.subviews { +// if let border = subview as? LuminaMetadataBorderView, border.boundsFace == false { +// border.removeFromSuperview() +// } +// } +// self.metadataBordersCodes = nil +// } +// } +// +// @objc private func removeAllBordersFaces() { +// DispatchQueue.main.async { +// for subview in self.view.subviews { +// if let border = subview as? LuminaMetadataBorderView, border.boundsFace == true { +// border.removeFromSuperview() +// } +// } +// self.metadataBordersFaces = nil +// } +// } +//} +// +//private extension UIImage { // MARK: Fix UIImage orientation +// var fixOrientation: UIImage { +// if imageOrientation == UIImageOrientation.up { +// return self +// } +// +// var transform: CGAffineTransform = CGAffineTransform.identity +// +// switch imageOrientation { +// case UIImageOrientation.down, UIImageOrientation.downMirrored: +// transform = transform.translatedBy(x: size.width, y: size.height) +// transform = transform.rotated(by: CGFloat.pi) +// break +// case UIImageOrientation.left, UIImageOrientation.leftMirrored: +// transform = transform.translatedBy(x: size.width, y: 0) +// transform = transform.rotated(by: CGFloat.pi / 2) +// break +// case UIImageOrientation.right, UIImageOrientation.rightMirrored: +// transform = transform.translatedBy(x: 0, y: size.height) +// transform = transform.rotated(by: CGFloat.pi / -2) +// break +// case UIImageOrientation.up, UIImageOrientation.upMirrored: +// break +// } +// +// switch imageOrientation { +// case UIImageOrientation.upMirrored, UIImageOrientation.downMirrored: +// transform.translatedBy(x: size.width, y: 0) +// transform.scaledBy(x: -1, y: 1) +// break +// case UIImageOrientation.leftMirrored, UIImageOrientation.rightMirrored: +// transform.translatedBy(x: size.height, y: 0) +// transform.scaledBy(x: -1, y: 1) +// case UIImageOrientation.up, UIImageOrientation.down, UIImageOrientation.left, UIImageOrientation.right: +// break +// } +// +// guard let cgImage = self.cgImage, let colorspace = cgImage.colorSpace else { +// return self +// } +// +// guard let ctx: CGContext = CGContext(data: nil, width: Int(size.width), height: Int(size.height), bitsPerComponent: cgImage.bitsPerComponent, bytesPerRow: 0, space: colorspace, bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue) else { +// return self +// } +// +// ctx.concatenate(transform) +// +// switch imageOrientation { +// case UIImageOrientation.left, UIImageOrientation.leftMirrored, UIImageOrientation.right, UIImageOrientation.rightMirrored: +// ctx.draw(cgImage, in: CGRect(x: 0, y: 0, width: size.height, height: size.width)) +// default: +// ctx.draw(cgImage, in: CGRect(x: 0, y: 0, width: size.width, height: size.height)) +// break +// } +// +// if let convertedCGImage = ctx.makeImage() { +// return UIImage(cgImage: convertedCGImage) +// } else { +// return self +// } +// } +//} + diff --git a/LuminaSample/LuminaSample/ViewController.swift b/LuminaSample/LuminaSample/ViewController.swift index 53f438d..e931eeb 100644 --- a/LuminaSample/LuminaSample/ViewController.swift +++ b/LuminaSample/LuminaSample/ViewController.swift @@ -20,38 +20,39 @@ class ViewController: UITableViewController { extension ViewController { //MARK: IBActions @IBAction func cameraButtonTapped() { - let direction: CameraDirection = frontCameraSwitch.isOn ? .front : .back - let camera = LuminaController(camera: direction) - if showTextPromptViewSwitch.isOn { - camera!.updateTextPromptView(to: "I love Lumina, and I'm going to start using it everywhere!! Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah") - } - camera!.delegate = self - camera!.trackImages = trackImagesSwitch.isOn - camera!.trackMetadata = trackMetadataSwitch.isOn - camera!.improvedImageDetectionPerformance = increaseImagePerformanceSwitch.isOn - camera!.drawMetadataBorders = drawMetadataBorders.isOn - present(camera!, animated: true, completion: nil) - let deadline = DispatchTime.now() + .seconds(4) - DispatchQueue.main.asyncAfter(deadline: deadline) { - camera!.updateTextPromptView(to: "And here's what happens after you update the text view on the camera!!!") - let hideDeadline = DispatchTime.now() + .seconds(2) - DispatchQueue.main.asyncAfter(deadline: hideDeadline, execute: { - camera!.hideTextPromptView(andEraseText: true) - }) - } + + let camera = LuminaViewController() + +// if showTextPromptViewSwitch.isOn { +// camera.updateTextPromptView(to: "I love Lumina, and I'm going to start using it everywhere!! Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah") +// } + camera.delegate = self +// camera.trackImages = trackImagesSwitch.isOn +// camera.trackMetadata = trackMetadataSwitch.isOn +// camera.improvedImageDetectionPerformance = increaseImagePerformanceSwitch.isOn +// camera.drawMetadataBorders = drawMetadataBorders.isOn + present(camera, animated: true, completion: nil) +// let deadline = DispatchTime.now() + .seconds(4) +// DispatchQueue.main.asyncAfter(deadline: deadline) { +// camera.updateTextPromptView(to: "And here's what happens after you update the text view on the camera!!!") +// let hideDeadline = DispatchTime.now() + .seconds(2) +// DispatchQueue.main.asyncAfter(deadline: hideDeadline, execute: { +// camera.hideTextPromptView(andEraseText: true) +// }) +// } } } extension ViewController: LuminaDelegate { - func detected(camera: LuminaController, image: UIImage) { + func detected(camera: LuminaViewController, image: UIImage) { print("got an image") } - func detected(camera: LuminaController, data: [Any]) { + func detected(camera: LuminaViewController, data: [Any]) { print("detected data: \(data)") } - func cancelled(camera: LuminaController) { + func cancelled(camera: LuminaViewController) { camera.dismiss(animated: true, completion: nil) } } From 30a232c80126de1b81530082a464b504eccb4d0e Mon Sep 17 00:00:00 2001 From: David-Okun Date: Mon, 11 Sep 2017 21:30:31 -0500 Subject: [PATCH 2/7] Camera switch button now working correctly --- Lumina/Lumina/LuminaCamera.swift | 2 +- Lumina/Lumina/LuminaViewController.swift | 47 +++++++++++------------- 2 files changed, 22 insertions(+), 27 deletions(-) diff --git a/Lumina/Lumina/LuminaCamera.swift b/Lumina/Lumina/LuminaCamera.swift index fbf00cc..ae43707 100644 --- a/Lumina/Lumina/LuminaCamera.swift +++ b/Lumina/Lumina/LuminaCamera.swift @@ -15,7 +15,7 @@ protocol LuminaCameraDelegate { final class LuminaCamera: NSObject { var controller: LuminaViewController? - fileprivate var delegate: LuminaCameraDelegate? + internal var delegate: LuminaCameraDelegate? var position: CameraDirection = .unspecified { didSet { diff --git a/Lumina/Lumina/LuminaViewController.swift b/Lumina/Lumina/LuminaViewController.swift index 127958b..a418baa 100644 --- a/Lumina/Lumina/LuminaViewController.swift +++ b/Lumina/Lumina/LuminaViewController.swift @@ -87,7 +87,6 @@ public final class LuminaViewController: UIViewController { } } - fileprivate var currentCameraDirection: CameraDirection = .back fileprivate var isUpdating = false fileprivate var torchOn = false fileprivate var metadataBordersCodes: [LuminaMetadataBorderView]? @@ -177,7 +176,9 @@ public final class LuminaViewController: UIViewController { public init() { super.init(nibName: nil, bundle: nil) - self.camera = LuminaCamera(with: self) + let camera = LuminaCamera(with: self) + camera.delegate = self + self.camera = camera } public override func viewWillAppear(_ animated: Bool) { @@ -278,7 +279,7 @@ public final class LuminaViewController: UIViewController { } } -extension LuminaViewController: LuminaCameraDelegate { +extension LuminaViewController: LuminaCameraDelegate { // MARK: Camera Delegate Functions func finishedUpdating() { self.cameraTorchButton.isEnabled = true self.cameraCancelButton.isEnabled = true @@ -319,29 +320,23 @@ extension LuminaViewController { // MARK: Text prompt methods private extension LuminaViewController { //MARK: Button Tap Methods @objc func cameraSwitchButtonTapped() { - if let camera = self.camera { - switch self.position { - case .front: - self.position = .back - break - case .back: - self.position = .front - break - case .telephoto: - self.position = .front - break - case .dual: - self.position = .front - break - case .unspecified: - self.position = .back - break - } - do { - try camera.update() - } catch { - print("camera update error while tapping camera switch button") - } + self.cameraSwitchButton.isEnabled = false + switch self.position { + case .front: + self.position = .back + break + case .back: + self.position = .front + break + case .telephoto: + self.position = .front + break + case .dual: + self.position = .front + break + case .unspecified: + self.position = .front + break } } From aa1866d24aae5874d0ac7504cba6634516a53357 Mon Sep 17 00:00:00 2001 From: David-Okun Date: Mon, 18 Sep 2017 14:34:50 -0500 Subject: [PATCH 3/7] A lot of features working in the new way. --- Lumina/Lumina/LuminaButton.swift | 7 + Lumina/Lumina/LuminaCamera.swift | 235 ++++- Lumina/Lumina/LuminaTextPromptView.swift | 33 +- Lumina/Lumina/LuminaViewController.swift | 839 ++++-------------- .../cameraShutter.imageset/Contents.json | 23 + .../cameraShutter.imageset/trigger.png | Bin 0 -> 1705 bytes .../cameraShutter.imageset/trigger@2x-8.png | Bin 0 -> 3412 bytes .../cameraShutter.imageset/trigger@3x-8.png | Bin 0 -> 5286 bytes .../LuminaSample/Base.lproj/Main.storyboard | 44 +- LuminaSample/LuminaSample/Info.plist | 2 + .../LuminaSample/ViewController.swift | 35 +- 11 files changed, 444 insertions(+), 774 deletions(-) create mode 100644 Lumina/Lumina/Media.xcassets/cameraShutter.imageset/Contents.json create mode 100644 Lumina/Lumina/Media.xcassets/cameraShutter.imageset/trigger.png create mode 100644 Lumina/Lumina/Media.xcassets/cameraShutter.imageset/trigger@2x-8.png create mode 100644 Lumina/Lumina/Media.xcassets/cameraShutter.imageset/trigger@3x-8.png diff --git a/Lumina/Lumina/LuminaButton.swift b/Lumina/Lumina/LuminaButton.swift index 5573a24..159a1a5 100644 --- a/Lumina/Lumina/LuminaButton.swift +++ b/Lumina/Lumina/LuminaButton.swift @@ -13,6 +13,7 @@ enum SystemButtonType { case cameraSwitch case photoCapture case cancel + case shutter } final class LuminaButton: UIButton { @@ -20,6 +21,7 @@ final class LuminaButton: UIButton { private var squareSystemButtonHeight = 40 private var cancelButtonWidth = 70 private var cancelButtonHeight = 30 + private var shutterButtonDimension = 70 private var _image: UIImage? var image: UIImage? { @@ -71,6 +73,11 @@ final class LuminaButton: UIButton { case .cancel: self.text = "Cancel" self.frame = CGRect(origin: CGPoint(x: 10, y: UIScreen.main.bounds.maxY - 50), size: CGSize(width: self.cancelButtonWidth, height: self.cancelButtonHeight)) + break + case .shutter: + self.image = UIImage(named: "cameraShutter", in: Bundle(for: LuminaViewController.self), compatibleWith: nil) + self.frame = CGRect(origin: CGPoint(x: UIScreen.main.bounds.midX - 35, y: UIScreen.main.bounds.maxY - 80), size: CGSize(width: self.shutterButtonDimension, height: self.shutterButtonDimension)) + break default: break } diff --git a/Lumina/Lumina/LuminaCamera.swift b/Lumina/Lumina/LuminaCamera.swift index ae43707..41bb9d1 100644 --- a/Lumina/Lumina/LuminaCamera.swift +++ b/Lumina/Lumina/LuminaCamera.swift @@ -1,8 +1,8 @@ // -// LuminaCamera.swift -// Lumina +// Camera.swift +// CameraFramework // -// Created by David Okun IBM on 9/10/17. +// Created by David Okun IBM on 8/31/17. // Copyright © 2017 David Okun. All rights reserved. // @@ -10,22 +10,54 @@ import UIKit import AVFoundation protocol LuminaCameraDelegate { - func finishedUpdating() + func stillImageCaptured(camera: LuminaCamera, image: UIImage) + func videoFrameCaptured(camera: LuminaCamera, frame: UIImage) } -final class LuminaCamera: NSObject { +class LuminaCamera: NSObject { + var delegate: LuminaCameraDelegate! = nil var controller: LuminaViewController? - internal var delegate: LuminaCameraDelegate? - var position: CameraDirection = .unspecified { + var torchState = false { didSet { - if self.session.isRunning { - self.session.stopRunning() + guard let input = self.videoInput else { + torchState = false + return } do { - try update() + try input.device.lockForConfiguration() + if torchState == false { + if input.device.isTorchModeSupported(.off) { + input.device.torchMode = .off + input.device.unlockForConfiguration() + } + } else { + if input.device.isTorchModeSupported(.on) { + input.device.torchMode = .on + input.device.unlockForConfiguration() + } + } } catch { - print("could not update camera position") + torchState = false + input.device.unlockForConfiguration() + } + } + } + + var streamFrames = false { + didSet { + if self.session.isRunning { + self.session.stopRunning() + update() + } + } + } + + var position: CameraPosition = .unspecified { + didSet { + if self.session.isRunning { + self.session.stopRunning() + update() } } } @@ -38,56 +70,98 @@ final class LuminaCamera: NSObject { fileprivate var discoverySession: AVCaptureDevice.DiscoverySession? { return AVCaptureDevice.DiscoverySession(deviceTypes: [AVCaptureDevice.DeviceType.builtInWideAngleCamera], mediaType: AVMediaType.video, position: AVCaptureDevice.Position.unspecified) } - fileprivate var videoInput: AVCaptureDeviceInput? - fileprivate var videoOutput = AVCaptureVideoDataOutput() + fileprivate var videoBufferQueue = DispatchQueue(label: "com.Lumina.videoBufferQueue") + fileprivate var videoOutput: AVCaptureVideoDataOutput { + let output = AVCaptureVideoDataOutput() + output.setSampleBufferDelegate(self, queue: videoBufferQueue) + return output + } + fileprivate var photoOutput = AVCapturePhotoOutput() - private var _previewLayer: AVCaptureVideoPreviewLayer? - var previewLayer: AVCaptureVideoPreviewLayer? { - get { - if let existingLayer = self._previewLayer { - return existingLayer - } - guard let controller = self.controller else { - return nil + func getPreviewLayer() -> AVCaptureVideoPreviewLayer? { + guard let controller = self.controller else { + return nil + } + let previewLayer = AVCaptureVideoPreviewLayer(session: self.session) + previewLayer.frame = controller.view.bounds + previewLayer.videoGravity = AVLayerVideoGravity.resizeAspectFill + + return previewLayer + } + + func captureStillImage() { + let settings = AVCapturePhotoSettings() + settings.isAutoStillImageStabilizationEnabled = true + settings.flashMode = self.torchState ? .on : .off + self.photoOutput.capturePhoto(with: settings, delegate: self) + } + + func updateOutputVideoOrientation(_ orientation: AVCaptureVideoOrientation) { + self.videoBufferQueue.async { + for output in self.session.outputs { + guard let connection = output.connection(with: AVMediaType.video) else { + continue + } + if connection.isVideoOrientationSupported { + connection.videoOrientation = orientation + } } - let previewLayer = AVCaptureVideoPreviewLayer(session: self.session) - previewLayer.frame = controller.view.bounds - previewLayer.videoGravity = AVLayerVideoGravity.resizeAspect - _previewLayer = previewLayer - return previewLayer } } - func update() throws { - if let currentInput = self.videoInput { - self.session.removeInput(currentInput) - self.session.removeOutput(self.videoOutput) + func update() { + recycleDeviceIO() + self.torchState = false + guard let input = getNewInputDevice() else { + return } + guard self.session.canAddInput(input) else { + return + } + guard self.session.canAddOutput(self.videoOutput) else { + return + } + guard self.session.canAddOutput(self.photoOutput) else { + return + } + self.videoInput = input + self.session.addInput(input) + if self.streamFrames { + self.session.addOutput(self.videoOutput) + } + self.session.addOutput(self.photoOutput) + self.session.commitConfiguration() + self.session.startRunning() + } +} + +// MARK: CaptureDevice Handling + +private extension LuminaCamera { + func getNewInputDevice() -> AVCaptureDeviceInput? { do { guard let device = getDevice(with: self.position == .front ? AVCaptureDevice.Position.front : AVCaptureDevice.Position.back) else { print("could not find valid AVCaptureDevice") - return + return nil } let input = try AVCaptureDeviceInput(device: device) - if self.session.canAddInput(input) && self.session.canAddOutput(self.videoOutput) { - self.videoInput = input - self.session.addInput(input) - self.session.addOutput(self.videoOutput) - self.session.commitConfiguration() - self.session.startRunning() - if let delegate = self.delegate { - delegate.finishedUpdating() - } - } else { - print("could not add input") - } + return input } catch { - // TODO: add error handling here. + return nil + } + } + + func recycleDeviceIO() { + for oldInput in self.session.inputs { + self.session.removeInput(oldInput) + } + for oldOutput in self.session.outputs { + self.session.removeOutput(oldOutput) } } - private func getDevice(with position: AVCaptureDevice.Position) -> AVCaptureDevice? { + func getDevice(with position: AVCaptureDevice.Position) -> AVCaptureDevice? { guard let discoverySession = self.discoverySession else { return nil } @@ -98,5 +172,76 @@ final class LuminaCamera: NSObject { } return nil } +} + +// MARK: Still Photo Capture + +extension LuminaCamera: AVCapturePhotoCaptureDelegate { + func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photoSampleBuffer: CMSampleBuffer?, previewPhoto previewPhotoSampleBuffer: CMSampleBuffer?, resolvedSettings: AVCaptureResolvedPhotoSettings, bracketSettings: AVCaptureBracketedStillImageSettings?, error: Error?) { + guard let buffer = photoSampleBuffer else { + return + } + guard let image = buffer.normalizedStillImage(forCameraPosition: self.position) else { + return + } + self.delegate.stillImageCaptured(camera: self, image: image) + } +} + +// MARK: Video Frame Streaming + +extension LuminaCamera: AVCaptureVideoDataOutputSampleBufferDelegate { + func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) { + guard let image = sampleBuffer.normalizedVideoFrame() else { + return + } + DispatchQueue.main.async { + self.delegate.videoFrameCaptured(camera: self, frame: image) + } + } +} + +// MARK: Image Normalization Methods + +extension CMSampleBuffer { + func normalizedStillImage(forCameraPosition position: CameraPosition) -> UIImage? { + guard let imageData = AVCapturePhotoOutput.jpegPhotoDataRepresentation(forJPEGSampleBuffer: self, previewPhotoSampleBuffer: nil) else { + return nil + } + guard let dataProvider = CGDataProvider(data: imageData as CFData) else { + return nil + } + guard let cgImageRef = CGImage(jpegDataProviderSource: dataProvider, decode: nil, shouldInterpolate: true, intent: CGColorRenderingIntent.defaultIntent) else { + return nil + } + return UIImage(cgImage: cgImageRef, scale: 1.0, orientation: getImageOrientation(forCamera: position)) + } + func normalizedVideoFrame() -> UIImage? { + guard let imageBuffer = CMSampleBufferGetImageBuffer(self) else { + return nil + } + let coreImage: CIImage = CIImage(cvPixelBuffer: imageBuffer) + let context: CIContext = CIContext() + guard let sample: CGImage = context.createCGImage(coreImage, from: coreImage.extent) else { + return nil + } + return UIImage(cgImage: sample) + } + + private func getImageOrientation(forCamera: CameraPosition) -> UIImageOrientation { + switch UIApplication.shared.statusBarOrientation { + case .landscapeLeft: + return forCamera == .back ? .down : .upMirrored + case .landscapeRight: + return forCamera == .back ? .up : .downMirrored + case .portraitUpsideDown: + return forCamera == .back ? .left : .rightMirrored + case .portrait: + return forCamera == .back ? .right : .leftMirrored + case .unknown: + return forCamera == .back ? .right : .leftMirrored + } + } } + diff --git a/Lumina/Lumina/LuminaTextPromptView.swift b/Lumina/Lumina/LuminaTextPromptView.swift index bec7d8a..4fe39da 100644 --- a/Lumina/Lumina/LuminaTextPromptView.swift +++ b/Lumina/Lumina/LuminaTextPromptView.swift @@ -13,9 +13,9 @@ final class LuminaTextPromptView: UIView { private var textLabel = UILabel() static private let animationDuration = 0.3 - public override init(frame: CGRect) { - super.init(frame: frame) - self.textLabel = UILabel(frame: CGRect(origin: CGPoint(x: 5, y: 5), size: CGSize(width: frame.width - 10, height: frame.height - 10))) + init() { + super.init(frame: CGRect.zero) + self.textLabel = UILabel() self.textLabel.backgroundColor = UIColor.clear self.textLabel.textColor = UIColor.white self.textLabel.textAlignment = .center @@ -28,10 +28,28 @@ final class LuminaTextPromptView: UIView { self.alpha = 0.0 self.layer.cornerRadius = 5.0 } +// +// public override init(frame: CGRect) { +// super.init(frame: frame) +// self.textLabel = UILabel(frame: CGRect(origin: CGPoint(x: 5, y: 5), size: CGSize(width: frame.width - 10, height: frame.height - 10))) +// self.textLabel.backgroundColor = UIColor.clear +// self.textLabel.textColor = UIColor.white +// self.textLabel.textAlignment = .center +// self.textLabel.font = UIFont.systemFont(ofSize: 20) +// self.textLabel.numberOfLines = 3 +// self.textLabel.minimumScaleFactor = 10/UIFont.labelFontSize +// self.textLabel.adjustsFontSizeToFitWidth = true +// self.addSubview(textLabel) +// self.backgroundColor = UIColor.blue +// self.alpha = 0.0 +// self.layer.cornerRadius = 5.0 +// } public func updateText(to text:String) { - self.textLabel.text = text - if self.alpha < 0.1 { + if text.isEmpty { + self.hide(andErase: true) + } else if self.alpha < 0.1 && !text.isEmpty { + self.textLabel.text = text self.makeAppear() } } @@ -56,6 +74,11 @@ final class LuminaTextPromptView: UIView { } } + override func layoutSubviews() { + self.frame.size = CGSize(width: UIScreen.main.bounds.maxX - 20, height: 80) + self.textLabel.frame = CGRect(origin: CGPoint(x: 5, y: 5), size: CGSize(width: frame.width - 10, height: frame.height - 10)) + } + required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } diff --git a/Lumina/Lumina/LuminaViewController.swift b/Lumina/Lumina/LuminaViewController.swift index a418baa..7b42a51 100644 --- a/Lumina/Lumina/LuminaViewController.swift +++ b/Lumina/Lumina/LuminaViewController.swift @@ -1,178 +1,119 @@ // -// LuminaController.swift -// Lumina +// CameraViewController.swift +// CameraFramework // -// Created by David Okun IBM on 4/21/17. +// Created by David Okun IBM on 8/29/17. // Copyright © 2017 David Okun. All rights reserved. // -import Foundation +import UIKit import AVFoundation public protocol LuminaDelegate { - func detected(camera: LuminaViewController, image: UIImage) - func detected(camera: LuminaViewController, data: [Any]) - func cancelled(camera: LuminaViewController) + func cancelButtonTapped(controller: LuminaViewController) + func stillImageTaken(controller: LuminaViewController, image: UIImage) + func videoFrameCaptured(controller: LuminaViewController, frame: UIImage) } -public enum CameraDirection: String { - case front = "Front" - case back = "Back" - case telephoto = "Telephoto" - case dual = "Dual" - case unspecified = "Unspecified" +public enum CameraPosition { + case front + case back + case unspecified } public final class LuminaViewController: UIViewController { - private var sessionPreset: String? + var camera: LuminaCamera? - fileprivate var previewLayer: AVCaptureVideoPreviewLayer? - private var previewView: UIView? - - private var metadataOutput: AVCaptureMetadataOutput? - private var videoBufferQueue = DispatchQueue(label: "com.lumina.videoBufferQueue") - private var metadataBufferQueue = DispatchQueue(label: "com.lumina.metadataBufferQueue") - - fileprivate var input: AVCaptureDeviceInput? - - fileprivate var videoOutput: AVCaptureVideoDataOutput { - let videoOutput = AVCaptureVideoDataOutput() - videoOutput.alwaysDiscardsLateVideoFrames = true - videoOutput.videoSettings = [kCVPixelBufferPixelFormatTypeKey as AnyHashable as! String : kCVPixelFormatType_32BGRA] - //videoOutput.setSampleBufferDelegate(self, queue: videoBufferQueue) - return videoOutput + private var _previewLayer: AVCaptureVideoPreviewLayer? + var previewLayer: AVCaptureVideoPreviewLayer { + if let currentLayer = _previewLayer { + return currentLayer + } + guard let camera = self.camera, let layer = camera.getPreviewLayer() else { + return AVCaptureVideoPreviewLayer() + } + _previewLayer = layer + return layer } - fileprivate var textPromptView: LuminaTextPromptView? - fileprivate var initialPrompt: String? - - fileprivate var session: AVCaptureSession? + private var _cancelButton: LuminaButton? + var cancelButton: LuminaButton { + if let currentButton = _cancelButton { + return currentButton + } + let button = LuminaButton(with: SystemButtonType.cancel) + button.addTarget(self, action: #selector(cancelButtonTapped), for: .touchUpInside) + _cancelButton = button + return button + } - private var _cameraSwitchButton: LuminaButton? - fileprivate var cameraSwitchButton: LuminaButton { - get { - if let switchButton = _cameraSwitchButton { - return switchButton - } - let button = LuminaButton(with: .cameraSwitch) - button.addTarget(self, action: #selector(cameraSwitchButtonTapped), for: UIControlEvents.touchUpInside) - _cameraSwitchButton = button - return button + private var _shutterButton: LuminaButton? + var shutterButton: LuminaButton { + if let currentButton = _shutterButton { + return currentButton } + let button = LuminaButton(with: SystemButtonType.shutter) + button.addTarget(self, action: #selector(shutterButtonTapped), for: .touchUpInside) + _shutterButton = button + return button } - private var _cameraCancelButton: LuminaButton? - fileprivate var cameraCancelButton: LuminaButton { - get { - if let cancelButton = _cameraCancelButton { - return cancelButton - } - let button = LuminaButton(with: .cancel) - button.addTarget(self, action: #selector(cameraCancelButtonTapped), for: UIControlEvents.touchUpInside) - _cameraCancelButton = button - return button + private var _switchButton: LuminaButton? + var switchButton: LuminaButton { + if let currentButton = _switchButton { + return currentButton } + let button = LuminaButton(with: SystemButtonType.cameraSwitch) + button.addTarget(self, action: #selector(switchButtonTapped), for: .touchUpInside) + _switchButton = button + return button } - private var _cameraTorchButton: LuminaButton? - fileprivate var cameraTorchButton: UIButton { - get { - if let torchButton = _cameraTorchButton { - return torchButton - } - let button = LuminaButton(with: .torch) - button.addTarget(self, action: #selector(cameraTorchButtonTapped), for: UIControlEvents.touchUpInside) - _cameraTorchButton = button - return button + private var _torchButton: LuminaButton? + var torchButton: LuminaButton { + if let currentButton = _torchButton { + return currentButton } + let button = LuminaButton(with: SystemButtonType.torch) + button.addTarget(self, action: #selector(torchButtonTapped), for: .touchUpInside) + _torchButton = button + return button } - fileprivate var isUpdating = false - fileprivate var torchOn = false - fileprivate var metadataBordersCodes: [LuminaMetadataBorderView]? - fileprivate var metadataBordersFaces: [LuminaMetadataBorderView]? - fileprivate var metadataBordersCodesDestructionTimer: Timer? - fileprivate var metadataBordersFacesDestructionTimer: Timer? - fileprivate var camera: LuminaCamera? + private var _textPromptView: LuminaTextPromptView? + var textPromptView: LuminaTextPromptView { + if let existingView = _textPromptView { + return existingView + } + let promptView = LuminaTextPromptView() + _textPromptView = promptView + return promptView + } - public var delegate: LuminaDelegate! = nil - public var trackImages = false - public var trackMetadata = false - public var improvedImageDetectionPerformance = false - public var drawMetadataBorders = false + open var delegate: LuminaDelegate! = nil - open var position: CameraDirection = .unspecified { + open var position: CameraPosition = .unspecified { didSet { guard let camera = self.camera else { return } - if position == .unspecified { - self.position = .back - } else { - camera.position = position - } + camera.position = position } } - override public var prefersStatusBarHidden: Bool { - return true + open var streamFrames = false { + didSet { + if let camera = self.camera { + camera.streamFrames = streamFrames + } + } } -// private var discoverySession: AVCaptureDevice.DiscoverySession? { -// var deviceTypes: [AVCaptureDevice.DeviceType] = [] -// deviceTypes.append(AVCaptureDevice.DeviceType.builtInWideAngleCamera) -// if #available(iOS 10.2, *) { -// deviceTypes.append(AVCaptureDevice.DeviceType.builtInDualCamera) -// deviceTypes.append(AVCaptureDevice.DeviceType.builtInTelephotoCamera) -// } -// let discoverySession = AVCaptureDevice.DiscoverySession(deviceTypes: deviceTypes, mediaType: AVMediaType.video, position: AVCaptureDevice.Position.unspecified) -// -// return discoverySession -// } - -// private func getDevice(for cameraDirection: CameraDirection) -> AVCaptureDevice? { -// var device: AVCaptureDevice? -// guard let discoverySession = self.discoverySession else { -// print("Could not get discovery session") -// return nil -// } -// for discoveryDevice: AVCaptureDevice in discoverySession.devices { -// if cameraDirection == .front { -// if discoveryDevice.position == AVCaptureDevice.Position.front { -// device = discoveryDevice -// break -// } -// } else { -// if discoveryDevice.position == AVCaptureDevice.Position.back { // TODO: add support for iPhone 7 plus dual cameras -// device = discoveryDevice -// break -// } -// } -// } -// return device -// } - -// public init?(camera: CameraDirection) { -// super.init(nibName: nil, bundle: nil) -// -// let session = AVCaptureSession() -// self.previewLayer = AVCaptureVideoPreviewLayer(session: session) -// self.previewView = self.view -// -// guard let previewLayer = self.previewLayer else { -// print("Could not access image preview layer") -// return -// } -// previewLayer.videoGravity = AVLayerVideoGravity.resizeAspectFill -// self.view.layer.addSublayer(previewLayer) -// self.view.bounds = UIScreen.main.bounds -// -// previewLayer.frame = self.view.bounds -// self.session = session -// commitSession(for: camera) -// createUI() -// createTextPromptView() -// } + open var textPrompt = "" { + didSet { + self.textPromptView.updateText(to: textPrompt) + } + } public init() { super.init(nibName: nil, bundle: nil) @@ -181,563 +122,139 @@ public final class LuminaViewController: UIViewController { self.camera = camera } + public required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + let camera = LuminaCamera(with: self) + camera.delegate = self + self.camera = camera + } + + public override func didReceiveMemoryWarning() { + super.didReceiveMemoryWarning() + print("Camera framework is overloading on memory") + } + public override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) if let camera = self.camera { + camera.update() createUI() - do { - try camera.update() - } - catch { - print("could not update camera") - } } } -// fileprivate func commitSession(for desiredCameraDirection: CameraDirection) { -// guard let session = self.session else { -// print("Error getting session") -// return -// } -// self.currentCameraDirection = desiredCameraDirection -// -// session.sessionPreset = AVCaptureSession.Preset.high -// -// if let input = self.input { -// session.removeInput(input) -// } -// -// do { -// guard let device = getDevice(for: desiredCameraDirection) else { -// print("could not get desired camera direction") -// return -// } -// try input = AVCaptureDeviceInput(device: device) -// if session.canAddInput(input!) { -// session.addInput(input!) -// self.input = input! -// } -// } catch { -// print("Error getting device input for \(desiredCameraDirection.rawValue)") -// return -// } -// -//// let metadataOutput = AVCaptureMetadataOutput() -// -// let videoOutput = self.videoOutput -// -// if session.canAddOutput(videoOutput) { -// session.addOutput(videoOutput) -// } -// -//// if session.canAddOutput(metadataOutput) { -//// session.addOutput(metadataOutput) -//// } -//// metadataOutput.setMetadataObjectsDelegate(self, queue: metadataBufferQueue) -//// metadataOutput.metadataObjectTypes = metadataOutput.availableMetadataObjectTypes -//// -//// self.metadataOutput = metadataOutput -// -// session.commitConfiguration() -// session.startRunning() -// -// if let connection = videoOutput.connection(with: AVMediaType.video) { -// connection.isEnabled = true -// if connection.isVideoMirroringSupported && desiredCameraDirection == .front { -// connection.isVideoMirrored = true -// connection.preferredVideoStabilizationMode = .standard -// } -// } -// -// guard let cameraSwitchButton = self.cameraSwitchButton else { -// print("Could not create camera switch button") -// return -// } -// cameraSwitchButton.isEnabled = true -// -// } + public override func viewWillLayoutSubviews() { + super.viewWillLayoutSubviews() + updateUI(orientation: UIApplication.shared.statusBarOrientation) + updateButtonFrames() + } - private func createUI() { - guard let camera = self.camera else { - return + open class func getVersion() -> String? { + let bundle = Bundle(for: LuminaViewController.self) + guard let infoDictionary = bundle.infoDictionary else { + return nil } - guard let previewLayer = camera.previewLayer else { + guard let versionString = infoDictionary["CFBundleShortVersionString"] as? String else { + return nil + } + return versionString + } +} + +// MARK: User Interface Creation + +fileprivate extension LuminaViewController { + func createUI() { + self.view.layer.addSublayer(self.previewLayer) + self.view.addSubview(self.cancelButton) + self.view.addSubview(self.shutterButton) + self.view.addSubview(self.switchButton) + self.view.addSubview(self.torchButton) + self.view.addSubview(self.textPromptView) + } + + func updateUI(orientation: UIInterfaceOrientation) { + guard let connection = self.previewLayer.connection, connection.isVideoOrientationSupported else { return } - self.previewLayer = previewLayer - self.view.layer.addSublayer(previewLayer) + self.previewLayer.frame = self.view.bounds + connection.videoOrientation = necessaryVideoOrientation(for: orientation) + if let camera = self.camera { + camera.updateOutputVideoOrientation(connection.videoOrientation) + } + } - self.view.addSubview(self.cameraSwitchButton) - - self.view.addSubview(self.cameraSwitchButton) - self.view.addSubview(self.cameraCancelButton) - self.view.addSubview(self.cameraTorchButton) + func updateButtonFrames() { + self.cancelButton.center = CGPoint(x: self.view.frame.minX + 55, y: self.view.frame.maxY - 45) + self.shutterButton.center = CGPoint(x: self.view.frame.midX, y: self.view.frame.maxY - 45) + self.switchButton.center = CGPoint(x: self.view.frame.maxX - 25, y: self.view.frame.minY + 25) + self.torchButton.center = CGPoint(x: self.view.frame.minX + 25, y: self.view.frame.minY + 25) + self.textPromptView.center = CGPoint(x: self.view.frame.midX, y: self.view.frame.minY + 95) + self.textPromptView.layoutSubviews() } - public required init?(coder aDecoder: NSCoder) { - return nil + private func necessaryVideoOrientation(for statusBarOrientation: UIInterfaceOrientation) -> AVCaptureVideoOrientation { + switch statusBarOrientation { + case .portrait: + return AVCaptureVideoOrientation.portrait + case .landscapeLeft: + return AVCaptureVideoOrientation.landscapeLeft + case .landscapeRight: + return AVCaptureVideoOrientation.landscapeRight + case .portraitUpsideDown: + return AVCaptureVideoOrientation.portraitUpsideDown + default: + return AVCaptureVideoOrientation.portrait + } } } -extension LuminaViewController: LuminaCameraDelegate { // MARK: Camera Delegate Functions - func finishedUpdating() { - self.cameraTorchButton.isEnabled = true - self.cameraCancelButton.isEnabled = true - self.cameraSwitchButton.isEnabled = true - } -} +// MARK: CameraDelegate Functions -extension LuminaViewController { // MARK: Text prompt methods - fileprivate func createTextPromptView() { - let view = LuminaTextPromptView(frame: CGRect(origin: CGPoint(x: self.view.bounds.minX + 10, y: self.view.bounds.minY + 70), size: CGSize(width: self.view.bounds.size.width - 20, height: 80))) - if let prompt = self.initialPrompt { - view.updateText(to: prompt) +extension LuminaViewController: LuminaCameraDelegate { + func stillImageCaptured(camera: LuminaCamera, image: UIImage) { + if let delegate = self.delegate { + delegate.stillImageTaken(controller: self, image: image) } - self.view.addSubview(view) - self.textPromptView = view } - public func updateTextPromptView(to text:String) { - DispatchQueue.main.async { - guard let view = self.textPromptView else { - print("No text prompt view to update!!") - return - } - view.updateText(to: text) + func videoFrameCaptured(camera: LuminaCamera, frame: UIImage) { + if let delegate = self.delegate { + delegate.videoFrameCaptured(controller: self, frame: frame) + } + } +} + +// MARK: UIButton Functions + +fileprivate extension LuminaViewController { + @objc func cancelButtonTapped() { + if let delegate = self.delegate { + delegate.cancelButtonTapped(controller: self) } } - public func hideTextPromptView(andEraseText: Bool) { - DispatchQueue.main.async { - guard let view = self.textPromptView else { - print("Could not find text prompt view to hide!!!") - return - } - view.hide(andErase: andEraseText) + @objc func shutterButtonTapped() { + guard let camera = self.camera else { + return } + camera.captureStillImage() } -} - -private extension LuminaViewController { //MARK: Button Tap Methods - @objc func cameraSwitchButtonTapped() { - self.cameraSwitchButton.isEnabled = false + + @objc func switchButtonTapped() { switch self.position { - case .front: - self.position = .back - break case .back: self.position = .front break - case .telephoto: - self.position = .front - break - case .dual: - self.position = .front - break - case .unspecified: - self.position = .front + default: + self.position = .back break } } - @objc func cameraCancelButtonTapped() { - if let delegate = self.delegate { - delegate.cancelled(camera: self) + @objc func torchButtonTapped() { + guard let camera = self.camera else { + return } - } - - @objc func cameraTorchButtonTapped() { - // TODO: Update for new camera arch -// guard let input = self.input else { -// print("Trying to update torch, but cannot detect device input!") -// return -// } -// if self.torchOn == false { -// do { -// if input.device.isTorchModeSupported(.on) { -// try input.device.lockForConfiguration() -// try input.device.setTorchModeOn(level: 1.0) -// self.torchOn = !self.torchOn -// input.device.unlockForConfiguration() -// } -// } catch { -// print("Could not turn torch on!!") -// } -// } else { -// do { -// if input.device.isTorchModeSupported(.off) { -// try input.device.lockForConfiguration() -// input.device.torchMode = .off -// self.torchOn = !self.torchOn -// input.device.unlockForConfiguration() -// } -// } catch { -// print("Could not turn torch off!!") -// } -// } + camera.torchState = !camera.torchState } } - -//private extension CMSampleBuffer { // MARK: Extending CMSampleBuffer -// var imageFromCoreImage: CGImage? { -// guard let imageBuffer = CMSampleBufferGetImageBuffer(self) else { -// print("Could not get image buffer from CMSampleBuffer") -// return nil -// } -// let coreImage: CIImage = CIImage(cvPixelBuffer: imageBuffer) -// let context: CIContext = CIContext() -// guard let sample: CGImage = context.createCGImage(coreImage, from: coreImage.extent) else { -// print("Could not create CoreGraphics image from context") -// return nil -// } -// return sample -// } -// -// var imageFromPixelBuffer: CGImage? { -// guard let imageBuffer = CMSampleBufferGetImageBuffer(self) else { -// print("Could not get image buffer from CMSampleBuffer") -// return nil -// } -// if CVPixelBufferLockBaseAddress(imageBuffer, CVPixelBufferLockFlags(rawValue: 0)) == kCVReturnSuccess { -// var colorSpace = CGColorSpaceCreateDeviceRGB() -// var bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.noneSkipFirst.rawValue | CGBitmapInfo.byteOrder32Little.rawValue) -// var width = CVPixelBufferGetWidth(imageBuffer) -// var height = CVPixelBufferGetHeight(imageBuffer) -// var bitsPerComponent: size_t = 8 -// var bytesPerRow = CVPixelBufferGetBytesPerRow(imageBuffer) -// var baseAddress = CVPixelBufferGetBaseAddress(imageBuffer) -// -// let format = CVPixelBufferGetPixelFormatType(imageBuffer) -// if format == kCVPixelFormatType_420YpCbCr8BiPlanarFullRange { -// baseAddress = CVPixelBufferGetBaseAddressOfPlane(imageBuffer, 0) -// width = CVPixelBufferGetWidthOfPlane(imageBuffer, 0) -// height = CVPixelBufferGetHeightOfPlane(imageBuffer, 0) -// bitsPerComponent = 1 -// bytesPerRow = CVPixelBufferGetBytesPerRowOfPlane(imageBuffer, 0) -// colorSpace = CGColorSpaceCreateDeviceGray() -// bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.none.rawValue | CGBitmapInfo.byteOrder32Big.rawValue) -// } -// -// let context = CGContext(data: baseAddress, width: width, height: height, bitsPerComponent: bitsPerComponent, bytesPerRow: bytesPerRow, space: colorSpace, bitmapInfo: bitmapInfo.rawValue) -// if let context = context { -// guard let sample = context.makeImage() else { -// print("Could not create CoreGraphics image from context") -// return nil -// } -// CVPixelBufferUnlockBaseAddress(imageBuffer, CVPixelBufferLockFlags(rawValue: 0)) -// return sample -// } else { -// print("Could not create CoreGraphics context") -// return nil -// } -// } else { -// print("Could not lock base address for pixel buffer") -// return nil -// } -// } -//} - -//extension LuminaViewController: AVCaptureVideoDataOutputSampleBufferDelegate { // MARK: Image Tracking Output -// public func captureOutput(_ captureOutput: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) { -// guard case self.trackImages = true else { -// return -// } -// guard let delegate = self.delegate else { -// print("Warning!! No delegate set, but image tracking turned on") -// return -// } -// -//// guard let sampleBuffer = sampleBuffer else { -//// print("No sample buffer detected") -//// return -//// } -// let startTime = Date() -// var sample: CGImage? = nil -// if self.improvedImageDetectionPerformance { -// sample = sampleBuffer.imageFromPixelBuffer -// } else { -// sample = sampleBuffer.imageFromCoreImage -// } -// guard let completedSample = sample else { -// return -// } -// let orientation: UIImageOrientation = self.currentCameraDirection == .front ? .left : .right -// let image = UIImage(cgImage: completedSample, scale: 1.0, orientation: orientation).fixOrientation -// let end = Date() -// print("Image tracking processing time: \(end.timeIntervalSince(startTime))") -// delegate.detected(camera: self, image: image) -// } -//} - -//extension LuminaViewController { // MARK: Tap to focus methods -// override public func touchesBegan(_ touches: Set, with event: UIEvent?) { -// if self.isUpdating == true { -// return -// } else { -// self.isUpdating = true -// } -// for touch in touches { -// let point = touch.location(in: touch.view) -// let focusX = point.x/UIScreen.main.bounds.size.width -// let focusY = point.y/UIScreen.main.bounds.size.height -// guard let input = self.input else { -// print("Trying to focus, but cannot detect device input!") -// return -// } -// do { -// if input.device.isFocusModeSupported(.autoFocus) && input.device.isFocusPointOfInterestSupported { -// try input.device.lockForConfiguration() -// input.device.focusMode = .autoFocus -// input.device.focusPointOfInterest = CGPoint(x: focusX, y: focusY) -// if input.device.isExposureModeSupported(.autoExpose) && input.device.isExposurePointOfInterestSupported { -// input.device.exposureMode = .autoExpose -// input.device.exposurePointOfInterest = CGPoint(x: focusX, y: focusY) -// } -// input.device.unlockForConfiguration() -// showFocusView(at: point) -// let deadlineTime = DispatchTime.now() + .seconds(1) -// DispatchQueue.main.asyncAfter(deadline: deadlineTime) { -// self.resetCameraToContinuousExposureAndFocus() -// } -// } else { -// self.isUpdating = false -// } -// } catch { -// print("could not lock for configuration! Not able to focus") -// self.isUpdating = false -// } -// } -// } -// -// func resetCameraToContinuousExposureAndFocus() { -// do { -// guard let input = self.input else { -// print("Trying to focus, but cannot detect device input!") -// return -// } -// if input.device.isFocusModeSupported(.continuousAutoFocus) { -// try input.device.lockForConfiguration() -// input.device.focusMode = .continuousAutoFocus -// if input.device.isExposureModeSupported(.continuousAutoExposure) { -// input.device.exposureMode = .continuousAutoExposure -// } -// input.device.unlockForConfiguration() -// } -// } catch { -// print("could not reset to continuous auto focus and exposure!!") -// } -// } -// -// func showFocusView(at: CGPoint) { -// let focusView: UIImageView = UIImageView(image: UIImage(named: "cameraFocus", in: Bundle(for: LuminaViewController.self), compatibleWith: nil)) -// focusView.contentMode = .scaleAspectFit -// focusView.frame = CGRect(x: 0, y: 0, width: 50, height: 50) -// focusView.center = at -// focusView.alpha = 0.0 -// self.view.addSubview(focusView) -// UIView.animate(withDuration: 0.2, animations: { -// focusView.alpha = 1.0 -// }, completion: { complete in -// UIView.animate(withDuration: 1.0, animations: { -// focusView.alpha = 0.0 -// }, completion: { final in -// focusView.removeFromSuperview() -// self.isUpdating = false -// }) -// }) -// } -//} - -//extension LuminaViewController: AVCaptureMetadataOutputObjectsDelegate { // MARK: Metadata output buffer -// public func metadataOutput(_ captureOutput: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) { -// guard case self.trackMetadata = true else { -// return -// } -// guard let delegate = self.delegate else { -// return -// } -// defer { -// delegate.detected(camera: self, data: metadataObjects) -// } -// if self.drawMetadataBorders == true { -// guard let previewLayer = self.previewLayer else { -// return -// } -// guard let firstObject = metadataObjects.first else { -// return -// } -// if let _: AVMetadataMachineReadableCodeObject = previewLayer.transformedMetadataObject(for: firstObject ) as? AVMetadataMachineReadableCodeObject { // TODO: Figure out exactly why Faces and Barcodes fire this method separately -// if let oldBorders = self.metadataBordersCodes { -// for oldBorder in oldBorders { -// DispatchQueue.main.async { -// oldBorder.removeFromSuperview() -// } -// } -// } -// self.metadataBordersCodes = nil -// var newBorders = [LuminaMetadataBorderView]() -// -// for metadata in metadataObjects { -// guard let transformed: AVMetadataMachineReadableCodeObject = previewLayer.transformedMetadataObject(for: metadata ) as? AVMetadataMachineReadableCodeObject else { -// continue -// } -// var border = LuminaMetadataBorderView() -// border.isHidden = true -// border.frame = transformed.bounds -// -// let translatedCorners = translate(points: transformed.corners, fromView: self.view, toView: border) -// border = LuminaMetadataBorderView(frame: transformed.bounds, corners: translatedCorners) -// border.isHidden = false -// newBorders.append(border) -// DispatchQueue.main.async { -// self.view.addSubview(border) -// } -// } -// DispatchQueue.main.async { -// self.drawingTimerCodes() -// } -// self.metadataBordersCodes = newBorders -// } else { -// if let oldBorders = self.metadataBordersFaces { -// for oldBorder in oldBorders { -// DispatchQueue.main.async { -// oldBorder.removeFromSuperview() -// } -// } -// } -// self.metadataBordersFaces = nil -// var newBorders = [LuminaMetadataBorderView]() -// -// for metadata in metadataObjects { -// guard let face: AVMetadataFaceObject = previewLayer.transformedMetadataObject(for: metadata ) as? AVMetadataFaceObject else { -// continue -// } -// let border = LuminaMetadataBorderView(frame: face.bounds) -// border.boundsFace = true -// newBorders.append(border) -// DispatchQueue.main.async { -// self.view.addSubview(border) -// } -// } -// DispatchQueue.main.async { -// self.drawingTimerFaces() -// } -// self.metadataBordersFaces = newBorders -// } -// } -// } -// -// private func translate(points: [CGPoint], fromView: UIView, toView: UIView) -> [CGPoint] { -// var translatedPoints = [CGPoint]() -// for point in points { -// let currentPoint = CGPoint(x: point.x, y: point.y) //CGPoint(x: point["X"] as! Double, y: point["Y"] as! Double) -// let translatedPoint = fromView.convert(currentPoint, to: toView) -// translatedPoints.append(translatedPoint) -// } -// return translatedPoints -// } -// -// private func drawingTimerCodes() { -// DispatchQueue.main.async { -// if let _ = self.metadataBordersCodesDestructionTimer { -// self.metadataBordersCodesDestructionTimer!.invalidate() -// } -// self.metadataBordersCodesDestructionTimer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(self.removeAllBordersCodes), userInfo: nil, repeats: false) -// } -// } -// -// private func drawingTimerFaces() { -// DispatchQueue.main.async { -// if let _ = self.metadataBordersFacesDestructionTimer { -// self.metadataBordersFacesDestructionTimer!.invalidate() -// } -// self.metadataBordersFacesDestructionTimer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(self.removeAllBordersFaces), userInfo: nil, repeats: false) -// } -// } -// -// @objc private func removeAllBordersCodes() { -// DispatchQueue.main.async { -// for subview in self.view.subviews { -// if let border = subview as? LuminaMetadataBorderView, border.boundsFace == false { -// border.removeFromSuperview() -// } -// } -// self.metadataBordersCodes = nil -// } -// } -// -// @objc private func removeAllBordersFaces() { -// DispatchQueue.main.async { -// for subview in self.view.subviews { -// if let border = subview as? LuminaMetadataBorderView, border.boundsFace == true { -// border.removeFromSuperview() -// } -// } -// self.metadataBordersFaces = nil -// } -// } -//} -// -//private extension UIImage { // MARK: Fix UIImage orientation -// var fixOrientation: UIImage { -// if imageOrientation == UIImageOrientation.up { -// return self -// } -// -// var transform: CGAffineTransform = CGAffineTransform.identity -// -// switch imageOrientation { -// case UIImageOrientation.down, UIImageOrientation.downMirrored: -// transform = transform.translatedBy(x: size.width, y: size.height) -// transform = transform.rotated(by: CGFloat.pi) -// break -// case UIImageOrientation.left, UIImageOrientation.leftMirrored: -// transform = transform.translatedBy(x: size.width, y: 0) -// transform = transform.rotated(by: CGFloat.pi / 2) -// break -// case UIImageOrientation.right, UIImageOrientation.rightMirrored: -// transform = transform.translatedBy(x: 0, y: size.height) -// transform = transform.rotated(by: CGFloat.pi / -2) -// break -// case UIImageOrientation.up, UIImageOrientation.upMirrored: -// break -// } -// -// switch imageOrientation { -// case UIImageOrientation.upMirrored, UIImageOrientation.downMirrored: -// transform.translatedBy(x: size.width, y: 0) -// transform.scaledBy(x: -1, y: 1) -// break -// case UIImageOrientation.leftMirrored, UIImageOrientation.rightMirrored: -// transform.translatedBy(x: size.height, y: 0) -// transform.scaledBy(x: -1, y: 1) -// case UIImageOrientation.up, UIImageOrientation.down, UIImageOrientation.left, UIImageOrientation.right: -// break -// } -// -// guard let cgImage = self.cgImage, let colorspace = cgImage.colorSpace else { -// return self -// } -// -// guard let ctx: CGContext = CGContext(data: nil, width: Int(size.width), height: Int(size.height), bitsPerComponent: cgImage.bitsPerComponent, bytesPerRow: 0, space: colorspace, bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue) else { -// return self -// } -// -// ctx.concatenate(transform) -// -// switch imageOrientation { -// case UIImageOrientation.left, UIImageOrientation.leftMirrored, UIImageOrientation.right, UIImageOrientation.rightMirrored: -// ctx.draw(cgImage, in: CGRect(x: 0, y: 0, width: size.height, height: size.width)) -// default: -// ctx.draw(cgImage, in: CGRect(x: 0, y: 0, width: size.width, height: size.height)) -// break -// } -// -// if let convertedCGImage = ctx.makeImage() { -// return UIImage(cgImage: convertedCGImage) -// } else { -// return self -// } -// } -//} - diff --git a/Lumina/Lumina/Media.xcassets/cameraShutter.imageset/Contents.json b/Lumina/Lumina/Media.xcassets/cameraShutter.imageset/Contents.json new file mode 100644 index 0000000..5bab5bc --- /dev/null +++ b/Lumina/Lumina/Media.xcassets/cameraShutter.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "trigger.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "trigger@2x-8.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "trigger@3x-8.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Lumina/Lumina/Media.xcassets/cameraShutter.imageset/trigger.png b/Lumina/Lumina/Media.xcassets/cameraShutter.imageset/trigger.png new file mode 100644 index 0000000000000000000000000000000000000000..f71557d71f741cbd3a320928258b0caff78d96bf GIT binary patch literal 1705 zcmV;a23GlrP)^ITa;d*=$Q|t&{ zA2|Dv;ZVYbVV>^CXG-5KxjmHsp$mNaql~_%WEZ`zPt+qOeC^BFg{m;dFBLn}cgU+qGBjiSb#)O+)ZrB*f4!9+%@VXm@$JQ=KD6~V2-IN`?L=86Xx^U=|WG{h2JG5tQf&Z9ld% z6NIZ9K#6MVKSc?BK)bnxS&fLf86^YB!sj;TY8Tr|xGrm@p06i`;j03T$n zzfa6*&KM23(HG1OG$KM$vUwllK?*WNSQu=XSRNt^U=i2=*_(po5}igeEF97!-Qxj3 z9T^r2gXH49z?5<5LbL{H!0B#EC2AzZvIGg0?jk?um+SP{Wr-h1$`fOe#X`WO#S$?V50;7o+-4PDNHDP9 z9f6MmeW5n+c8~!>sD z1Uv6_vqUDfYw}g2g7S`pQM5r~a7!TV^o8_)MI539q|xU zjuEi>*lZux0l4iMvusdH1T1H-plQJsI(JeW^{ zWwvZU40{+%gyQx!lpR%FTR_(UN=&35XV?q|V;-k%6sY(-0L5@ldP>?Rhw}00IXup+ zx(Acqln-UNLqk!0maY5y4R9Lb;JC9pJUOw-|MuvF@%cp5@qs9n6_0MXQ?}FGU6kdS z*({ZlUtgzi@C6gkjfq7tR#rz*k9YjCoPaw!v6cq!Vj7U=(SQNXM7r~nd}&f4GVd%J zIL0mQd`iq2n3(^qs88E|XqrHZ2XwnOFASs%#-Ftchg_oT1K6UwXriWu_u%;MK&;K4 zKOj?qwKqn(;nW=Wcu;sUm2jC!Wz2*eGq;A8(KKQ4fCDY%`%xa;g!hj@mX{h#E$2Pq zn%SJ1*u;~(#y{~80z`)CXm%J-somAX)QPmhQf3y_C{-|Cap$vZ@DAQ|?{(00000NkvXXu0mjfxw}Ip literal 0 HcmV?d00001 diff --git a/Lumina/Lumina/Media.xcassets/cameraShutter.imageset/trigger@2x-8.png b/Lumina/Lumina/Media.xcassets/cameraShutter.imageset/trigger@2x-8.png new file mode 100644 index 0000000000000000000000000000000000000000..48664ea6a36bb840d5d15a239b7dcf33a259467e GIT binary patch literal 3412 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 ddpH8nE3bC?=(C^a3`G4;8t9qm615*A{}1-GQu literal 0 HcmV?d00001 diff --git a/LuminaSample/LuminaSample/Base.lproj/Main.storyboard b/LuminaSample/LuminaSample/Base.lproj/Main.storyboard index 9d5ffd7..677bae1 100644 --- a/LuminaSample/LuminaSample/Base.lproj/Main.storyboard +++ b/LuminaSample/LuminaSample/Base.lproj/Main.storyboard @@ -1,11 +1,11 @@ - + - + @@ -120,41 +120,8 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -187,7 +154,7 @@ - + @@ -237,7 +204,6 @@ - @@ -253,7 +219,7 @@ - + diff --git a/LuminaSample/LuminaSample/Info.plist b/LuminaSample/LuminaSample/Info.plist index 7bea6e0..09857f5 100644 --- a/LuminaSample/LuminaSample/Info.plist +++ b/LuminaSample/LuminaSample/Info.plist @@ -33,6 +33,8 @@ UISupportedInterfaceOrientations UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight UISupportedInterfaceOrientations~ipad diff --git a/LuminaSample/LuminaSample/ViewController.swift b/LuminaSample/LuminaSample/ViewController.swift index e931eeb..f53b962 100644 --- a/LuminaSample/LuminaSample/ViewController.swift +++ b/LuminaSample/LuminaSample/ViewController.swift @@ -13,46 +13,33 @@ class ViewController: UITableViewController { @IBOutlet weak var frontCameraSwitch: UISwitch! @IBOutlet weak var trackImagesSwitch: UISwitch! @IBOutlet weak var trackMetadataSwitch: UISwitch! - @IBOutlet weak var increaseImagePerformanceSwitch: UISwitch! @IBOutlet weak var showTextPromptViewSwitch: UISwitch! @IBOutlet weak var drawMetadataBorders: UISwitch! } extension ViewController { //MARK: IBActions @IBAction func cameraButtonTapped() { - let camera = LuminaViewController() - -// if showTextPromptViewSwitch.isOn { -// camera.updateTextPromptView(to: "I love Lumina, and I'm going to start using it everywhere!! Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah") -// } camera.delegate = self -// camera.trackImages = trackImagesSwitch.isOn -// camera.trackMetadata = trackMetadataSwitch.isOn -// camera.improvedImageDetectionPerformance = increaseImagePerformanceSwitch.isOn -// camera.drawMetadataBorders = drawMetadataBorders.isOn + camera.position = self.frontCameraSwitch.isOn ? .front : .back + camera.streamFrames = self.trackImagesSwitch.isOn + camera.textPrompt = self.showTextPromptViewSwitch.isOn ? "This is how to test the text prompt view" : "" present(camera, animated: true, completion: nil) -// let deadline = DispatchTime.now() + .seconds(4) -// DispatchQueue.main.asyncAfter(deadline: deadline) { -// camera.updateTextPromptView(to: "And here's what happens after you update the text view on the camera!!!") -// let hideDeadline = DispatchTime.now() + .seconds(2) -// DispatchQueue.main.asyncAfter(deadline: hideDeadline, execute: { -// camera.hideTextPromptView(andEraseText: true) -// }) -// } } } extension ViewController: LuminaDelegate { - func detected(camera: LuminaViewController, image: UIImage) { - print("got an image") + func cancelButtonTapped(controller: LuminaViewController) { + controller.dismiss(animated: true, completion: nil) } - func detected(camera: LuminaViewController, data: [Any]) { - print("detected data: \(data)") + func stillImageTaken(controller: LuminaViewController, image: UIImage) { + controller.dismiss(animated: true) { + print("image received - check debugger") + } } - func cancelled(camera: LuminaViewController) { - camera.dismiss(animated: true, completion: nil) + func videoFrameCaptured(controller: LuminaViewController, frame: UIImage) { + print("frame received") } } From 630e127164e523b5f26631aa1859800df7478186 Mon Sep 17 00:00:00 2001 From: David-Okun Date: Mon, 18 Sep 2017 14:56:05 -0500 Subject: [PATCH 4/7] Now camera can focus, only needs metadata detection --- Lumina/Lumina/LuminaCamera.swift | 50 +++++++++++++ Lumina/Lumina/LuminaViewController.swift | 72 +++++++++++++++++-- .../LuminaSample/ViewController.swift | 18 +++-- 3 files changed, 127 insertions(+), 13 deletions(-) diff --git a/Lumina/Lumina/LuminaCamera.swift b/Lumina/Lumina/LuminaCamera.swift index 41bb9d1..49a5d93 100644 --- a/Lumina/Lumina/LuminaCamera.swift +++ b/Lumina/Lumina/LuminaCamera.swift @@ -12,6 +12,7 @@ import AVFoundation protocol LuminaCameraDelegate { func stillImageCaptured(camera: LuminaCamera, image: UIImage) func videoFrameCaptured(camera: LuminaCamera, frame: UIImage) + func finishedFocus(camera: LuminaCamera) } class LuminaCamera: NSObject { @@ -134,6 +135,55 @@ class LuminaCamera: NSObject { self.session.commitConfiguration() self.session.startRunning() } + + func pause() { + self.session.stopRunning() + } +} + +// MARK: Focus Handling + +extension LuminaCamera { + func handleFocus(at focusPoint: CGPoint) { + guard let input = self.videoInput else { + return + } + do { + if input.device.isFocusModeSupported(.autoFocus) && input.device.isFocusPointOfInterestSupported { + try input.device.lockForConfiguration() + input.device.focusMode = .autoFocus + input.device.focusPointOfInterest = CGPoint(x: focusPoint.x, y: focusPoint.y) + if input.device.isExposureModeSupported(.autoExpose) && input.device.isExposurePointOfInterestSupported { + input.device.exposureMode = .autoExpose + input.device.exposurePointOfInterest = CGPoint(x: focusPoint.x, y: focusPoint.y) + } + input.device.unlockForConfiguration() + } else { + self.delegate.finishedFocus(camera: self) + } + } catch { + self.delegate.finishedFocus(camera: self) + } + } + + func resetCameraToContinuousExposureAndFocus() { + do { + guard let input = self.videoInput else { + print("Trying to focus, but cannot detect device input!") + return + } + if input.device.isFocusModeSupported(.continuousAutoFocus) { + try input.device.lockForConfiguration() + input.device.focusMode = .continuousAutoFocus + if input.device.isExposureModeSupported(.continuousAutoExposure) { + input.device.exposureMode = .continuousAutoExposure + } + input.device.unlockForConfiguration() + } + } catch { + print("could not reset to continuous auto focus and exposure!!") + } + } } // MARK: CaptureDevice Handling diff --git a/Lumina/Lumina/LuminaViewController.swift b/Lumina/Lumina/LuminaViewController.swift index 7b42a51..348dc8e 100644 --- a/Lumina/Lumina/LuminaViewController.swift +++ b/Lumina/Lumina/LuminaViewController.swift @@ -10,9 +10,10 @@ import UIKit import AVFoundation public protocol LuminaDelegate { - func cancelButtonTapped(controller: LuminaViewController) - func stillImageTaken(controller: LuminaViewController, image: UIImage) - func videoFrameCaptured(controller: LuminaViewController, frame: UIImage) + func detected(controller: LuminaViewController, stillImage: UIImage) + func detected(controller: LuminaViewController, videoFrame: UIImage) + func detected(controller: LuminaViewController, metadata: [Any]) + func cancelled(controller: LuminaViewController) } public enum CameraPosition { @@ -90,6 +91,8 @@ public final class LuminaViewController: UIViewController { return promptView } + fileprivate var isUpdating = false + open var delegate: LuminaDelegate! = nil open var position: CameraPosition = .unspecified { @@ -142,6 +145,13 @@ public final class LuminaViewController: UIViewController { } } + public override func viewDidDisappear(_ animated: Bool) { + super.viewDidDisappear(true) + if let camera = self.camera { + camera.pause() + } + } + public override func viewWillLayoutSubviews() { super.viewWillLayoutSubviews() updateUI(orientation: UIApplication.shared.statusBarOrientation) @@ -211,15 +221,19 @@ fileprivate extension LuminaViewController { // MARK: CameraDelegate Functions extension LuminaViewController: LuminaCameraDelegate { + func finishedFocus(camera: LuminaCamera) { + self.isUpdating = false + } + func stillImageCaptured(camera: LuminaCamera, image: UIImage) { if let delegate = self.delegate { - delegate.stillImageTaken(controller: self, image: image) + delegate.detected(controller: self, stillImage: image) } } func videoFrameCaptured(camera: LuminaCamera, frame: UIImage) { if let delegate = self.delegate { - delegate.videoFrameCaptured(controller: self, frame: frame) + delegate.detected(controller: self, videoFrame: frame) } } } @@ -229,7 +243,7 @@ extension LuminaViewController: LuminaCameraDelegate { fileprivate extension LuminaViewController { @objc func cancelButtonTapped() { if let delegate = self.delegate { - delegate.cancelButtonTapped(controller: self) + delegate.cancelled(controller: self) } } @@ -258,3 +272,49 @@ fileprivate extension LuminaViewController { camera.torchState = !camera.torchState } } + +// MARK: Tap to Focus Methods + +extension LuminaViewController { + private func showFocusView(at: CGPoint) { + let focusView: UIImageView = UIImageView(image: UIImage(named: "cameraFocus", in: Bundle(for: LuminaViewController.self), compatibleWith: nil)) + focusView.contentMode = .scaleAspectFit + focusView.frame = CGRect(x: 0, y: 0, width: 50, height: 50) + focusView.center = at + focusView.alpha = 0.0 + self.view.addSubview(focusView) + UIView.animate(withDuration: 0.2, animations: { + focusView.alpha = 1.0 + }, completion: { complete in + UIView.animate(withDuration: 1.0, animations: { + focusView.alpha = 0.0 + }, completion: { final in + focusView.removeFromSuperview() + self.isUpdating = false + }) + }) + } + + override public func touchesBegan(_ touches: Set, with event: UIEvent?) { + if self.isUpdating == true { + return + } else { + self.isUpdating = true + } + for touch in touches { + let point = touch.location(in: touch.view) + let focusX = point.x/UIScreen.main.bounds.size.width + let focusY = point.y/UIScreen.main.bounds.size.height + guard let camera = self.camera else { + return + } + camera.handleFocus(at: CGPoint(x: focusX, y: focusY)) + showFocusView(at: point) + let deadlineTime = DispatchTime.now() + .seconds(1) + DispatchQueue.main.asyncAfter(deadline: deadlineTime) { + camera.resetCameraToContinuousExposureAndFocus() + } + } + } +} + diff --git a/LuminaSample/LuminaSample/ViewController.swift b/LuminaSample/LuminaSample/ViewController.swift index f53b962..d3d25ad 100644 --- a/LuminaSample/LuminaSample/ViewController.swift +++ b/LuminaSample/LuminaSample/ViewController.swift @@ -29,17 +29,21 @@ extension ViewController { //MARK: IBActions } extension ViewController: LuminaDelegate { - func cancelButtonTapped(controller: LuminaViewController) { - controller.dismiss(animated: true, completion: nil) - } - - func stillImageTaken(controller: LuminaViewController, image: UIImage) { + func detected(controller: LuminaViewController, stillImage: UIImage) { controller.dismiss(animated: true) { print("image received - check debugger") } } - func videoFrameCaptured(controller: LuminaViewController, frame: UIImage) { - print("frame received") + func detected(controller: LuminaViewController, videoFrame: UIImage) { + print("video frame received") + } + + func detected(controller: LuminaViewController, metadata: [Any]) { + print(metadata) + } + + func cancelled(controller: LuminaViewController) { + controller.dismiss(animated: true, completion: nil) } } From 5830b2a6138d5e6ec144ba0b2f873cafd381153a Mon Sep 17 00:00:00 2001 From: David-Okun Date: Tue, 19 Sep 2017 10:53:00 -0500 Subject: [PATCH 5/7] now detecting metadata, not drawing borders --- Lumina/Lumina/LuminaCamera.swift | 40 +++++++++++++++++++ Lumina/Lumina/LuminaViewController.swift | 14 +++++++ .../LuminaSample/Base.lproj/Main.storyboard | 34 ---------------- .../LuminaSample/ViewController.swift | 2 +- 4 files changed, 55 insertions(+), 35 deletions(-) diff --git a/Lumina/Lumina/LuminaCamera.swift b/Lumina/Lumina/LuminaCamera.swift index 49a5d93..408335e 100644 --- a/Lumina/Lumina/LuminaCamera.swift +++ b/Lumina/Lumina/LuminaCamera.swift @@ -13,6 +13,7 @@ protocol LuminaCameraDelegate { func stillImageCaptured(camera: LuminaCamera, image: UIImage) func videoFrameCaptured(camera: LuminaCamera, frame: UIImage) func finishedFocus(camera: LuminaCamera) + func detected(camera: LuminaCamera, metadata: [Any]) } class LuminaCamera: NSObject { @@ -54,6 +55,15 @@ class LuminaCamera: NSObject { } } + var trackMetadata = false { + didSet { + if self.session.isRunning { + self.session.stopRunning() + update() + } + } + } + var position: CameraPosition = .unspecified { didSet { if self.session.isRunning { @@ -73,6 +83,7 @@ class LuminaCamera: NSObject { } fileprivate var videoInput: AVCaptureDeviceInput? fileprivate var videoBufferQueue = DispatchQueue(label: "com.Lumina.videoBufferQueue") + fileprivate var metadataBufferQueue = DispatchQueue(label: "com.lumina.metadataBufferQueue") fileprivate var videoOutput: AVCaptureVideoDataOutput { let output = AVCaptureVideoDataOutput() output.setSampleBufferDelegate(self, queue: videoBufferQueue) @@ -80,6 +91,17 @@ class LuminaCamera: NSObject { } fileprivate var photoOutput = AVCapturePhotoOutput() + private var _metadataOutput: AVCaptureMetadataOutput? + fileprivate var metadataOutput: AVCaptureMetadataOutput { + if let existingOutput = _metadataOutput { + return existingOutput + } + let output = AVCaptureMetadataOutput() + output.setMetadataObjectsDelegate(self, queue: metadataBufferQueue) + _metadataOutput = output + return output + } + func getPreviewLayer() -> AVCaptureVideoPreviewLayer? { guard let controller = self.controller else { return nil @@ -126,12 +148,19 @@ class LuminaCamera: NSObject { guard self.session.canAddOutput(self.photoOutput) else { return } + guard self.session.canAddOutput(self.metadataOutput) else { + return + } self.videoInput = input self.session.addInput(input) if self.streamFrames { self.session.addOutput(self.videoOutput) } self.session.addOutput(self.photoOutput) + if self.trackMetadata { + self.session.addOutput(self.metadataOutput) + self.metadataOutput.metadataObjectTypes = self.metadataOutput.availableMetadataObjectTypes + } self.session.commitConfiguration() self.session.startRunning() } @@ -295,3 +324,14 @@ extension CMSampleBuffer { } } +extension LuminaCamera: AVCaptureMetadataOutputObjectsDelegate { + func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) { + guard case self.trackMetadata = true else { + return + } + DispatchQueue.main.async { + self.delegate.detected(camera: self, metadata: metadataObjects) + } + } +} + diff --git a/Lumina/Lumina/LuminaViewController.swift b/Lumina/Lumina/LuminaViewController.swift index 348dc8e..e0d5501 100644 --- a/Lumina/Lumina/LuminaViewController.swift +++ b/Lumina/Lumina/LuminaViewController.swift @@ -112,6 +112,14 @@ public final class LuminaViewController: UIViewController { } } + open var trackMetadata = false { + didSet { + if let camera = self.camera { + camera.trackMetadata = trackMetadata + } + } + } + open var textPrompt = "" { didSet { self.textPromptView.updateText(to: textPrompt) @@ -236,6 +244,12 @@ extension LuminaViewController: LuminaCameraDelegate { delegate.detected(controller: self, videoFrame: frame) } } + + func detected(camera: LuminaCamera, metadata: [Any]) { + if let delegate = self.delegate { + delegate.detected(controller: self, metadata: metadata) + } + } } // MARK: UIButton Functions diff --git a/LuminaSample/LuminaSample/Base.lproj/Main.storyboard b/LuminaSample/LuminaSample/Base.lproj/Main.storyboard index 677bae1..077b88e 100644 --- a/LuminaSample/LuminaSample/Base.lproj/Main.storyboard +++ b/LuminaSample/LuminaSample/Base.lproj/Main.storyboard @@ -153,39 +153,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - @@ -202,7 +169,6 @@ - diff --git a/LuminaSample/LuminaSample/ViewController.swift b/LuminaSample/LuminaSample/ViewController.swift index d3d25ad..ff942b2 100644 --- a/LuminaSample/LuminaSample/ViewController.swift +++ b/LuminaSample/LuminaSample/ViewController.swift @@ -14,7 +14,6 @@ class ViewController: UITableViewController { @IBOutlet weak var trackImagesSwitch: UISwitch! @IBOutlet weak var trackMetadataSwitch: UISwitch! @IBOutlet weak var showTextPromptViewSwitch: UISwitch! - @IBOutlet weak var drawMetadataBorders: UISwitch! } extension ViewController { //MARK: IBActions @@ -24,6 +23,7 @@ extension ViewController { //MARK: IBActions camera.position = self.frontCameraSwitch.isOn ? .front : .back camera.streamFrames = self.trackImagesSwitch.isOn camera.textPrompt = self.showTextPromptViewSwitch.isOn ? "This is how to test the text prompt view" : "" + camera.trackMetadata = self.trackMetadataSwitch.isOn present(camera, animated: true, completion: nil) } } From 0d4602b4ddea928c7549da5d98a4481aec297192 Mon Sep 17 00:00:00 2001 From: David-Okun Date: Tue, 19 Sep 2017 11:27:00 -0500 Subject: [PATCH 6/7] Updated shutter button location after rotation : --- Lumina/Lumina/LuminaViewController.swift | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Lumina/Lumina/LuminaViewController.swift b/Lumina/Lumina/LuminaViewController.swift index e0d5501..6b468d2 100644 --- a/Lumina/Lumina/LuminaViewController.swift +++ b/Lumina/Lumina/LuminaViewController.swift @@ -166,6 +166,10 @@ public final class LuminaViewController: UIViewController { updateButtonFrames() } + override public var prefersStatusBarHidden: Bool { + return true + } + open class func getVersion() -> String? { let bundle = Bundle(for: LuminaViewController.self) guard let infoDictionary = bundle.infoDictionary else { @@ -203,7 +207,11 @@ fileprivate extension LuminaViewController { func updateButtonFrames() { self.cancelButton.center = CGPoint(x: self.view.frame.minX + 55, y: self.view.frame.maxY - 45) - self.shutterButton.center = CGPoint(x: self.view.frame.midX, y: self.view.frame.maxY - 45) + if self.view.frame.width > self.view.frame.height { + self.shutterButton.center = CGPoint(x: self.view.frame.maxX - 45, y: self.view.frame.midY) + } else { + self.shutterButton.center = CGPoint(x: self.view.frame.midX, y: self.view.frame.maxY - 45) + } self.switchButton.center = CGPoint(x: self.view.frame.maxX - 25, y: self.view.frame.minY + 25) self.torchButton.center = CGPoint(x: self.view.frame.minX + 25, y: self.view.frame.minY + 25) self.textPromptView.center = CGPoint(x: self.view.frame.midX, y: self.view.frame.minY + 95) From 38509fde3473e2acefd13f82eb2ccb3598066300 Mon Sep 17 00:00:00 2001 From: David-Okun Date: Tue, 19 Sep 2017 11:35:03 -0500 Subject: [PATCH 7/7] updated header comments and fixed os_activity_mode issue --- .../xcshareddata/xcschemes/Lumina.xcscheme | 7 +++++++ Lumina/Lumina/Lumina.h | 2 +- Lumina/Lumina/LuminaButton.swift | 2 +- Lumina/Lumina/LuminaCamera.swift | 2 +- Lumina/Lumina/LuminaMetadataBorderView.swift | 2 +- Lumina/Lumina/LuminaTextPromptView.swift | 18 +----------------- Lumina/Lumina/LuminaViewController.swift | 2 +- .../xcschemes/LuminaSample.xcscheme | 7 +++++++ LuminaSample/LuminaSample/AppDelegate.swift | 2 +- LuminaSample/LuminaSample/ViewController.swift | 2 +- 10 files changed, 22 insertions(+), 24 deletions(-) diff --git a/Lumina/Lumina.xcodeproj/xcshareddata/xcschemes/Lumina.xcscheme b/Lumina/Lumina.xcodeproj/xcshareddata/xcschemes/Lumina.xcscheme index 0ff8eff..3e414a8 100644 --- a/Lumina/Lumina.xcodeproj/xcshareddata/xcschemes/Lumina.xcscheme +++ b/Lumina/Lumina.xcodeproj/xcshareddata/xcschemes/Lumina.xcscheme @@ -72,6 +72,13 @@ ReferencedContainer = "container:Lumina.xcodeproj"> + + + + diff --git a/Lumina/Lumina/Lumina.h b/Lumina/Lumina/Lumina.h index 33522ed..2a7d409 100644 --- a/Lumina/Lumina/Lumina.h +++ b/Lumina/Lumina/Lumina.h @@ -2,7 +2,7 @@ // Lumina.h // Lumina // -// Created by David Okun IBM on 4/21/17. +// Created by David Okun on 4/21/17. // Copyright © 2017 David Okun. All rights reserved. // diff --git a/Lumina/Lumina/LuminaButton.swift b/Lumina/Lumina/LuminaButton.swift index 159a1a5..c81aebd 100644 --- a/Lumina/Lumina/LuminaButton.swift +++ b/Lumina/Lumina/LuminaButton.swift @@ -2,7 +2,7 @@ // LuminaButton.swift // Lumina // -// Created by David Okun IBM on 9/11/17. +// Created by David Okun on 9/11/17. // Copyright © 2017 David Okun. All rights reserved. // diff --git a/Lumina/Lumina/LuminaCamera.swift b/Lumina/Lumina/LuminaCamera.swift index 408335e..9bd5b81 100644 --- a/Lumina/Lumina/LuminaCamera.swift +++ b/Lumina/Lumina/LuminaCamera.swift @@ -2,7 +2,7 @@ // Camera.swift // CameraFramework // -// Created by David Okun IBM on 8/31/17. +// Created by David Okun on 8/31/17. // Copyright © 2017 David Okun. All rights reserved. // diff --git a/Lumina/Lumina/LuminaMetadataBorderView.swift b/Lumina/Lumina/LuminaMetadataBorderView.swift index 41063f6..70ece4b 100644 --- a/Lumina/Lumina/LuminaMetadataBorderView.swift +++ b/Lumina/Lumina/LuminaMetadataBorderView.swift @@ -2,7 +2,7 @@ // LuminaMetadataBorderView.swift // Lumina // -// Created by David Okun IBM on 5/11/17. +// Created by David Okun on 5/11/17. // Copyright © 2017 David Okun. All rights reserved. // diff --git a/Lumina/Lumina/LuminaTextPromptView.swift b/Lumina/Lumina/LuminaTextPromptView.swift index 4fe39da..95a708f 100644 --- a/Lumina/Lumina/LuminaTextPromptView.swift +++ b/Lumina/Lumina/LuminaTextPromptView.swift @@ -2,7 +2,7 @@ // LuminaTextPromptView.swift // Lumina // -// Created by David Okun IBM on 5/7/17. +// Created by David Okun on 5/7/17. // Copyright © 2017 David Okun. All rights reserved. // @@ -28,22 +28,6 @@ final class LuminaTextPromptView: UIView { self.alpha = 0.0 self.layer.cornerRadius = 5.0 } -// -// public override init(frame: CGRect) { -// super.init(frame: frame) -// self.textLabel = UILabel(frame: CGRect(origin: CGPoint(x: 5, y: 5), size: CGSize(width: frame.width - 10, height: frame.height - 10))) -// self.textLabel.backgroundColor = UIColor.clear -// self.textLabel.textColor = UIColor.white -// self.textLabel.textAlignment = .center -// self.textLabel.font = UIFont.systemFont(ofSize: 20) -// self.textLabel.numberOfLines = 3 -// self.textLabel.minimumScaleFactor = 10/UIFont.labelFontSize -// self.textLabel.adjustsFontSizeToFitWidth = true -// self.addSubview(textLabel) -// self.backgroundColor = UIColor.blue -// self.alpha = 0.0 -// self.layer.cornerRadius = 5.0 -// } public func updateText(to text:String) { if text.isEmpty { diff --git a/Lumina/Lumina/LuminaViewController.swift b/Lumina/Lumina/LuminaViewController.swift index 6b468d2..cd85f00 100644 --- a/Lumina/Lumina/LuminaViewController.swift +++ b/Lumina/Lumina/LuminaViewController.swift @@ -2,7 +2,7 @@ // CameraViewController.swift // CameraFramework // -// Created by David Okun IBM on 8/29/17. +// Created by David Okun on 8/29/17. // Copyright © 2017 David Okun. All rights reserved. // diff --git a/LuminaSample/LuminaSample.xcodeproj/xcshareddata/xcschemes/LuminaSample.xcscheme b/LuminaSample/LuminaSample.xcodeproj/xcshareddata/xcschemes/LuminaSample.xcscheme index 25b65b6..5ae117e 100644 --- a/LuminaSample/LuminaSample.xcodeproj/xcshareddata/xcschemes/LuminaSample.xcscheme +++ b/LuminaSample/LuminaSample.xcodeproj/xcshareddata/xcschemes/LuminaSample.xcscheme @@ -63,6 +63,13 @@ ReferencedContainer = "container:LuminaSample.xcodeproj"> + + + + diff --git a/LuminaSample/LuminaSample/AppDelegate.swift b/LuminaSample/LuminaSample/AppDelegate.swift index e881167..963ef82 100644 --- a/LuminaSample/LuminaSample/AppDelegate.swift +++ b/LuminaSample/LuminaSample/AppDelegate.swift @@ -2,7 +2,7 @@ // AppDelegate.swift // LuminaSample // -// Created by David Okun IBM on 4/21/17. +// Created by David Okun on 4/21/17. // Copyright © 2017 David Okun. All rights reserved. // diff --git a/LuminaSample/LuminaSample/ViewController.swift b/LuminaSample/LuminaSample/ViewController.swift index ff942b2..adfaeba 100644 --- a/LuminaSample/LuminaSample/ViewController.swift +++ b/LuminaSample/LuminaSample/ViewController.swift @@ -2,7 +2,7 @@ // ViewController.swift // LuminaSample // -// Created by David Okun IBM on 4/21/17. +// Created by David Okun on 4/21/17. // Copyright © 2017 David Okun. All rights reserved. //