swift 实现可以对焦、手电筒的自定义相机

需求:swift 实现可以对焦、手电筒的自定义相机 

主要是如何获取清晰图片,获取到图片后进行裁剪,至于裁剪的是正方形,还是其他比例,自己根据自己的需求做调整即可

 

主要思路

1、布局UI

2、检测权限

3、开始会话

4、对焦,打开手电,拍出喜欢的图片

5、裁剪图片

6、获取后回调,并停止会话

7、返回,没有了

话不多说,(代码及文档)大佬请看代码

import UIKit
import AVFoundation
// 拍照后的回调
protocol DhCameraVCDelegate: class {
    func didSelectedImage( _ image: UIImage)
}

class DhCameraVC: DhViewController ,UIGestureRecognizerDelegate{

    weak var delegate: DhCameraVCDelegate?
    var guideImageView: UIImageView!
    var cameraPreviewView: UIView!
    var captureButton: UIButton!
    var focusView: UIView!
    var chongPaiButton: UIButton!
    var queDingButton: UIButton!
    var shouDianButton: UIButton!
    var istorchOn : Bool!
    var captureSession = AVCaptureSession()
    var previewLayer: CALayer!
    var captureDevice: AVCaptureDevice!
    /// 当用户单击click photo按钮时,这将为真
    var takePhoto = false
    
    ///
    override func viewDidLoad() {
        super.viewDidLoad()
        self.navItem.title = "自定义相机拍照"
        cameraPreviewView = UIView().then{
            $0.frame = CGRect(x:0, y: NAVH, width: WIN_WIDTH, height: WIN_WIDTH)
        }
        self.view .addSubview(cameraPreviewView)
        // 设置三等分的线
        let ww  = (WIN_WIDTH - 2) / 3.0
        let linView0 = UIView().then{
            $0.backgroundColor = .white
            $0.frame = CGRect(x:ww, y: NAVH, width: 1, height: WIN_WIDTH)
        }
        self.view.addSubview(linView0)
        
        let linView1 = UIView().then{
            $0.backgroundColor = .white
            $0.frame = CGRect(x:ww * 2 + 1, y: NAVH, width: 1, height: WIN_WIDTH)
        }
        self.view.addSubview(linView1)
        
        let linView2 = UIView().then{
            $0.backgroundColor = .white
            $0.frame = CGRect(x:0, y: NAVH + ww, width: WIN_WIDTH, height: 1)
        }
        self.view.addSubview(linView2)
        
        let linView3 = UIView().then{
            $0.backgroundColor = .white
            $0.frame = CGRect(x:0, y:NAVH + ww * 2 + 1, width: WIN_WIDTH, height: 1)
        }
        self.view.addSubview(linView3)
        
        // 预览UIImageView
        guideImageView = UIImageView().then{
            $0.frame = CGRect(x:0, y: NAVH, width: WIN_WIDTH, height: WIN_WIDTH)
        }
        self.view .addSubview(guideImageView)
        
        // 拍照btn
        captureButton = UIButton.buttonWith(imageName:"", titleColor: .white, titleFont: fontMedium_size(16), backgroundColor: C1, title: "拍照")
        captureButton.frame = CGRect(x: WIN_WIDTH * 0.5 - 25, y: cameraPreviewView.bottom + 2 * NAVH, width: 50, height: 40)
        self.view .addSubview(captureButton)
        captureButton.addTarget(self, action: #selector(didTapClick), for: .touchUpInside)
        captureButton.layer.cornerRadius = 2
        
        // 重拍btn
        chongPaiButton = UIButton.buttonWith(imageName:"", titleColor: .white, titleFont: fontMedium_size(16), backgroundColor: C1, title: "重拍")
        chongPaiButton.frame = CGRect(x: Interval, y: cameraPreviewView.bottom + 2 * NAVH, width: 50, height: 40)
        self.view .addSubview(chongPaiButton)
        chongPaiButton.addTarget(self, action: #selector(chongPaiButtonClick), for: .touchUpInside)
        chongPaiButton.layer.cornerRadius = 2
        
        // 确定btn
        queDingButton = UIButton.buttonWith(imageName:"", titleColor: .white, titleFont: fontMedium_size(16), backgroundColor: C1, title: "确定")
        queDingButton.frame = CGRect(x: WIN_WIDTH - 50 - Interval, y: cameraPreviewView.bottom + 2 * NAVH, width: 50, height: 40)
        self.view .addSubview(queDingButton)
        queDingButton.layer.cornerRadius = 2
        queDingButton.addTarget(self, action: #selector(queDingButtonClick), for: .touchUpInside)
        
        // 手电开关,在导航栏右上角
        shouDianButton = UIButton.buttonWith(imageName:"", titleColor: C1, titleFont: fontMedium_size(16), backgroundColor: nil, title: "手电开")
        shouDianButton.frame = CGRect(x: WIN_WIDTH - 70 - Interval, y: STAH, width: 70, height: 40)
        self.navigationBar.addSubview(shouDianButton)
        shouDianButton.addTarget(self, action: #selector(shouDianButtonClick), for: .touchUpInside)
        
        captureSession = AVCaptureSession()
        previewLayer = CALayer()
        takePhoto = false

        requestAuthorization() // 将请求授权,如果授权则启动相机
        
    }
    
    /// 将请求授权,如果授权则启动相机。
    private func requestAuthorization() {
        switch AVCaptureDevice.authorizationStatus(for: AVMediaType.video) {
        case .authorized:
            prepareCamera()

        case .denied, .restricted, .notDetermined:
            AVCaptureDevice.requestAccess(for: AVMediaType.video, completionHandler: { (granted) in
                if !Thread.isMainThread {
                    DispatchQueue.main.async {
                        if granted {
                            self.prepareCamera()
                        } else {
                            self.showSetPrivacyAlert()
                        }
                    }
                } else {
                    if granted {
                        self.prepareCamera()
                    } else {
                        self.showSetPrivacyAlert()
                    }
                }
            })
        }
    }
    
    //
    func showSetPrivacyAlert() {
        let alert = UIAlertController(title: "无法进入相机", message: "请去设置隐私并允许此应用程序访问相机", preferredStyle: UIAlertController.Style.alert)
        alert.addAction(UIAlertAction(title: "好的", style: .default, handler: {_ in
            self.navigationController?.popToRootViewController(animated: true)
        }))
        self.present(alert, animated: true, completion: nil)
    }
    
    // 将查看主摄像头是否可访问,如果找到,将调用将可用设备指定到AVCaptureDevice的方法。
    private func prepareCamera() {
        // 重置会话 Resets the session.
        self.captureSession.sessionPreset = AVCaptureSession.Preset.photo

        if #available(iOS 10.0, *) {
            let availableDevices = AVCaptureDevice.DiscoverySession(deviceTypes: [AVCaptureDevice.DeviceType.builtInWideAngleCamera], mediaType: AVMediaType.video, position: .back).devices
            self.assignCamera(availableDevices)
        } else {
           
            if let availableDevices = AVCaptureDevice.default(for: AVMediaType.video) {
                self.assignCamera([availableDevices])
            } else {
                self.showAlert()
            }
        }
    }
    
    // 无法进入相机
    func showAlert() {
        let alert = UIAlertController(title: "无法进入相机", message: "看来你的设备要么没有摄像头要么坏了", preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: "取消", style: .cancel, handler: {_ in
            self.navigationController?.dismiss(animated: true, completion: nil)
        }))
        self.present(alert, animated: true, completion: nil)
    }
    
    /// 将AVCaptureDevice分配给指定的变量,将开始会话
    /// - Parameter availableDevices: [AVCaptureDevice]
    private func assignCamera(_ availableDevices: [AVCaptureDevice]) {
        if availableDevices.first != nil {
            captureDevice = availableDevices.first
            beginSession()
        } else {
            self.showAlert()
        }
    }
    
   // 配置相机设置并开始会话,此函数将负责在UI上显示图像
    private func beginSession() {
        do {
            let captureDeviceInput = try AVCaptureDeviceInput(device: captureDevice)
            captureSession.addInput(captureDeviceInput)
        } catch {
            print(error.localizedDescription)
        }
        let previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
        self.previewLayer = previewLayer
        self.previewLayer.frame = self.cameraPreviewView.frame
        self.previewLayer.frame.origin.y = 0
        (self.previewLayer as! AVCaptureVideoPreviewLayer).videoGravity = AVLayerVideoGravity.resizeAspectFill
        self.previewLayer.masksToBounds = true
        self.cameraPreviewView.clipsToBounds = true
        self.cameraPreviewView.layer.addSublayer(self.previewLayer)
        captureSession.startRunning()

        let dataOutput = AVCaptureVideoDataOutput()
        dataOutput.videoSettings = [((kCVPixelBufferPixelFormatTypeKey as NSString) as String):NSNumber(value:kCVPixelFormatType_32BGRA)]

        dataOutput.alwaysDiscardsLateVideoFrames = true

        if captureSession.canAddOutput(dataOutput) {
            captureSession.addOutput(dataOutput)
        }

        captureSession.commitConfiguration()

        let queue = DispatchQueue(label: "com.letsappit.camera")
        dataOutput.setSampleBufferDelegate(self, queue: queue)

        self.userinteractionToButton(true)
        
        // 对焦
        focusView = UIView(frame: CGRect(x: WIN_WIDTH * 0.5, y: NAVH + WIN_WIDTH * 0.5, width: 60, height: 60))
        focusView?.layer.borderWidth = 1
        focusView?.layer.borderColor = C1.cgColor
        if let focusView = focusView {
            self.cameraPreviewView.addSubview(focusView)
        }
        focusView?.isHidden = true
        
        let tapGesture = UITapGestureRecognizer(target: self, action: #selector(focusGesture(_:)))
        tapGesture.delegate = self
        self.cameraPreviewView.addGestureRecognizer(tapGesture)
        
        // 手电默认关闭
        istorchOn = false
    }
    
    // 对焦点击
    @objc func focusGesture(_ gesture: UITapGestureRecognizer?) {
        let point = (gesture?.location(in: gesture?.view))!
        tofocusPoint (point: point)
    }
    
    // 对焦
    func tofocusPoint (point:CGPoint){
        do {
            let focusPoint = CGPoint(x: point.y / WIN_WIDTH, y: 1 - point.x / WIN_WIDTH)
            try captureDevice.lockForConfiguration()

            if captureDevice.isFocusModeSupported(.autoFocus) {
                captureDevice.focusPointOfInterest = focusPoint
                captureDevice.focusMode = .autoFocus
            }

            if captureDevice.isExposureModeSupported(.autoExpose) {
                captureDevice.exposurePointOfInterest = focusPoint
                // 曝光量调节
                captureDevice.exposureMode = .autoExpose
            }
            captureDevice.unlockForConfiguration()
        } catch {
        }
        focusView.center = point
        focusView.isHidden = false
        weak var weakSelf = self
        UIView.animate(withDuration: 0.3, animations: {
            weakSelf!.focusView.transform = CGAffineTransform(scaleX: 1.25, y: 1.25)
        }) { finished in
            UIView.animate(withDuration: 0.5, animations: {
                weakSelf!.focusView.transform = CGAffineTransform.identity
            }) { finished in
                weakSelf!.focusView.isHidden = true
            }
        }
    }
    
    // 手电
    @objc func shouDianButtonClick(_ sender: Any) {

        do {
            try captureDevice.lockForConfiguration()

            if istorchOn {

                if captureDevice.isTorchModeSupported(.off) {
                    captureDevice.torchMode = .off
                    istorchOn = false
                    shouDianButton.setTitle("手电开", for: .normal)
                }
            } else {
                if captureDevice.isTorchModeSupported(.on) {
                    captureDevice.torchMode = .on
                    istorchOn = true
                    shouDianButton.setTitle("手电关", for: .normal)
                }
            }

            captureDevice.unlockForConfiguration()
        } catch {
        }
    }
    
    /// 从给定的CMSampleBuffer获得UIImage
    /// - Parameter buffer: CMSampleBuffer
    /// - Returns: UIImage?
    func getImageFromSampleBuffer(buffer:CMSampleBuffer, orientation: UIImage.Orientation) -> UIImage? {
         if let pixelBuffer = CMSampleBufferGetImageBuffer(buffer) {
             let ciImage = CIImage(cvPixelBuffer: pixelBuffer)
             let context = CIContext()
            let imageRect = CGRect(x: 0, y: 0, width: CVPixelBufferGetWidth(pixelBuffer), height: CVPixelBufferGetHeight(pixelBuffer))

             if let image = context.createCGImage(ciImage, from: imageRect) {
                 return UIImage(cgImage: image, scale: UIScreen.main.scale, orientation: orientation)

             }
         }
         return nil
     }

    // 停止会话
    func stopCaptureSession() {
        self.captureSession.stopRunning()

        if let inputs = captureSession.inputs as? [AVCaptureDeviceInput] {
            for input in inputs {
                self.captureSession.removeInput(input)
            }
        }
    }

    // 拍照
    @objc func didTapClick(_ sender: Any) {
        userinteractionToButton(false)
        takePhoto = true
    }
    
    private func userinteractionToButton(_ interaction: Bool) {
            captureButton.isEnabled = interaction
    }

    // 重新拍摄
    @objc func chongPaiButtonClick(_ sender: Any) {
        userinteractionToButton(true)
        takePhoto = false
        self.guideImageView.image = nil
    }
    
    // 确定回调
    @objc func queDingButtonClick(_ sender: Any) {
        
        self.stopCaptureSession()
        self.previewLayer.removeFromSuperlayer()
        let newImage = UIImage(data: self.guideImageView.image!.pngData()!)
        delegate?.didSelectedImage(newImage!)
        navigationController?.popViewController(animated: true)
    }
    
    // 检测
    deinit {
        print("DhCameraVC-deinit")
    }
}

extension DhCameraVC: AVCaptureVideoDataOutputSampleBufferDelegate {
    func captureOutput(_ captureOutput: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {

        if connection.isVideoOrientationSupported {
            connection.videoOrientation = .portrait
        }

        if takePhoto {
            takePhoto = false

            // Rotation should be unlocked to work.
            // 回正方向
            var orientation = UIImage.Orientation.up
            switch UIDevice.current.orientation {
            case .landscapeLeft:
                orientation = .left

            case .landscapeRight:
                orientation = .right

            case .portraitUpsideDown:
                orientation = .down

            default:
                orientation = .up
            }

            if let image = self.getImageFromSampleBuffer(buffer: sampleBuffer, orientation: orientation) {
                DispatchQueue.main.async {
                    if isIphoneX {
                        // func imageByCropToRect(rect:CGRect, scale:Bool) -> UIImage 为UIImage的extension 用来裁剪想要的图片
                        let newImage = image.imageByCropToRect(rect:CGRect(x: 0, y:self.cameraPreviewView.frame.origin.y - 24, width: self.cameraPreviewView.frame.size.width, height: self.cameraPreviewView.frame.size.height) , scale: true)
                        self.guideImageView.image = newImage
                    }else {
                        let newImage = image.imageByCropToRect(rect: self.cameraPreviewView.frame, scale: true)
                        self.guideImageView.image = newImage
                    }
                }
            }
        }
    }
}

UIImage的extension裁剪图片

关于UIImage的extension 用来裁剪想要的图片见代码,如果你有UIImage的extension了,把这个func放进去就可以了

extension UIImage {
    func imageByCropToRect(rect:CGRect, scale:Bool) -> UIImage {
        var rect = rect
        var scaleFactor: CGFloat = 1.0
        if scale  {
            scaleFactor = self.scale
            rect.origin.x *= scaleFactor
            rect.origin.y *= scaleFactor
            rect.size.width *= scaleFactor
            rect.size.height *= scaleFactor
        }

        var image: UIImage? = nil;
        if rect.size.width > 0 && rect.size.height > 0 {
            let imageRef = self.cgImage!.cropping(to: rect)
            image = UIImage(cgImage: imageRef!, scale: scaleFactor, orientation: self.imageOrientation)
        }

        return image!
    }
}

打完收工!

 

posted @ 2020-11-09 17:27  甘林梦  阅读(1115)  评论(0编辑  收藏  举报