文章来源于《Functional Programing in Swift》,本系列仅仅是观后概括的一些内容
Wrapping Core Image
上一篇文章我们介绍了 高阶函数并且展示了函数是如何作为参数传递给其他函数。在本章中,我们将展示如何使用高阶函数对已有的 面向对象的API 进行函数式包装。
Core Image 是一个非常强大的图形处理的框架,但有些时候 它的API的使用有点笨重。CoreImage的API是松散类型—— image filters are configured using key-value coding.这样容易在类型以及参数的名称发生错误,导致运行时错误。我们新的API将会是安全和模块化的,利用类型来确保没有这样的运行时错误。
The Filter Type
CIFilter 用于创建图像过滤器(filter),当你初始化一个CIFilter对象时,你总是会提供一个输入图像通过 kCIInputImageKey,然后通过kCIOutputImageKey来获得过滤后的结果。然后您还可以用这个结果作为下个过滤器的输入。
我们将试着封装这些键值对的具体细节并提供一个安全、强类型的API给我们的用户。
typealias Filter = CIImage -> CIImage
这是我们要构建的基本类型
Building Filters
既然我们已经定义了过滤器的基本类型,我们可以开始为特定的过滤器定义相关的函数,这些便利的函数的参数需要一个特定filter和返回一个Filter。
func myFilter(/* parameters */) -> Filter
注意这个返回的Filter类型,这也是一个函数。此后,这个将帮组我们组装多个过滤器来实现我们想要的图像效果。
To make our lives a bit easier,我们将扩展CIFilter类 通过convenience initializer 以及一个计算属性来取回output image
typealias Parameters = Dictionary<String, AnyObject>
extension CIFilter {
convenience init(name: String, parameters: Parameters) {
self.init(name: name)
setDefaults()
for (key, value: AnyObject) in parameters {
setValue(value, forKey: key)
}
}
var outputImage: CIImage {
return self.valueForKey(kCIOutputImageKey) as CIImage
}
}
我们的便利构造器首先会调用我们的指定构造器。这个计算属性outputImage提供了一个简单的方法从filter对象中去获取output image 。通过这种计算属性,使用我们的API的用户再也不需要去关心如何获取这样的操作。
Blur(模糊)
blur filter 只需要一个blur radius 作为它的参数
func blur(radius: Double) -> Filter {
return { image in
let parameters: Parameters = [
kCIInputRadiusKey: radius,
kCIInputImageKey: image
]
let filter = CIFilter(name: "CIGaussianBlur",parameters:parameters)
return filter.outputImage
}
}
blur function 返回一个 function,这个函数的参数是CIImage 类型的image,返回一个新的image。正因为如此,模糊函数的返回值符合我们之前定义的Filter类型
(typealias Filter = CIImage -> CIImage).这个例子我们是在原来已存在Core Image中的filter只是进行了轻包装。我们可以反复使用相同的模式来创建我们自己的过滤功能。
Color Overlay
我们定义一个过滤器:在图像上覆盖我们选定的颜色。在Core Image中默认没有这样的filter,但是我们可以,当然,这也是通过已存在的过滤器组成的。
这两个构建块是我们要用颜色生成器filter(CIConstantColorGenerator)和source-over compositing filter(CISourceOverCompositing)。
func colorGenerator(color: NSColor) -> Filter {
return { _ in
let parameters: Parameters = [kCIInputColorKey: color]
let filter = CIFilter(name:"CIConstantColorGenerator",parameters: parameters)
return filter.outputImage
}
}
这个跟之前我们定义的blur filter相像,但有一点不同:这个constant color generator filter跟 imput image没有联系,因此我们不需要image 这个参数。
func compositeSourceOver(overlay: CIImage) -> Filter {
return { image in
let parameters: Parameters = [
kCIInputBackgroundImageKey: image,
kCIInputImageKey: overlay
]
let filter = CIFilter(name: "CISourceOverCompositing",parameters: parameters)
let cropRect = image.extent()
return filter.outputImage.imageByCroppingToRect(cropRect) }
}
这里我们将output image的尺寸裁剪成input image的尺寸。这不是必须的,取决于这个我们过滤器的行为。然而,在这个例子中工作的非常好。
最后,我们将两个filter进行组合成color overlay filter:
func colorOverlay(color: NSColor) -> Filter {
return { image in
let overlay = colorGenerator(color)(image)
return compositeSourceOver(overlay)(image)
}
}
Composing Filters
现在我们使用我们定义的filter运用到实际的图片上:first we blur the image, and then we put a red overlay on top.
let url = NSURL(string: "http://tinyurl.com/m74sldb")
let image = CIImage(contentsOfURL: url)
// Now we can apply both filters to these by chaining them together:
let blurRadius = 5.0
let overlayColor = NSColor.redColor().colorWithAlphaComponent(0.2)
let blurredImage = blur(blurRadius)(image)
let overlaidImage = colorOverlay(overlayColor)(blurredImage)
Function Composition
当然上面的filter组合我们可以用一个语句:
let result = colorOverlay(overlayColor)(blur(blurRadius)(image))
但是,代码很快变得不可读。有一个比较好的办法就是自定义一个filter组合的操作:
func composeFilters(filter1: Filter, filter2: Filter) -> Filter {
return { img in filter2(filter1(img)) }
}
let myFilter1 = composeFilters(blur(blurRadius), colorOverlay(overlayColor))
let result1 = myFilter1(image)
我们可以更进一步,使得这个更可读,通过自定义操作符来组合
infix operator >>> { associativity left }
func >>> (filter1: Filter, filter2: Filter) -> Filter {
return { img in filter2(filter1(img)) }
}
let myFilter2 = blur(blurRadius) >>> colorOverlay(overlayColor)
let result2 = myFilter2(image)
当我们定义>>> 为左结合
Discussion
在这章中,我们会发现我们设计的API有这么几个优点:
1. safety----it is almost impossible to create runtime errors arising from undefined keys or failed casts
2. modularity ---it is easy to compose filters using the >>> operator.Doing so allows you to tease apart complex filters into smaller, simpler, reusable components. Additionally, composed filters have the exact same type as their building blocks, so you can use them interchangeably.
3. clarity ---- even if you have never used Core Image, you should be able to assemble simple filters using the functions we have defined. To access the results, you don’t need to know about special dictionary keys, such as kCIOutputImageKey, or worry about initializing certain keys, such as kCIInputImageKey or kCIInputRadiusKey. From the types alone, you can almost figure out how to use the API, even without further documentation