camera --> basicOperation --> renderView
左边的参数遵循ImageSource
协议,作为数据的输入,右边的参数遵循ImageConsumer
协议,作为数据的输出。这里的basicOperation
是BasicOperation
的一个实例,其父类ImageProcessingOperation
同时遵循ImageSource
和ImageConsumer
协议,所以它可以放在-->
的左边或右边。
-->
的运算是左结合的,类似于GPUImage
中的addTarget
方法,但是-->
有一个返回值,就是右边的参数。在上面的示例中,先计算了前半部camera --> basicOperation
,然后右边的参数basicOperation
作为返回值又参与了后半部basicOperation --> renderView
的计算。
-->
体现了链式编程的思想,让代码更加优雅,在GPUImage2
有着大量运用,这得益于Swift
强大的语法,关于Swift
中的高级运算符,请看这里。
GPUImage2
主要提供了这些功能:
处理静态图片
实时视频滤镜
从视频中捕获图片
编写自定义的图像处理操作
从静态图片中捕获并添加滤镜(即将实现)
添加滤镜并转码视频(即将实现)
导入头文件
import GPUImage
import AVFoundation
var camera: Camera!
var basicOperation: BasicOperation!
var renderView: RenderView!
lazy var imageView: UIImageView = {
let imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height))
imageView.image = UIImage(contentsOfFile: Bundle.main.path(forResource: "Yui", ofType: "jpg")!)
imageView.contentMode = .scaleAspectFit
return imageView
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(imageView)
处理静态图片
创建滤镜实例
// 创建一个BrightnessAdjustment颜色处理滤镜
let brightnessAdjustment = BrightnessAdjustment()
brightnessAdjustment.brightness = 0.2
// 创建一个ExposureAdjustment颜色处理滤镜
let exposureAdjustment = ExposureAdjustment()
exposureAdjustment.exposure = 0.5
使用GPUImage
对UIImage
提供的扩展方法进行便利滤镜处理
// 1.使用GPUImage对UIImage的扩展方法进行滤镜处理
var filteredImage: UIImage
// 1.1单一滤镜
filteredImage = imageView.image!.filterWithOperation(brightnessAdjustment)
// 1.2多个滤镜叠加
filteredImage = imageView.image!.filterWithPipeline { (input, output) in
input --> brightnessAdjustment --> exposureAdjustment --> output
// 不建议的
imageView.image = filteredImage
注意:如果要将图片显示在屏幕上或者进行多次滤镜处理时,以上方法会让Core Graphics
产生更多开销,建议使用处理链,最后指向RenderView
来显示,如下:
// 2.使用管道处理
// 创建图片输入
let pictureInput = PictureInput(image: imageView.image!)
// 创建图片输出
let pictureOutput = PictureOutput()
// 给闭包赋值
pictureOutput.imageAvailableCallback = { image in
// 这里的image是处理完的数据,UIImage类型
// 绑定处理链
pictureInput --> brightnessAdjustment --> exposureAdjustment --> pictureOutput
// 开始处理 synchronously: true 同步执行 false 异步执行,处理完毕后会调用imageAvailableCallback这个闭包
pictureInput.processImage(synchronously: true)
你可以将若干个BasicOperation
的实例包装成一个OperationGroup
操作组,通过给闭包赋值来定义组内滤镜的处理流程,外部可以将OperationGroup
的实例作为一个独立单位参与其他滤镜处理。
// MARK: - 操作组
func operationGroup() {
// 创建一个BrightnessAdjustment颜色处理滤镜
let brightnessAdjustment = BrightnessAdjustment()
brightnessAdjustment.brightness = 0.2
// 创建一个ExposureAdjustment颜色处理滤镜
let exposureAdjustment = ExposureAdjustment()
exposureAdjustment.exposure = 0.5
// 创建一个操作组
let operationGroup = OperationGroup()
// 给闭包赋值,绑定处理链
operationGroup.configureGroup{input, output in
input --> brightnessAdjustment --> exposureAdjustment --> output
// 进行滤镜处理
imageView.image = imageView.image!.filterWithOperation(operationGroup)
实时视频滤镜
从相机中获取图像数据,经过滤镜处理后实时的显示在屏幕上。
// MARK: - 实时视频滤镜
func CameraFiltering() {
// Camera的构造函数是可抛出错误的
// 创建一个Camera的实例,Camera遵循ImageSource协议,用来从相机捕获数据
/// Camera的指定构造器
/// - Parameters:
/// - sessionPreset: 捕获视频的分辨率
/// - cameraDevice: 相机设备,默认为nil
/// - location: 前置相机还是后置相机,默认为.backFacing
/// - captureAsYUV: 是否采集为YUV颜色编码,默认为true
/// - Throws: AVCaptureDeviceInput构造错误
camera = try Camera(sessionPreset: AVCaptureSessionPreset1280x720,
cameraDevice: nil,
location: .backFacing,
captureAsYUV: true)
// Camera的指定构造器是有默认参数的,可以只传入sessionPreset参数
// camera = try Camera(sessionPreset: AVCaptureSessionPreset1280x720)
} catch {
print(error)
return
// 创建一个Luminance颜色处理滤镜
basicOperation = Luminance()
// 创建一个RenderView的实例并添加到view上,用来显示最终处理出的内容
renderView = RenderView(frame: view.bounds)
view.addSubview(renderView)
// 绑定处理链
camera --> basicOperation --> renderView
// 开始捕捉数据
camera.startCapture()
// 结束捕捉数据
// camera.stopCapture()
从视频中捕获图片
从视频中获取某一帧的图片,可以以任一滤镜节点作为数据源。
// MARK: - 从实时视频中截图图片
func captureImageFromVideo() {
// 启动实时视频滤镜
self.cameraFiltering()
// 设置保存路径
guard let outputPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first else { return }
let originalPath = outputPath + "/originalImage.png"
print("path: \(originalPath)")
let originalURL = URL(fileURLWithPath: originalPath)
let filteredPath = outputPath + "/filteredImage.png"
print("path: \(filteredPath)")
let filteredlURL = URL(fileURLWithPath: filteredPath)
// 延迟1s执行,防止截到黑屏
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + .seconds(1)) {
// 保存相机捕捉到的图片
self.camera.saveNextFrameToURL(originalURL, format: .png)
// 保存滤镜后的图片
self.basicOperation.saveNextFrameToURL(filteredlURL, format: .png)
// 如果需要处理回调,有下面两种写法
let dataOutput = PictureOutput()
dataOutput.encodedImageFormat = .png
dataOutput.encodedImageAvailableCallback = {imageData in
// 这里的imageData是截取到的数据,Data类型
self.camera --> dataOutput
let imageOutput = PictureOutput()
imageOutput.encodedImageFormat = .png
imageOutput.imageAvailableCallback = {image in
// 这里的image是截取到的数据,UIImage类型
self.camera --> imageOutput
编写自定义的图像处理操作
自定义滤镜需要使用OpenGL着色语言
(GLSL)编写Fragment Shader
(片段着色器),调用BasicOperation
的构造器读取写好的文件,可以创建自定义滤镜。
// MARK: - 编写自定义的图像处理操作
func customFilter() {
// 获取文件路径
let url = URL(fileURLWithPath: Bundle.main.path(forResource: "Custom", ofType: "fsh")!)
var customFilter: BasicOperation
// 从文件中创建自定义滤镜
customFilter = try BasicOperation(fragmentShaderFile: url)
} catch {
print(error)
return
// 进行滤镜处理
imageView.image = imageView.image!.filterWithOperation(customFilter)
Custom.fsh
文件像是这样:
varying highp vec2 textureCoordinate;
uniform sampler2D inputImageTexture;
void main()
highp vec2 sampleDivisor = vec2(1.0 / 200.0, 1.0 / 320.0);
//highp vec4 colorDivisor = vec4(colorDepth);
highp vec2 samplePos = textureCoordinate - mod(textureCoordinate, sampleDivisor);
highp vec4 color = texture2D(inputImageTexture, samplePos );
//gl_FragColor = texture2D(inputImageTexture, samplePos );
mediump vec4 colorCyan = vec4(85.0 / 255.0, 1.0, 1.0, 1.0);
mediump vec4 colorMagenta = vec4(1.0, 85.0 / 255.0, 1.0, 1.0);
mediump vec4 colorWhite = vec4(1.0, 1.0, 1.0, 1.0);
mediump vec4 colorBlack = vec4(0.0, 0.0, 0.0, 1.0);
mediump vec4 endColor;
highp float blackDistance = distance(color, colorBlack);
highp float whiteDistance = distance(color, colorWhite);
highp float magentaDistance = distance(color, colorMagenta);
highp float cyanDistance = distance(color, colorCyan);
mediump vec4 finalColor;
highp float colorDistance = min(magentaDistance, cyanDistance);
colorDistance = min(colorDistance, whiteDistance);
colorDistance = min(colorDistance, blackDistance);
if (colorDistance == blackDistance) {
finalColor = colorBlack;
} else if (colorDistance == whiteDistance) {
finalColor = colorWhite;
} else if (colorDistance == cyanDistance) {
finalColor = colorCyan;
} else {
finalColor = colorMagenta;
gl_FragColor = finalColor;
从静态图片中捕获并添加滤镜
作者暂未实现
添加滤镜并转码视频
作者暂未实现
使用Cocoapods安装
作者暂不支持。但是有网友制作了EVGPUImage2
这个仓库来间接使用GPUImage2
,有兴趣可以尝试一下。
使用ACV文件创建滤镜
在GPUImage
中可以通过ACV文件快速创建自定义滤镜。AVC可以通过photoShop
进行图片颜色曲线处理得到,但是GPUImage2
暂未移植这个功能。
与Core Image比较
Core Image
是iOS内置的图像处理框架,两者相比各有优点:
GPUImage 优势
最低支持 iOS 4.0,iOS 5.0 之后就支持自定义滤镜。
在低端机型上,GPUImage 有更好的表现。(这个我没用真正的设备对比过,GPUImage 的主页上是这么说的)
GPUImage 在视频处理上有更好的表现。
GPUImage 的代码完成公开,实现透明。
可以根据自己的业务需求,定制更加复杂的管线操作。可定制程度高。
Core Image 优势
官方框架,使用放心,维护方便。
支持 CPU 渲染,可以在后台继续处理和保存图片。
一些滤镜的性能更强劲。例如由 Metal Performance Shaders 支持的模糊滤镜等。
支持使用 Metal 渲染图像。而 Metal 在 iOS 平台上有更好的表现。
与 Metal,SpriteKit,SceneKit,Core Animation 等更完美的配合。
支持图像识别功能。包括人脸识别、条形码识别、文本识别等。
支持自动增强图像效果,会分析图像的直方图,图像属性,脸部区域,然后通过一组滤镜来改善图像效果。
支持对原生 RAW 格式图片的处理。
滤镜链的性能比 GPUImage 高。(没有验证过,GPUImage 的主页上是这么说的)。
支持对大图进行处理,超过 GPU 纹理限制 (4096 * 4096)的时候,会自动拆分成几个小块处理(Automatic tiling)。GPUImage 当处理超过纹理限制的图像时候,会先做判断,压缩成最大纹理限制的图像,导致图像质量损失。
GPUImage
是一套主流的图像处理框架,很多直播、美图APP都采用此技术,当你的项目是以Swift
为主时,GPUImage2
就是你的首选。
当然,你可以根据业务需要决定使用GPUImage
还是Core Image
,它们都是相当成熟的工具。
本文所有示例代码或Demo可以在此获取:https://github.com/WillieWangWei/SampleCode_GPUImageUsage.git
如果本文对你有所帮助,请给个Star👍
GPUImage2(二)滤镜大全:图像生成
GPUImage2(三)滤镜大全:色彩调校
GPUImage2(四)滤镜大全:图像处理