zoukankan      html  css  js  c++  java
  • 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,  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,  1, height: WIN_WIDTH)
            }
            self.view.addSubview(linView0)
            
            let linView1 = UIView().then{
                $0.backgroundColor = .white
                $0.frame = CGRect(x:ww * 2 + 1, y: NAVH,  1, height: WIN_WIDTH)
            }
            self.view.addSubview(linView1)
            
            let linView2 = UIView().then{
                $0.backgroundColor = .white
                $0.frame = CGRect(x:0, y: NAVH + ww,  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,  WIN_WIDTH, height: 1)
            }
            self.view.addSubview(linView3)
            
            // 预览UIImageView
            guideImageView = UIImageView().then{
                $0.frame = CGRect(x:0, y: NAVH,  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,  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,  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,  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,  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,  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,  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,  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!
        }
    }

    打完收工!

  • 相关阅读:
    Linux/UNIX线程(1)
    jeecms 链接标签
    JEECMS 系统权限设计
    jeecms 前台拦截器的研究与改造
    jeecms系统_自定义对象流程
    jeecms技术预研
    jeecms获取绝对路径
    JEECMS自定义标签
    jeecms项目相关配置文件
    [jeecms]获取父栏目下的子栏目名称
  • 原文地址:https://www.cnblogs.com/ljcgood66/p/13949650.html
Copyright © 2011-2022 走看看