Created
October 23, 2018 03:58
-
-
Save saito-sv/7fe22d1bfb1eb716b7a2f1f6bf7ac5e6 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Created by Marlon Monroy on 3/3/18. | |
// Copyright © 2018 Self-in. All rights reserved. | |
// | |
import UIKit | |
import AVFoundation | |
class CroppingFrame: UIView { | |
let mover = UIView() | |
override init(frame: CGRect) { | |
super.init(frame: frame) | |
layer.borderWidth = 2 | |
layer.borderColor = UIColor.white.cgColor | |
setup() | |
} | |
required init?(coder aDecoder: NSCoder) { | |
fatalError("init(coder:) has not been implemented") | |
} | |
func setup() { | |
let sizing = UIPanGestureRecognizer(target: self, action: #selector(resize(gesture:))) | |
let move = UIPanGestureRecognizer(target: self, action: #selector(move(gesture:))) | |
addGestureRecognizer(move) | |
mover.frame = CGRect(x: bounds.width / 2 - 25, y: bounds.height - 15, width: 50, height: 15) | |
mover.addGestureRecognizer(sizing) | |
mover.layer.cornerRadius = 2 | |
mover.backgroundColor = .white | |
addSubview(mover) | |
} | |
@objc func move(gesture:UIPanGestureRecognizer) { | |
guard let parent = gesture.view?.superview else {return} | |
guard let view = gesture.view else { return } | |
let parentSize = parent.bounds.size | |
let viewSize = view.bounds.size | |
let translation = gesture.translation(in: self) | |
var center = CGPoint(x:view.center.x + translation.x, y: view.center.y + translation.y) | |
var reset = CGPoint(x: translation.x, y: translation.y) | |
if center.x - viewSize.width / 2 < 0 { | |
center.x = viewSize.width / 2 | |
}else if center.x + viewSize.width / 2 > parentSize.width { | |
center.x = parentSize.width - viewSize.width / 2 | |
}else { | |
reset.x = 0 | |
} | |
if center.y - viewSize.height / 2 < 0 { | |
center.y = viewSize.height / 2 | |
}else if center.y + viewSize.height / 2 > parentSize.height { | |
center.y = parentSize.height - viewSize.height / 2 | |
}else { | |
reset.y = 0 | |
} | |
view.center = center | |
gesture.setTranslation(CGPoint(x: 0, y: 0), in: self) | |
} | |
@objc func resize(gesture:UIPanGestureRecognizer) { | |
if gesture.state == .began || gesture.state == .changed { | |
guard let view = gesture.view else { return } | |
let translation = gesture.translation(in: self) | |
let newFrame = CGRect(origin: frame.origin, size: CGSize(width: frame.size.width, height: frame.size.height + translation.y)) | |
view.superview?.frame.size.height = max(newFrame.height, 300) | |
view.frame = CGRect(x: bounds.width / 2 - 25, y: bounds.height - 15, width: 50, height: 15) | |
gesture.setTranslation(.zero, in: self) | |
} | |
} | |
internal class CropOverlay: UIView { | |
var outerLines = [UIView]() | |
var horizontalLines = [UIView]() | |
var verticalLines = [UIView]() | |
var topLeftCornerLines = [UIView]() | |
var topRightCornerLines = [UIView]() | |
var bottomLeftCornerLines = [UIView]() | |
var bottomRightCornerLines = [UIView]() | |
var cornerButtons = [UIButton]() | |
let cornerLineDepth: CGFloat = 3 | |
let cornerLineWidth: CGFloat = 22.5 | |
var cornerButtonWidth: CGFloat { | |
return self.cornerLineWidth * 2 | |
} | |
let lineWidth: CGFloat = 1 | |
let outterGapRatio: CGFloat = 1/3 | |
var outterGap: CGFloat { | |
return self.cornerButtonWidth * self.outterGapRatio | |
} | |
var isResizable: Bool = false | |
var isMovable: Bool = false | |
var minimumSize: CGSize = CGSize.zero | |
internal override init(frame: CGRect) { | |
super.init(frame: frame) | |
createLines() | |
} | |
internal required init?(coder aDecoder: NSCoder) { | |
super.init(coder: aDecoder) | |
createLines() | |
} | |
override func layoutSubviews() { | |
for i in 0..<outerLines.count { | |
let line = outerLines[i] | |
var lineFrame: CGRect | |
switch (i) { | |
case 0: | |
lineFrame = CGRect(x: outterGap, y: outterGap, width: bounds.width - outterGap * 2, height: lineWidth) | |
break | |
case 1: | |
lineFrame = CGRect(x: bounds.width - lineWidth - outterGap, y: outterGap, width: lineWidth, height: bounds.height - outterGap * 2) | |
break | |
case 2: | |
lineFrame = CGRect(x: outterGap, y: bounds.height - lineWidth - outterGap, width: bounds.width - outterGap * 2, height: lineWidth) | |
break | |
case 3: | |
lineFrame = CGRect(x: outterGap, y: outterGap, width: lineWidth, height: bounds.height - outterGap * 2) | |
break | |
default: | |
lineFrame = CGRect.zero | |
break | |
} | |
line.frame = lineFrame | |
} | |
let corners = [topLeftCornerLines, topRightCornerLines, bottomLeftCornerLines, bottomRightCornerLines] | |
for i in 0..<corners.count { | |
let corner = corners[i] | |
var horizontalFrame: CGRect | |
var verticalFrame: CGRect | |
var buttonFrame: CGRect | |
let buttonSize = CGSize(width: cornerButtonWidth, height: cornerButtonWidth) | |
switch (i) { | |
case 0: // Top Left | |
verticalFrame = CGRect(x: outterGap, y: outterGap, width: cornerLineDepth, height: cornerLineWidth) | |
horizontalFrame = CGRect(x: outterGap, y: outterGap, width: cornerLineWidth, height: cornerLineDepth) | |
buttonFrame = CGRect(origin: CGPoint(x: 0, y: 0), size: buttonSize) | |
case 1: // Top Right | |
verticalFrame = CGRect(x: bounds.width - cornerLineDepth - outterGap, y: outterGap, width: cornerLineDepth, height: cornerLineWidth) | |
horizontalFrame = CGRect(x: bounds.width - cornerLineWidth - outterGap, y: outterGap, width: cornerLineWidth, height: cornerLineDepth) | |
buttonFrame = CGRect(origin: CGPoint(x: bounds.width - cornerButtonWidth, y: 0), size: buttonSize) | |
case 2: // Bottom Left | |
verticalFrame = CGRect(x: outterGap, y: bounds.height - cornerLineWidth - outterGap, width: cornerLineDepth, height: cornerLineWidth) | |
horizontalFrame = CGRect(x: outterGap, y: bounds.height - cornerLineDepth - outterGap, width: cornerLineWidth, height: cornerLineDepth) | |
buttonFrame = CGRect(origin: CGPoint(x: 0, y: bounds.height - cornerButtonWidth), size: buttonSize) | |
case 3: // Bottom Right | |
verticalFrame = CGRect(x: bounds.width - cornerLineDepth - outterGap, y: bounds.height - cornerLineWidth - outterGap, width: cornerLineDepth, height: cornerLineWidth) | |
horizontalFrame = CGRect(x: bounds.width - cornerLineWidth - outterGap, y: bounds.height - cornerLineDepth - outterGap, width: cornerLineWidth, height: cornerLineDepth) | |
buttonFrame = CGRect(origin: CGPoint(x: bounds.width - cornerButtonWidth, y: bounds.height - cornerButtonWidth), size: buttonSize) | |
default: | |
verticalFrame = CGRect.zero | |
horizontalFrame = CGRect.zero | |
buttonFrame = CGRect.zero | |
} | |
corner[0].frame = verticalFrame | |
corner[1].frame = horizontalFrame | |
cornerButtons[i].frame = buttonFrame | |
} | |
let lineThickness = lineWidth / UIScreen.main.scale | |
let vPadding = (bounds.height - outterGap * 2 - (lineThickness * CGFloat(horizontalLines.count))) / CGFloat(horizontalLines.count + 1) | |
let hPadding = (bounds.width - outterGap * 2 - (lineThickness * CGFloat(verticalLines.count))) / CGFloat(verticalLines.count + 1) | |
for i in 0..<horizontalLines.count { | |
let hLine = horizontalLines[i] | |
let vLine = verticalLines[i] | |
let vSpacing = (vPadding * CGFloat(i + 1)) + (lineThickness * CGFloat(i)) | |
let hSpacing = (hPadding * CGFloat(i + 1)) + (lineThickness * CGFloat(i)) | |
hLine.frame = CGRect(x: outterGap, y: vSpacing + outterGap, width: bounds.width - outterGap * 2, height: lineThickness) | |
vLine.frame = CGRect(x: hSpacing + outterGap, y: outterGap, width: lineThickness, height: bounds.height - outterGap * 2) | |
} | |
} | |
func createLines() { | |
outerLines = [createLine(), createLine(), createLine(), createLine()] | |
horizontalLines = [createLine(), createLine()] | |
verticalLines = [createLine(), createLine()] | |
topLeftCornerLines = [createLine(), createLine()] | |
topRightCornerLines = [createLine(), createLine()] | |
bottomLeftCornerLines = [createLine(), createLine()] | |
bottomRightCornerLines = [createLine(), createLine()] | |
cornerButtons = [createButton(), createButton(), createButton(), createButton()] | |
let dragGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(moveCropOverlay)) | |
addGestureRecognizer(dragGestureRecognizer) | |
} | |
func createLine() -> UIView { | |
let line = UIView() | |
line.backgroundColor = UIColor.white | |
addSubview(line) | |
return line | |
} | |
func createButton() -> UIButton { | |
let button = UIButton() | |
button.backgroundColor = UIColor.clear | |
let dragGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(moveCropOverlay)) | |
button.addGestureRecognizer(dragGestureRecognizer) | |
addSubview(button) | |
return button | |
} | |
@objc func moveCropOverlay(gestureRecognizer: UIPanGestureRecognizer) { | |
if isResizable, let button = gestureRecognizer.view as? UIButton { | |
if gestureRecognizer.state == .began || gestureRecognizer.state == .changed { | |
let translation = gestureRecognizer.translation(in: self) | |
var newFrame: CGRect | |
switch button { | |
case cornerButtons[0]: // Top Left | |
newFrame = CGRect(x: frame.origin.x + translation.x, y: frame.origin.y + translation.y, width: frame.size.width - translation.x, height: frame.size.height - translation.y) | |
case cornerButtons[1]: // Top Right | |
newFrame = CGRect(x: frame.origin.x, y: frame.origin.y + translation.y, width: frame.size.width + translation.x, height: frame.size.height - translation.y) | |
case cornerButtons[2]: // Bottom Left | |
newFrame = CGRect(x: frame.origin.x + translation.x, y: frame.origin.y, width: frame.size.width - translation.x, height: frame.size.height + translation.y) | |
case cornerButtons[3]: // Bottom Right | |
newFrame = CGRect(x: frame.origin.x, y: frame.origin.y, width: frame.size.width + translation.x, height: frame.size.height + translation.y) | |
default: | |
newFrame = CGRect.zero | |
} | |
let minimumFrame = CGRect(x: newFrame.origin.x, y: newFrame.origin.y, width: max(newFrame.size.width, minimumSize.width + 2 * outterGap), height: max(newFrame.size.height, minimumSize.height + 2 * outterGap)) | |
frame = minimumFrame | |
layoutSubviews() | |
gestureRecognizer.setTranslation(CGPoint.zero, in: self) | |
} | |
} else if isMovable { | |
if gestureRecognizer.state == .began || gestureRecognizer.state == .changed { | |
let translation = gestureRecognizer.translation(in: self) | |
gestureRecognizer.view!.center = CGPoint(x: gestureRecognizer.view!.center.x + translation.x, y: gestureRecognizer.view!.center.y + translation.y) | |
gestureRecognizer.setTranslation(CGPoint(x: 0, y: 0), in: self) | |
} | |
} | |
} | |
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { | |
let view = super.hitTest(point, with: event) | |
if !isMovable && isResizable && view != nil { | |
let isButton = cornerButtons.reduce(false) { $1.hitTest(convert(point, to: $1), with: event) != nil || $0 } | |
if !isButton { | |
return nil | |
} | |
} | |
return view | |
} | |
} | |
public typealias CameraShotCompletion = (UIImage?) -> Void | |
final class ImageResizer { | |
static func resizeWith(on image:UIImage, with targetSize:CGRect) ->UIImage? { | |
let size = image.size | |
let widthRatio = targetSize.width / size.width | |
let heightRatio = targetSize.height / size.height | |
let newSize:CGSize | |
if widthRatio > heightRatio { | |
newSize = CGSize(width: size.width * heightRatio, height: size.height * heightRatio) | |
}else { | |
newSize = CGSize(width: size.width * widthRatio, height: size.height * widthRatio) | |
} | |
let rect = CGRect(x: 0, y: 0, width: newSize.width, height: newSize.height) | |
UIGraphicsBeginImageContextWithOptions(newSize, false, 0.0) | |
image.draw(in: rect) | |
let newImage = UIGraphicsGetImageFromCurrentImageContext() | |
UIGraphicsEndImageContext() | |
return newImage | |
} | |
static func resize(_ image:UIImage, with targetRect:CGRect) ->UIImage? { | |
UIGraphicsBeginImageContextWithOptions(targetRect.size, false, image.scale) | |
let origin = CGPoint(x: targetRect.origin.x * CGFloat(-1), y: targetRect.origin.y * CGFloat(-1)) | |
image.draw(at: origin) | |
let newImage = UIGraphicsGetImageFromCurrentImageContext() | |
UIGraphicsEndImageContext() | |
return newImage | |
} | |
} | |
class CameraView: UIView { | |
var camereDispatchQueue = DispatchQueue(label: "io.selfin.camera.dispatch.queue") | |
var captureSession: AVCaptureSession? | |
var videoPreviewLayer: AVCaptureVideoPreviewLayer? | |
var captureDevice:AVCaptureDevice! | |
var capturePhotoOutput: AVCapturePhotoOutput? | |
var input:AVCaptureDeviceInput! | |
var didCaptureImage:((_ :UIImage?)->())? | |
var currentImage:UIImage? | |
var cameraPosition = AVCaptureDevice.Position.back | |
var flashMode:AVCaptureDevice.FlashMode = .auto | |
let focus = CropOverlay(frame: CGRect(x: 0, y: 0, width: 80, height: 80)) | |
func startSession() { | |
do { | |
captureDevice = device(for: .back) | |
input = try AVCaptureDeviceInput(device: captureDevice) | |
captureSession = AVCaptureSession() | |
// captureSession?.sessionPreset = .high | |
capturePhotoOutput = AVCapturePhotoOutput() | |
captureSession?.addInput(input) | |
captureSession?.addOutput(capturePhotoOutput!) | |
} catch { | |
print(error) | |
} | |
videoPreviewLayer = AVCaptureVideoPreviewLayer(session: captureSession!) | |
videoPreviewLayer?.videoGravity = .resizeAspectFill | |
videoPreviewLayer?.frame = layer.bounds | |
layer.addSublayer(videoPreviewLayer!) | |
captureSession?.startRunning() | |
} | |
func stopSession() { | |
captureSession?.stopRunning() | |
videoPreviewLayer?.removeFromSuperlayer() | |
captureSession = nil | |
input = nil | |
capturePhotoOutput = nil | |
videoPreviewLayer = nil | |
captureDevice = nil | |
} | |
override init(frame: CGRect) { | |
super.init(frame: frame) | |
focus.isHidden = true | |
addSubview(focus) | |
setupGestures() | |
} | |
required init?(coder aDecoder: NSCoder) { | |
fatalError("init(coder:) has not been implemented") | |
} | |
private func settingOnTakePhoto() { | |
let photoSettings = AVCapturePhotoSettings() | |
guard let capturePhotoOutput = self.capturePhotoOutput else { return } | |
photoSettings.isAutoStillImageStabilizationEnabled = true | |
capturePhotoOutput.isHighResolutionCaptureEnabled = true | |
photoSettings.isHighResolutionPhotoEnabled = true | |
photoSettings.flashMode = flashMode | |
// let previewPixelType = photoSettings.availablePreviewPhotoPixelFormatTypes.first! | |
// let previewFormat = [kCVPixelBufferPixelFormatTypeKey as String: previewPixelType, | |
// kCVPixelBufferWidthKey as String: 160, | |
// kCVPixelBufferHeightKey as String: 160] | |
// photoSettings.previewPhotoFormat = previewFormat | |
capturePhotoOutput.capturePhoto(with: photoSettings, delegate: self) | |
} | |
func setupGestures() { | |
gestureRecognizers?.forEach({removeGestureRecognizer($0)}) | |
let swipe = UISwipeGestureRecognizer(target: self, action: #selector(takePhoto(gesture:))) | |
swipe.direction = .up | |
addGestureRecognizer(swipe) | |
let tap = UITapGestureRecognizer(target: self, action: #selector(focus(gesture:))) | |
addGestureRecognizer(tap) | |
let pinch = UIPinchGestureRecognizer(target: self, action: #selector(zoom(gesture:))) | |
addGestureRecognizer(pinch) | |
} | |
func toggleFlash() { | |
guard let device = captureDevice else { return } | |
if !device.hasFlash{ return } | |
switch flashMode { | |
case .auto: | |
flashMode = .off | |
case .off: | |
flashMode = .on | |
case .on: | |
flashMode = .auto | |
} | |
} | |
@objc func takePhoto(gesture:UITapGestureRecognizer) { | |
settingOnTakePhoto() | |
} | |
// focus gesture | |
@objc func focus(gesture:UITapGestureRecognizer) { | |
let point = gesture.location(in: self) | |
guard shouldFocus(on: point) else { return } | |
focus.isHidden = false | |
focus.center = point | |
focus.alpha = 0 | |
focus.transform = CGAffineTransform(scaleX: 1.2, y: 1.2) | |
bringSubview(toFront: focus) | |
UIView.animateKeyframes(withDuration: 1.5, delay: 0, options: [], animations: { | |
UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 0.15, animations: { | |
self.focus.alpha = 1 | |
self.focus.transform = CGAffineTransform.identity | |
}) | |
UIView.addKeyframe(withRelativeStartTime: 0.80, relativeDuration: 0.20, animations: { | |
self.focus.alpha = 0 | |
self.focus.transform = CGAffineTransform(scaleX: 0.5, y: 0.5) | |
}) | |
}) {if $0 { self.focus.isHidden = true} } | |
} | |
// focus | |
func shouldFocus(on point:CGPoint) -> Bool { | |
guard let device = captureDevice, let preview = videoPreviewLayer, | |
device.isFocusModeSupported(.continuousAutoFocus) else { return false} | |
do { try device.lockForConfiguration()} catch { | |
return false | |
} | |
let pFocus = preview.captureDevicePointConverted(fromLayerPoint: point) | |
device.focusPointOfInterest = pFocus | |
device.focusMode = .continuousAutoFocus | |
device.exposurePointOfInterest = pFocus | |
device.exposureMode = .continuousAutoExposure | |
device.unlockForConfiguration() | |
return true | |
} | |
@objc func zoom(gesture:UIPinchGestureRecognizer) { | |
guard let device = captureDevice else { fatalError()} | |
let velocity = gesture.velocity | |
let velocityFactor:CGFloat = 13.0 | |
let desiredZoomFactor = device.videoZoomFactor + atan2(velocity,velocityFactor) | |
let scaleFactor = minMaxZoom(factor: desiredZoomFactor) | |
switch gesture.state { | |
case .began,.changed: | |
update(scale: scaleFactor) | |
default: | |
break | |
} | |
} | |
private func minMaxZoom(factor:CGFloat) ->CGFloat { | |
guard let device = captureDevice else { fatalError()} | |
return min(max(factor, 1.0), device.activeFormat.videoMaxZoomFactor) | |
} | |
private func update(scale factor:CGFloat){ | |
guard let device = captureDevice else { fatalError()} | |
do { try device.lockForConfiguration() | |
defer { device.unlockForConfiguration() } | |
device.videoZoomFactor = factor | |
} catch { | |
fatalError("\(error.localizedDescription)") | |
} | |
} | |
func device(for position:AVCaptureDevice.Position) -> AVCaptureDevice? { | |
let device = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: position) | |
return device | |
} | |
func flip() { | |
guard let s = captureSession, let currentInput = input else { return } | |
s.beginConfiguration() | |
s.removeInput(currentInput) | |
if currentInput.device.position == .back { | |
cameraPosition = .front | |
captureDevice = device(for: cameraPosition) | |
}else { | |
cameraPosition = .back | |
captureDevice = device(for: cameraPosition) | |
} | |
guard let new = try? AVCaptureDeviceInput(device:captureDevice) else { | |
print("something wrong") | |
return | |
} | |
input = new | |
s.addInput(new) | |
s.commitConfiguration() | |
} | |
func rotateView() { | |
guard videoPreviewLayer != nil else{ return } | |
switch UIApplication.shared.statusBarOrientation { | |
case .portrait: | |
videoPreviewLayer?.connection?.videoOrientation = .portrait | |
case .portraitUpsideDown: | |
videoPreviewLayer?.connection?.videoOrientation = .portraitUpsideDown | |
case .landscapeRight: | |
videoPreviewLayer?.connection?.videoOrientation = .landscapeRight | |
case .landscapeLeft: | |
videoPreviewLayer?.connection?.videoOrientation = .landscapeLeft | |
default: break | |
} | |
} | |
} | |
extension CameraView:AVCapturePhotoCaptureDelegate { | |
func photoOutput(_ captureOutput: AVCapturePhotoOutput, | |
didFinishProcessingPhoto photoSampleBuffer: CMSampleBuffer?, | |
previewPhoto previewPhotoSampleBuffer: CMSampleBuffer?, | |
resolvedSettings: AVCaptureResolvedPhotoSettings, | |
bracketSettings: AVCaptureBracketedStillImageSettings?, | |
error: Error?) { | |
guard error == nil, | |
let photoSampleBuffer = photoSampleBuffer else { | |
print("Error capturing photo: \(String(describing: error))") | |
return | |
} | |
guard let imageData = | |
AVCapturePhotoOutput.jpegPhotoDataRepresentation(forJPEGSampleBuffer: photoSampleBuffer, previewPhotoSampleBuffer: previewPhotoSampleBuffer) else { | |
return | |
} | |
var image = UIImage.init(data: imageData , scale: 1)! | |
// flip the image to match the orientation of the preview | |
if cameraPosition == .front, let cgImage = image.cgImage { | |
switch image.imageOrientation { | |
case .leftMirrored: | |
image = UIImage(cgImage: cgImage, scale: image.scale, orientation: .right) | |
case .left: | |
image = UIImage(cgImage: cgImage, scale: image.scale, orientation: .rightMirrored) | |
case .rightMirrored: | |
image = UIImage(cgImage: cgImage, scale: image.scale, orientation: .left) | |
case .right: | |
image = UIImage(cgImage: cgImage, scale: image.scale, orientation: .leftMirrored) | |
case .up: | |
image = UIImage(cgImage: cgImage, scale: image.scale, orientation: .upMirrored) | |
case .upMirrored: | |
image = UIImage(cgImage: cgImage, scale: image.scale, orientation: .up) | |
case .down: | |
image = UIImage(cgImage: cgImage, scale: image.scale, orientation: .downMirrored) | |
case .downMirrored: | |
image = UIImage(cgImage: cgImage, scale: image.scale, orientation: .down) | |
} | |
} | |
currentImage = ImageResizer.resizeWith(on: image, with:frame) | |
didCaptureImage?(currentImage) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment