[Swift通天遁地]八、媒体与动画,4给相机添加CoreImage滤镜效果

★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★

➤微信公众号:山青咏芝(shanqingyongzhi)

➤博客园地址:山青咏芝(https://www.cnblogs.com/strengthen/

➤GitHub地址:https://github.com/strengthen/LeetCode

➤原文地址:https://www.cnblogs.com/strengthen/p/10353871.html

➤如果链接不是山青咏芝的博客园地址,则可能是爬取作者的文章。

➤原文已修改更新!强烈建议点击原文地址阅读!支持作者!支持原创!

★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★

热烈欢迎,请直接点击!!!

进入博主App Store主页,下载使用各个作品!!!

注:博主将坚持每月上线一个新app!!!

目录:[Swift]通天遁地Swift

本文将演示如何给相机添加实时的滤镜效果。

首先打开项目的配置文件【Info.plist】,在空白区域点击鼠标右键,弹出右键菜单。

选择【Add Row】添加行命令,添加一行配置选项。

在【Key】键输入框输入相机的访问标识:【Application Category】

在【Value】值输入框输入当应用程序访问相机设备时的提示语:

【Requires access to the camera】

在左侧的项目导航区,打开视图控制器的代码文件【ViewController.swift】

现在开始编写代码,在应用程序中使用相机设备,并给相机添加实时滤镜。

需要使用真机进行调试。

  1 import UIKit
  2 //引入需要使用到的类库,用来添加滤镜效果。
  3 import CoreImage
  4 //引入需要使用到的类库,用来对视频的采样进行处理
  5 import AVFoundation
  6 
  7 //给当前的类添加协议,使用该协议,可以获得相机设备中的实时输出的数据流。
  8 class ViewController: UIViewController, AVCaptureVideoDataOutputSampleBufferDelegate{
  9     
 10     //添加一个滤镜,它将在代理协议中的方法中被使用,
 11     //从而对视频流实时添加滤镜效果。
 12     var filter: CIFilter!
 13     //该属性用来存储在协议方法中获得的图像。
 14     //当用户点击截图按钮时,从而获得当前视频流中的截图。
 15     var cgImage: CGImage!
 16     //该属性用来展示应用滤镜后的视频流截图
 17     var videoLayer: CALayer!
 18     //当用户点击截图按钮时,展示视频流的截图
 19     var imageView : UIImageView!
 20     //使用该属性获得相机设备的数据流
 21     var avCaptureSession: AVCaptureSession!
 22     //该属性可以将应用滤镜后的图像,转换成CGImage格式的图像,
 23     //并提交给视频层进行展示。
 24     var context: CIContext = {
 25         //返回一个指定接口的上下文对象
 26         return CIContext(eaglContext: EAGLContext(api: EAGLRenderingAPI.openGLES2)!, options: nil)
 27     }()
 28     
 29     //在视图加载完成的方法中,对部分属性进行初始化操作。
 30     override func viewDidLoad() {
 31         super.viewDidLoad()
 32         
 33         //初始化滤镜对象,该滤镜可以使用图像显示复古、暖色调的艺术风格。
 34         filter = CIFilter(name: "CIPhotoEffectTransfer")
 35         //调用生成界面的方法,对程序的界面进行初始化操作。
 36         buildUI()
 37         //对用于捕捉视频流的对象进行初始化操作
 38         buildSession()
 39     }
 40     
 41     //添加一个方法,用来创建应用程序的界面
 42     func buildUI()
 43     {
 44         //对视图层进行初始化操作
 45         videoLayer = CALayer()
 46         //设置视图层的锚点点位置在原点
 47         videoLayer.anchorPoint = CGPoint.zero
 48         //保持视图层的尺寸和骗你干嘛的尺寸相同
 49         videoLayer.bounds = view.bounds
 50         //将视图层添加到根视图的层中
 51         self.view.layer.insertSublayer(videoLayer, at: 0)
 52         
 53         //创建一个图像视图对象,该图像视图将用来展示从视频流中,
 54         //获得应用滤镜后的截图,它的尺寸也跟屏幕尺寸相同。
 55         imageView = UIImageView(frame: view.bounds)
 56         //将图像视图添加到根视图中。
 57         self.view.addSubview(imageView)
 58         
 59         //添加一个按钮,当用户点击该按钮时,获得视频流中的应用滤镜后的截图。
 60         let button = UIButton(frame: CGRect(x: 0, y: 420, width: 320, height: 60))
 61         //设置按钮在正常状态下的标题文字
 62         button.setTitle("Capture", for: .normal)
 63         //同时设置按钮的背景颜色为黑色。
 64         button.backgroundColor = UIColor.black
 65         //给按钮控件绑定点击事件
 66         button.addTarget(self, action: #selector(ViewController.captureScreen), for: .touchUpInside)
 67         //将按钮添加到根视图
 68         self.view.addSubview(button)
 69     }
 70     
 71     //添加一个方法,用来响应按钮的点击事件
 72     func buildSession()
 73     {
 74         //对获得数据流的对象进行初始化操作
 75         avCaptureSession = AVCaptureSession()
 76         //通过调用对象的开始配置方法,开始对各种参数进行配置。
 77         avCaptureSession.beginConfiguration()
 78         //设置获得质量较高的视频流和音频流。
 79         avCaptureSession.sessionPreset = AVCaptureSession.Preset.high
 80         
 81         //获得当前的相机设备
 82         let captureDevice = AVCaptureDevice.default(for: .video)
 83        //初始化一个视频捕捉设备输入对象
 84         let deviceInput = try! AVCaptureDeviceInput(device: captureDevice!)
 85         //当相机设备处于可用状态时,
 86         if avCaptureSession.canAddInput(deviceInput)
 87         {
 88             //设置视频流的输入设备为相机设备
 89             avCaptureSession.addInput(deviceInput)
 90         }
 91         
 92         //获得视频捕捉数据输出对象,
 93         //该对象用于从视频流中,获取未经压缩的帧
 94         let dataOutput = AVCaptureVideoDataOutput()
 95         //设置视频帧的格式为32位的RGBA格式
 96         dataOutput.videoSettings = ([kCVPixelBufferPixelFormatTypeKey as AnyHashable : Int(kCVPixelFormatType_32BGRA)] as! [String : Any])
 97         //设置自动丢弃由于视频延迟等因素,而造成延迟等的视频帧。
 98         dataOutput.alwaysDiscardsLateVideoFrames = true
 99         
100         //将数据输出对象,添加到视频捕捉对象的数据输出端口
101         if avCaptureSession.canAddOutput(dataOutput)
102         {
103             avCaptureSession.addOutput(dataOutput)
104         }
105         
106         //创建一个串行的任务队列
107         let queue = DispatchQueue(label: "VideoQueue", attributes: .concurrent)
108         //设置数据输出对象的采样换成代理,位当前的视图控制器对象,
109         //并使用穿好的任务队列
110         dataOutput.setSampleBufferDelegate(self, queue: queue)
111         
112         //通过调用视频捕捉对象的提交配置方法,结束对各种参数的配置。
113         avCaptureSession.commitConfiguration()
114         //调用开始运行方法,开始使用相机设备捕捉视频。
115         avCaptureSession.startRunning()
116     }
117     
118     //实现协议中的代理方法,以实时检测视频流,
119     //并给视频流实时添加滤镜。
120     func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection)
121     {
122         //添加一个自动释放池
123         autoreleasepool
124             {
125                 //将采样的流数据,转换成图像缓存对象
126                 let imgBuffer = CMSampleBufferGetImageBuffer(sampleBuffer)!
127                 //将图像缓存对象进行格式的转换,
128                 //以便给图像添加滤镜。
129                 var ciImage = CIImage(cvPixelBuffer: imgBuffer)
130                 
131                 //将数据流转换格式后,就可以应用框架中的众多滤镜。
132                 //首先设置滤镜的输入图像,为流数据转换格式后的对象。
133                 self.filter.setValue(ciImage, forKey: kCIInputImageKey)
134                 //获得应用滤镜后所输出的图像。
135                 ciImage = self.filter.outputImage!
136                 
137                 //获得当前设备的朝向
138                 let orientation = UIDevice.current.orientation
139                 //因为两种坐标系统的原点不同,所以需要对视频流中的应用滤镜后的截图,进行旋转操作。
140                 if orientation == UIDeviceOrientation.portrait
141                 {
142                     ciImage = ciImage.transformed(by: CGAffineTransform(rotationAngle: CGFloat(Double.pi / -2.0)))
143                 }
144                 //处理设备在竖立状态,主键在上的情况。
145                 else if orientation == UIDeviceOrientation.portraitUpsideDown
146                 {
147                     ciImage = ciImage.transformed(by: CGAffineTransform(rotationAngle: CGFloat(Double.pi / 2.0)))
148                 }
149                 //处理设备在横向状态,主键在右的情况。
150                 else if (orientation == UIDeviceOrientation.landscapeRight)
151                 {
152                     ciImage = ciImage.transformed(by: CGAffineTransform(rotationAngle: CGFloat(Double.pi)))
153                 }
154                 //将调整方向后的图像,赋予应用的属性,供按钮控件的点击事件使用。
155                 self.cgImage = self.context.createCGImage(ciImage, from: ciImage.extent)
156                 
157                 //返回主线程,在主线程中刷新界面上的内容,
158                 DispatchQueue.main.sync(execute:
159                     {
160                         //将视频层的内容属性,设置为应用滤镜后的图像。
161                         self.videoLayer.contents = self.cgImage
162                 })
163         }
164     }
165     
166     //添加一个方法,用来响应按钮的点击事件
167     @objc func captureScreen(_ sender: UIButton)
168     {
169         //当按钮被点击时,首先中止视频流的传递。
170         avCaptureSession.stopRunning()
171         //将用来显示时流的图层,从父层中移除。
172         videoLayer.removeFromSuperlayer()
173         //隐藏当前的按钮控件
174         sender.isHidden = true
175         
176         //设置图像视图的内容模式,图片按一定比例缩放,
177         //直到在长度或者宽度达到图像视图的边界为止。
178         imageView.contentMode = .scaleAspectFit
179         //将应用滤镜后的截图,赋予当前根视图中的图像视图,
180         //在屏幕上显示来自视频流的截图。
181         imageView.image = UIImage(cgImage: self.cgImage)
182     }
183     
184     override func didReceiveMemoryWarning() {
185         super.didReceiveMemoryWarning()
186         // Dispose of any resources that can be recreated.
187     }
188 }