iOS 自定义简单相机,前后摄像头可切换,添加滤镜

import UIKit
@objc public class YXCamera: UIView {
    private lazy var session: AVCaptureSession = {
    let session = AVCaptureSession.init()
    session.sessionPreset = .high
    return session
}()

private lazy var capLayer:AVCaptureVideoPreviewLayer = {
    let layer = AVCaptureVideoPreviewLayer.init(session: self.session)
    layer.videoGravity = .resizeAspectFill
    layer.frame = self.bounds
    return layer
}()

private var device:AVCaptureDevice?

private lazy var capInput:AVCaptureDeviceInput? = {
    if let device = AVCaptureDevice.default( .builtInWideAngleCamera, for: .video, position: .back) {
        do {
            try device.lockForConfiguration()
        } catch  {
            return nil
        }
        if device.isFocusModeSupported(.autoFocus) {
            device.focusMode = .autoFocus
        }
        device.unlockForConfiguration()
        self.device = device
        var input:AVCaptureDeviceInput?
        do {
            try input = AVCaptureDeviceInput.init(device: device)
        } catch {
            print(error)
        }
        return input
    }
    return nil
}()

private lazy var capOutput:AVCapturePhotoOutput = {
    let output = AVCapturePhotoOutput.init()
    return output
}()

private lazy var delegate:YXCameraDelgate = {
    let delegate:YXCameraDelgate = YXCameraDelgate.init()
    return delegate
}()

public override init(frame: CGRect) {
    super.init(frame: frame)
    if self.deviceStatus() != .authorized {
        print("未授权")
        return
    }
    if self.capInput == nil {
        print("捕捉设备输入异常")
        return
    }
    self.layer.addSublayer(self.capLayer)
    if self.session.canAddInput(self.capInput!) {
        self.session.addInput(self.capInput!)
    }
    if self.session.canAddOutput(self.capOutput) {
        self.session.addOutput(self.capOutput)
    }
    self.session.startRunning()
    
}

/// 设备权限
/// - Returns: 授权状态
@objc public func deviceStatus() -> AVAuthorizationStatus {
    let status = AVCaptureDevice.authorizationStatus(for: .video)
    return status
}

/// 开始拍照
@objc public func start(backImage:@escaping((_:UIImage) -> Void)) {
    self.delegate.backimage = backImage
    let setting = AVCapturePhotoSettings.init(format: [AVVideoCodecKey:AVVideoCodecType.jpeg])
 //判断设备支持 Flash,并且 Flash 可用
    if self.device!.hasFlash && self.device!.isFlashAvailable {
        if self.capOutput.supportedFlashModes.contains(.auto) {
            setting.flashMode = .auto
        }
    }
    self.capOutput.capturePhoto(with: setting, delegate: self.delegate)
}

/// 切换前后摄像头
/// - Parameter position: .front 前置  .back 后置
/// - Returns: 设备捕捉输入
@objc public func changeCam(position:AVCaptureDevice.Position) {
    if self.device!.position == position {
        return
    }
    if let device = AVCaptureDevice.default( .builtInWideAngleCamera, for: .video, position: position) {
        do {
            try device.lockForConfiguration()
        } catch  {
            print("异常",error)
            return
        }
        if device.isFocusModeSupported(.autoFocus) {
            device.focusMode = .autoFocus
        }
        device.unlockForConfiguration()
        self.device = device
        var input:AVCaptureDeviceInput?
        do {
            try input = AVCaptureDeviceInput.init(device: device)
        } catch {
            print("异常",error)
            return
        }
        if input != nil {
            //添加到session
            if self.session.inputs.contains(self.capInput!) {
                self.session.removeInput(self.capInput!)
                self.capInput = nil
            }
            self.capInput = input
            if self.session.canAddInput(self.capInput!) {
                self.session.addInput(self.capInput!)
            }
            
            //添加切换动画
            let animation:CATransition = CATransition.init()
            animation.duration = 0.5
            animation.timingFunction = CAMediaTimingFunction.init(name: .easeInEaseOut)
            animation.type = CATransitionType(rawValue: "cameraIrisHollowOpen")
            if position == .front {
                animation.subtype = .fromRight
            } else if position == .back {
                animation.subtype = .fromLeft
            }
            self.capLayer.removeAllAnimations()
            self.capLayer.add(animation, forKey: nil)
            
        } else {
            print("摄像头切换失败")
        }
    }
}

/// 打开手电筒
/// - Parameter torchMode: public enum TorchMode : Int { case off = 0 case on = 1 case auto = 2 }
@objc public func openTorch(_ torchMode:AVCaptureDevice.TorchMode) {
    if self.device!.hasTorch && self.device!.isTorchAvailable {
        if self.device!.isTorchModeSupported(torchMode) {
            do {
                try self.device!.lockForConfiguration()
            } catch {
                print("手电筒打开异常",error)
                return
            }
            self.device!.torchMode = torchMode
            self.device!.unlockForConfiguration()
        }
    } else {
        print("手电筒不可用")
    }
}

/// 判断手电筒状态
/// - Returns: true 打开状态  false 关闭状态
@objc public func isTorchActive() -> Bool {
    return self.device!.isTorchActive
}
required init?(coder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
}
}

// MARK: -- 处理AVCapturePhotoCaptureDelegate代理回调
class YXCameraDelgate: NSObjec ,AVCapturePhotoCaptureDelegate {
var backimage:((_:UIImage) -> ())?
public func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) {
    guard let data = photo.fileDataRepresentation() else { return }
    guard let image = UIImage.init(data: data) else { return }
    UIImageWriteToSavedPhotosAlbum(image, self, #selector(saveImage(image:didFinishSavingWithError:contextInfo:)), nil)
    if self.backimage != nil {
        self.backimage!(image)
    }
}
@objc private func saveImage(image: UIImage, didFinishSavingWithError error: NSError?, contextInfo: AnyObject) {
    if error != nil{
        print("保存失败",error!)
    }else{
        print("保存成功")
    }
}
}

/// 滤镜

@objc public class YXFilter: CIFilter {

/// 获取所有的滤镜
/// - Parameter category: 滤镜扩展
/// - Returns: 某个扩展里所有的滤镜
/// [kCICategoryDistortionEffect, 扭曲效果,比如bump、旋转、hole
/// kCICategoryGeometryAdjustment, 几何开着调整,比如仿射变换、平切、透视转换
/// kCICategoryCompositeOperaCIFiltertion, 合并,比如源覆盖(source over)、最小化、源在顶(source atop)、色彩混合模式
/// kCICategoryHalftoneEffect, Halftone效果,比如screen、line screen、hatched
/// kCICategoryColorAdjustment, 色彩调整,比如伽马调整、白点调整、曝光
/// kCICategoryColorEffect, 色彩效果,比如色调调整、posterize
/// kCICategoryTransition, 图像间转换,比如dissolve、disintegrate with mask、swipe
/// kCICategoryTileEffect, 瓦片效果,比如parallelogram、triangle
/// kCICategoryGenerator, 图像生成器,比如stripes、constant color、checkerboard
/// kCICategoryReduction, 一种减少图像数据的过滤器。这些过滤器是用来解决图像分析问题
/// kCICategoryGradient, 渐变,比如轴向渐变、仿射渐变、高斯渐变
/// kCICategoryStylize, 风格化,比如像素化、水晶化
/// kCICategorySharpen, 锐化、发光
/// kCICategoryBlur, 模糊,比如高斯模糊、焦点模糊、运动模糊
/// kCICategoryVideo, 能用于视频
/// kCICategoryStillImage, 能用于静态图像
/// kCICategoryInterlaced, 能用于交错图像
/// kCICategoryNonSquarePixels, 能用于非矩形像素
/// kCICategoryHighDynamicRange, 能用于HDR
/// kCICategoryBuiltIn, 获取所有coreImage 内置滤镜
/// kCICategoryFilterGenerator,  通过链接几个过滤器并将其打包为CIFilterGenerator对象而创建的过滤器]
@objc public class func filterNamesinCategory(category: String) -> [String] {
    return YXFilter.filterNames(inCategory: category)
}

/// 给图片添加滤镜
/// - Parameters:
///   - filterName: 滤镜名
///   - image: 原始图片
/// - Returns: 目标图片
@objc public class func filterToImage(filterName:String,image:UIImage) -> UIImage? {
   
    let ciImage = CIImage.init(image: image)
    let filter:YXFilter = YXFilter.init(name: filterName)!
    guard filter.inputKeys.contains("inputImage") else {
        return nil
    }
    filter.setValue(ciImage, forKey: "inputImage")
    guard let outputImage:CIImage = filter.outputImage else {
        return nil
    }
    guard let glContext:EAGLContext = EAGLContext.init(api: .openGLES2) else {
        return nil
    }
    let context:CIContext = CIContext.init(eaglContext: glContext)
    guard let cgimage:CGImage = context.createCGImage(outputImage, from: outputImage.extent) else {
        return nil
    }
    let newImage:UIImage = UIImage.init(cgImage: cgimage, scale: image.scale, orientation: image.imageOrientation)
    return newImage
}
}

Dome:https://github.com/ShaoGangGitHub/YXCamera.git

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,612评论 5 471
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,345评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,625评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,022评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,974评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,227评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,688评论 3 392
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,358评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,490评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,402评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,446评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,126评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,721评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,802评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,013评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,504评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,080评论 2 341