本文是一个系列,是函数式Swift的读书笔记(其实是为了备忘)
本章围绕如何用高阶函数以小巧且函数式的方式将面向对象的api封装。
1. Core Image介绍
Core Image 是一个强大的图像处理框架,但是它的 API 有时可能略显笨拙。Core Image 的 API 是弱类型的 —— 我们通过键值编码 (KVC) 来配置图像滤镜 (filter)
我们的目标并不是围绕 Core Image 构建一个完整的封装,而是要说明如何把像高阶函数这样的函数式编程概念运用到实际的生产代码中
2.类型
CIFilter 是 Core Image 中的核心类之一,用于创建图像滤镜。当实例化一个 CIFilter 对象时,你 (几乎) 总是通过 kCIInputImageKey 键提供输入图像,再通过 outputImage 属性取回处理后的图像。取回的结果可以作为下一个滤镜的输入值
我们用Filter来定义一个函数,该函数接受一个图像作为参数并返回一个新的图
typealias Filter = (CIImage)->CIImage
我们将以这个类型为基础进行后续的构建(类型的选择)
3.构建滤镜
构建滤镜的函数大概如下
func myFilter() -> Filter
3.1 模糊
//定义一个高斯模糊滤镜,只需要模糊半径一个参数
func blur(radius:Double) -> Filter {
return{
image in
let parameters:[String:Any] = [
kCIInputRadiusKey:radius,
kCIInputImageKey:image
]
guard let filter = CIFilter(name:"CIGaussianBlur", withInputParameters:parameters) else {
fatalError()
}
guard let outputImage = filter.outputImage else{
fatalError()
}
return outputImage
}
}
3.2 颜色叠层
//“让我们来定义一个能够在图像上覆盖纯色叠层的滤镜。Core Image 默认不包含这样一个滤镜,但是我们完全可以用已经存在的滤镜来组成它”
//我们将使用的两个基础组件:颜色生成滤镜 (CIConstantColorGenerator) 和图像覆盖合成滤镜 (CISourceOverCompositing)。首先让我们来定义一个生成固定颜色的滤镜”
func generate(color: UIColor) -> Filter {
return { _ in
let parameters = [kCIInputColorKey: CIColor(cgColor: color.cgColor)]
guard let filter = CIFilter(name: "CIConstantColorGenerator",
withInputParameters: parameters)
else { fatalError() }
guard let outputImage = filter.outputImage
else { fatalError() }
return outputImage
}
}
//生成颜色滤镜不必检查输入图像,因此用一个用一个匿名参数 _
func compositeSourceOver(overlay: CIImage) -> Filter {
return { image in
let parameters = [
kCIInputBackgroundImageKey: image,
kCIInputImageKey: overlay
]
guard let filter = CIFilter(name: "CISourceOverCompositing",
withInputParameters: parameters)
else { fatalError() }
guard let outputImage = filter.outputImage
else { fatalError() }
return outputImage.cropped(to: image.extent)
}
}
最后,通过两个滤镜来创建颜色折叠滤镜
func overlay(color:UIColor)->Filter{
return {
image in
let overlay = generate(color:color)(image).cropped(to:image.extend)
return compositeSourceOver(overlay:overlay)(image)
}
}
3.3 组合滤镜
//创建一个先模糊再叠层的滤镜
let url = URL(string: "http://via.placeholder.com/500x500")!
let image = CIImage(contentsOf: url)!
let radius = 5.0
let color = UIColor.red.withAlphaComponent(0.2)
let blurredImage = blur(radius: radius)(image)
let overlaidImage = overlay(color: color)(blurredImage)
3.4 复合函数
我们可以将上面的代码里调用两个滤镜的表达式合二为一
let result = overlay(color:color)(blur(radius:radius)(image))
//但是,括号过多,失去了可读性。更好的解决途径是,
func compose (filter filter1:@escaping Filter,with filter2:@escaping Filter)->Filter{
return {
image in filter2(filter1(image))
}
}
//compose(filter:with:)函数接受两个Filter类型的参数,并返回一个新滤镜。 “这个复合滤镜接受一个 CIImage 类型的图像参数,然后将该参数传递给 filter1,取得返回值之后再传递给 filter2。
let blurAndOverlay = compose(filter: blur(radius: radius),
with: overlay(color: color))
let result1 = blurAndOverlay(image)
为了可读性(真的是为了可读性吗😓), 我们可以自定义运算符来进一步
infix operator >>>
func >>>(filter1: @escaping Filter, filter2: @escaping Filter) -> Filter {
return { image in filter2(filter1(image)) }
}
//现在我们使用>>> 来代替compose(filter:with:)
let blurAndOverlay2 =
blur(radius: radius) >>> overlay(color: color)
let result2 = blurAndOverlay2(image)
// 运算符 >>> 默认是左结合的 (left-associative),就像 Unix 的管道一样,因此滤镜将以从左到右的顺序被应用到图像上
//我们定义的组合滤镜运算符是一个复合函数的例子
4 ,柯里化
本章中,我们反复见这种代码
blur(radius:radius)(image)
先调用一个函数,返回新函数(本例是Filter),然后传入一个参数,并调用返回的新函数。
其实和下边的效果相同
let blurredImage = blur(image:image,radius:radius)
其实和下例相同
func add1(_ x: Int ,_ y:Int)->Int{
return x+y
}
在swift中,我们可以写另外一个版本
func add2(_ x:Int)-> ((int)->Int){
return {y in x+y}
}
//这里,add2 函数接受一个参数x后,返回一个闭包。然后再接受第二个参数y。
因为该函数的箭头是向右结合 ,所以可以写为如下版本,和add2一样
func add3(_ x:Int)-> (int)->Int{
return {y in x+y}
}
// add1 和add2 的区别在于调用方式:
add1(1,2)
add2(1)(2)
//在第一种方法中,我们将两个参数同时传递给 add1;而第二种方法则首先向函数传递第一个参数 1,然后将返回的函数应用到第二个参数 2。两个版本是完全等价的
//add1 和 add2 的例子向我们展示了如何将一个接受多参数的函数变换为一系列只接受单个参数的函数,这个过程被称为柯里化 (Currying),它得名于逻辑学家 Haskell Curry;我们将 add2 称为 add1 的柯里化版本
//在本章中为了创建滤镜而定义的函数全部都已经被柯里化了 —— 它们都接受一个附加的图像参数。按照柯里化风格来定义滤镜,我们可以很容易地使用 >>> 运算符将它们进行组合
网友评论