本文翻译自:Core Image(更新日期:2016-09-13
https://developer.apple.com/library/archive/documentation/GraphicsImaging/Conceptual/CoreImaging/ci_intro/ci_intro.html
Core Image 是一种图像处理和分析技术,旨在为静态图像和视频图像提供近乎实时的处理。
它使用 GPU 或 CPU 渲染路径 对来自 Core Graphics、Core Video 和 Image I/O 框架的图像数据类型进行操作。
Core Image 通过提供易于使用的应用程序编程接口 (API) 隐藏了低级图形处理的细节。
您无需了解 OpenGL、OpenGL ES 或 Metal 的细节,即可利用 GPU 的强大功能,也无需了解 Grand Central Dispatch (GCD) 的任何内容即可获得多核处理的优势。Core Image 会为您处理细节。
图 I-1 Core Image 与操作系统的关系

Core Image 框架提供:
在 macOS 上,Core Image 还提供了打包自定义过滤器 以供其他应用程序使用的方法。
Core Image 提供了数百个内置滤镜。
您可以通过为滤镜的输入参数提供键值对来设置滤镜。
一个滤镜的输出可以是另一个滤镜的输入,这样就可以将多个滤镜串联在一起以创建令人惊叹的效果。
如果您创建了想要再次使用的复合效果,则可以将 CIFilter 子类化 以捕获效果“配方”。
滤镜有十多个类别。
有些旨在实现艺术效果,例如 风格化 和 半色调滤镜 类别。
其他则最适合修复图像问题,例如色彩调整和锐化滤镜。
Core Image 可以分析图像的质量 并提供一组具有最佳设置的滤镜,用于调整色调、对比度和色调颜色等,以及校正红眼等闪光伪影。
您只需调用一个方法即可完成所有这些操作。
Core Image 可以检测静态图像中的人脸特征,并在视频图像中随时间跟踪这些特征。
了解人脸的位置可以帮助您确定在何处放置晕影或应用其他特殊滤镜。
相关章节: Processing Images, Detecting Faces in an Image, Auto Enhancing Images, Subclassing CIFilter: Recipes for Custom Effects
Core Image 为其滤镜“内置”了参考文档。您可以查询系统以找出哪些滤镜可用。
然后,对于每个滤镜,您可以检索 包含其属性的字典,例如其输入参数、默认参数值、最小值和最大值、显示名称等。
相关章节: 查询系统过滤器
如果您的应用需要 实时处理视频,您可以采取多种措施来优化性能。
相关章节: 获得最佳性能
CIImageAccumulator 类专为高效的 基于反馈的图像处理而设计,如果您的应用程序 需要对动态系统进行成像,您可能会发现它很有用。
相关章节: 使用反馈处理图像
如果内置滤镜都不能满足您的需求(即使组合在一起),请考虑创建自定义滤镜。
您需要了解内核(在像素级别运行的程序),因为它们是每个滤镜的核心。
在 macOS 中,你可以将一个或多个自定义滤镜打包为一个图像单元,以便其他应用程序可以加载和使用它们。
相关章节: 编写自定义过滤器前你需要知道的内容、创建自定义过滤器、打包和加载图像单元
Core Image 的其他重要文档包括:
处理图像意味着应用滤镜, 图像滤镜是一种软件,它逐个检查输入图像 并通过算法应用某种效果,以创建输出图像。
在 Core Image 中,图像处理依赖于和CIFilter类CIImage,它们描述滤镜及其输入和输出。
要应用滤镜并显示或导出结果,您可以利用 Core Image 与其他系统框架之间的集成,或使用 CIContext 类创建自己的渲染工作流程。
本章介绍使用这些类应用滤镜和渲染结果的关键概念。
在您的应用中,有多种方法可以使用 Core Image 进行图像处理。
清单 1-1展示了一个基本示例,并提供了本章进一步解释的指引。
清单 1-1 对图像应用滤镜的基础知识
import CoreImage
let context = CIContext() // 1
let filter = CIFilter(name: "CISepiaTone")! // 2
filter.setValue(0.8, forKey: kCIInputIntensityKey)
let image = CIImage(contentsOfURL: myURL) // 3
filter.setValue(image, forKey: kCIInputImageKey)
let result = filter.outputImage! // 4
let cgImage = context.createCGImage(result, from: result.extent) // 5
代码的作用如下:
CIContext对象(使用默认选项)。CIFilter表示 要应用的滤镜的对象,并为其参数提供值。CIImage 表示要处理的图像的对象,并将其作为输入图像参数提供给过滤器。CIImage表示滤镜输出的对象。Core Image 滤镜处理并生成 Core Image 图像。
CIImage实例是代表图像的不可变对象。
这些对象并不直接表示图像位图数据,而是CIImage生成图像的“配方”。
一个配方可能要求从文件加载图像;另一个配方可能表示来自滤镜或滤镜链的输出。
Core Image 仅在您请求渲染图像以供显示或输出时才执行这些配方。
要应用滤镜,请创建一个或多个CIImage表示要由滤镜处理的图像的对象,并将它们分配给滤镜的输入参数(例如kCIInputImageKey)。
您可以从几乎任何图像数据源创建 Core Image 图像对象,包括:
NSData包含图像文件数据的对象的 URLCGImageRef、UIImage或NSBitmapImageRep对象)CVImageBufferRef或CVPixelBufferRef)IOSurfaceRef在进程间共享图像数据的对象CIImageProvider按需提供数据的对象)有关创建对象的方法的完整列表CIImage,请参阅*CIImage 类参考*。
因为CIImage对象描述了如何生成图像(而不是包含图像数据),所以它也可以表示滤镜输出。
当您访问对象outputImage的属性CIFilter时,Core Image 仅识别并存储执行滤镜所需的步骤。
只有当您请求渲染图像以供显示或输出时,才会执行这些步骤。
您可以使用或方法之一明确请求渲染CIContext``render(draw请参阅使用Core Image 上下文构建您自己的工作流程),也可以通过使用与 Core Image 配合使用的众多系统框架之一显示图像来隐式请求渲染(请参阅与其他框架集成)。
将处理推迟到渲染时,使 Core Image 变得快速而高效。
在渲染时,Core Image 可以查看是否需要对图像应用多个滤镜。
如果需要,它会自动连接多个“配方”并组织它们以消除冗余操作,这样每个像素只需处理一次,而不是多次。
该类的实例CIFilter是一个可变对象,表示图像处理效果以及控制该效果行为的任何参数。
要使用滤镜,您需要创建一个CIFilter对象,设置其输入参数,然后访问其输出图像(请参阅下面的图像是滤镜的输入和输出)。
调用filterWithName:初始化程序以使用系统已知的滤镜名称实例化滤镜对象(请参阅查询系统中的滤镜或*核心图像滤镜参考*)。
大多数滤镜都有一个或多个输入参数,可让您控制处理方式。
每个输入参数都有一个指定其数据类型的属性类,例如NSNumber。
输入参数可以选择其他属性,例如其默认值、允许的最小值和最大值、参数的显示名称以及*CIFilter 类参考*中描述的其他属性。
例如,CIColorMonochrome滤镜有三个输入参数 - 要处理的图像、单色和颜色强度。
滤镜参数定义为键值对;要使用参数,您通常使用valueForKey:和方法或其他基于键值编码的功能(例如 Core Animation)。
键是标识属性的常量,值是与键关联的设置。
Core Image 属性值通常使用属性值数据类型setValue:forKey:中列出的数据类型之一。
| 数据类型 | 目的 | 描述 |
|---|---|---|
| 字符串 | NSString | 文本,通常用于显示给用户 |
| 浮点值 | NSNumber | 标量值,例如强度级别或半径 |
| 向量 | CIVector | 一组浮点值,可以指定位置、大小、矩形或未标记的颜色分量值 |
| 颜色 | CIColor | 一组颜色分量值,标有颜色空间,指定如何解释它们 |
| 图片 | CIImage | 图像;请参阅图像是过滤器的输入和输出 |
| 变换 | NSData,NSAffineTransform | 应用于图像的坐标变换 |
重要提示: CIFilter对象是可变的,因此您无法安全地在不同线程之间共享它们。
每个线程都必须创建自己的CIFilter对象。
但是,过滤器的输入和输出CIImage对象是不可变的,因此可以在线程之间安全地传递。
每个 Core Image 滤镜都会生成一个输出对象,因此您可以将此对象用作另一个滤镜的输入。
例如,图 1-1CIImage中所示的滤镜序列将颜色效果应用于图像,然后添加发光效果,最后从结果中裁剪出一部分。
图 1-1 通过连接过滤器输入和输出构建过滤器链

Core Image 优化了诸如此类的滤镜链的应用,以便快速高效地渲染结果。
CIImage链中的每个对象都不是完全渲染的图像,而仅仅是渲染的“配方”。
Core Image 不需要单独执行每个滤镜,从而浪费时间和内存来渲染永远不会看到的中间像素缓冲区。
相反,Core Image 将滤镜组合成一个操作,甚至可以在以不同顺序应用滤镜时重新组织滤镜,从而更高效地产生相同的结果。
图 1-2显示了图 1-1中示例滤镜链的更精确再现。
图 1-2 Core Image 将滤镜链优化为单个操作

请注意,在图 1-2中,裁剪操作已从最后移至最前面。
该滤镜会导致原始图像的大面积区域从最终输出中被裁剪掉。
因此,无需对这些像素应用颜色和锐化滤镜。
通过首先执行裁剪,Core Image 可确保昂贵的图像处理操作仅应用于最终输出中可见的像素。
清单 1-2显示了如何设置如上所示的过滤器链。
清单 1-2 创建过滤器链
func applyFilterChain(to image: CIImage) -> CIImage {
// The CIPhotoEffectInstant filter takes only an input image
let colorFilter = CIFilter(name: "CIPhotoEffectProcess", withInputParameters:
[kCIInputImageKey: image])!
// Pass the result of the color filter into the Bloom filter
// and set its parameters for a glowy effect.
let bloomImage = colorFilter.outputImage!.applyingFilter("CIBloom",
withInputParameters: [
kCIInputRadiusKey: 10.0,
kCIInputIntensityKey: 1.0
])
// imageByCroppingToRect is a convenience method for
// creating the CICrop filter and accessing its outputImage.
let cropRect = CGRect(x: 350, y: 350, width: 150, height: 150)
let croppedImage = bloomImage.cropping(to: cropRect)
return croppedImage
}
清单 1-2还展示了几种用于配置过滤器和访问其结果的便捷方法。
总之,您可以使用以下任何一种方法来应用过滤器,可以单独应用,也可以作为过滤器链的一部分应用:
CIFilter使用初始化程序创建一个实例filterWithName:,使用方法设置参数setValue:forKey:(包括kCIInputImageKey要处理的图像的),并使用属性访问输出图像outputImage。CIFilter实例并设置其参数(包括输入图像)filterWithName:withInputParameters:,然后使用该outputImage属性访问输出。colorFilter中的示例。)CIFilter使用该imageByApplyingFilter:withInputParameters:方法对对象应用滤镜而无需创建实例。CIImage中的 bloomImage 示例。)CIImage中列出的其他实例方法。大多数内置的 Core Image 滤镜都针对主输入图像(可能还会对处理产生影响的其他输入图像)进行操作,并创建单个输出图像。
但您可以使用其他几种类型的滤镜来创建有趣的效果或与其他滤镜结合使用,以产生更复杂的工作流程。
imageByApplyingTransform:方法。CIImage对象作为其输出,因此缩减滤镜生成的信息仍然是图像。Core Image 可与 iOS、macOS 和 tvOS 中的其他几种技术进行互操作。
得益于这种紧密集成,您可以使用 Core Image 轻松地为应用用户界面中的游戏、视频或图像添加视觉效果,而无需构建复杂的渲染代码。
以下部分介绍了在应用中使用 Core Image 的几种常见方法以及系统框架为每种方法提供的便利。
UIKit 和 AppKit 提供了向静态图像添加 Core Image 处理的简单方法,无论这些图像出现在应用的 UI 中还是其工作流程的一部分。
例如:
注意: 不要使用 Core Image 创建属于用户界面设计的模糊效果(例如 macOS、iOS 和 tvOS 系统界面的半透明侧边栏、工具栏和背景中看到的模糊效果)。
相反,请参阅NSVisualEffectView(macOS) 或 UIVisualEffectView(iOS/tvOS) 类,它们会自动匹配系统外观并提供高效的实时渲染。
在 iOS 和 tvOS 中,您可以在任何使用UIImage对象的地方应用 Core Image 滤镜。
清单 1-3展示了一种将滤镜与图像视图结合使用的简单方法。
清单 1-3 将过滤器应用于图像视图(iOS/tvOS)
class ViewController: UIViewController {
let filter = CIFilter(name: "CISepiaTone",
withInputParameters: [kCIInputIntensityKey: 0.5])!
@IBOutlet var imageView: UIImageView!
func displayFilteredImage(image: UIImage) {
// Create a Core Image image object for the input image.
let inputImage = CIImage(image: image)!
// Set that image as the filter's input image parameter.
filter.setValue(inputImage, forKey: kCIInputImageKey)
// Get a UIImage representation of the filter's output and display it.
imageView.image = UIImage(CIImage: filter.outputImage!)
}
}
在 macOS 中,使用 initWithBitmapImageRep: 方法从位图图像创建 CIImage 对象,使用 NSCIImageRep 类创建可以在任何支持 NSImage对象的地方使用的图像。
AVFoundation 框架提供了许多用于处理视频和音频内容的高级实用程序。
其中包括 类AVVideoComposition,您可以使用它将视频和音频轨道组合或编辑为单个演示文稿。
(有关合成的一般信息,请参阅*AVFoundation 编程指南*中的编辑。)您可以使用对象在播放或导出期间将 Core Image 滤镜应用于视频的每一帧,如清单 1-4所示。
AVVideoComposition
清单 1-4 将滤镜应用于视频合成
let filter = CIFilter(name: "CIGaussianBlur")!
let composition = AVVideoComposition(asset: asset, applyingCIFiltersWithHandler: { request in
// Clamp to avoid blurring transparent pixels at the image edges
let source = request.sourceImage.clampingToExtent()
filter.setValue(source, forKey: kCIInputImageKey)
// Vary filter parameters based on video timing
let seconds = CMTimeGetSeconds(request.compositionTime)
filter.setValue(seconds * 10.0, forKey: kCIInputRadiusKey)
// Crop the blurred output to the bounds of the original image
let output = filter.outputImage!.cropping(to: request.sourceImage.extent)
// Provide the filter output to the composition
request.finish(with: output, context: nil)
})
程序创建合成时,使用videoCompositionWithAsset:applyingCIFiltersWithHandler:初始化,您将提供一个处理程序,负责将滤镜应用于视频的每一帧。AxVFoundation 会在播放或导出期间自动调用您的处理程序。
在处理程序中,您首先使用 AVAsynchronousCIImageFilteringRequest 提供的对象来检索要过滤的视频帧(以及帧时间等补充信息),然后提供已过滤的图像以供合成使用。
要使用创建的视频合成进行播放,请从用作合成源的同一资产创建一个 AVPlayerItem 对象,然后将合成分配给播放器项目的videoComposition属性。
要将合成导出到新电影文件,请AVAssetExportSession从同一源资产创建一个对象,然后将合成分配给导出会话的videoComposition属性。
提示: 清单 1-4 还展示了另一种有用的 Core Image 技术。
默认情况下,模糊滤镜还会通过 模糊图像像素以及(在滤镜的图像处理空间中)环绕图像的透明像素 来柔化图像的边缘。
在某些情况下,例如在过滤视频时,这种效果可能不受欢迎。
为了避免这种效果,请在模糊之前使用imageByClampingToExtent方法(或CIAffineClamp滤镜)将图像的边缘像素无限延伸到所有方向。
钳制会创建无限大小的图像,因此您还应该在模糊后裁剪图像。
SpriteKit 是一种用于构建 2D 游戏和其他具有高度动态动画内容的应用类型的技术;SceneKit 用于处理 3D 资源、渲染和制作 3D 场景动画以及构建 3D 游戏。
(有关每种技术的更多信息,请参阅 SpriteKit 编程指南 和 SceneKit 框架参考 。)这两种框架都提供高性能实时渲染,并可轻松将核心图像处理添加到整个或部分场景。
在 SpriteKit 中,您可以使用 SKEffectNode 类添加 Core Image 滤镜。
要查看该类的使用示例,请使用 Game 模板(适用于 iOS 或 tvOS)创建一个新的 Xcode 项目,选择 SpriteKit 作为游戏技术,并修改GameScene 类中的 touchesBegan:withEvent: 方法以使用清单 1-5中的代码。
(对于 macOS Game 模板,您可以对 mouseDown: 方法进行类似的修改。)
清单 1-5 在 SpriteKit 中应用过滤器
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch in touches {
let sprite = SKSpriteNode(imageNamed:"Spaceship")
sprite.setScale(0.5)
sprite.position = touch.location(in: self)
sprite.run(.repeatForever(.rotate(byAngle: 1, duration:1)))
let effect = SKEffectNode()
effect.addChild(sprite)
effect.shouldEnableEffects = true
effect.filter = CIFilter(name: "CIPixellate",
withInputParameters: [kCIInputScaleKey: 20.0])
self.addChild(effect)
}
}
请注意,SKScene类 本身是SKEffectNode 的子类,因此您还可以将 Core Image 过滤器应用于整个 SpriteKit 场景。
在 SceneKit 中,filters 类 的属性SCNNode可以将 Core Image 滤镜应用于 3D 场景的任何元素。
要查看此属性的实际效果,请使用 Game 模板(适用于 iOS、tvOS 或 macOS)创建一个新的 Xcode 项目,选择 SceneKit 作为游戏技术,并修改 GameViewController 类中的 viewDidLoad 方法以使用清单 1-6中的代码。
清单 1-6 在 SceneKit 中应用滤镜
// Find this line in the template code:
let ship = rootNode.childNode(withName: "ship", recursively: true)!
// Add these lines after it:
let pixellate = CIFilter(name: "CIPixellate",
withInputParameters: [kCIInputScaleKey: 20.0])!
ship.filters = [ pixellate ]
您还可以在 SceneKit 节点上为过滤器参数设置动画 - 有关详细信息,请参阅属性的参考文档filters。
在 SpriteKit 和 SceneKit 中,您都可以使用过渡来更改视图的场景并增加视觉效果。
(请参阅 SpriteKit 的 presentScene:transition: 方法和 SceneKit 的 presentScene:withTransition:incomingPointOfView:completionHandler: 方法。)使用 SKTransition 类及其transitionWithCIFilter:duration:初始化 程序 从任何 Core Image 过渡滤镜创建过渡动画。
在 macOS 中,您可以使用该filters属性将滤镜应用于任何支持滤镜的 CALayer 视图的内容,并添加随时间变化的滤镜参数的动画。
请参阅 Core Animation 编程指南 中的滤镜为 OS X 视图添加视觉效果和高级动画技巧。
当您使用上一节中列出的技术应用 Core Image 滤镜时,这些框架会自动管理 Core Image 用于处理图像和渲染结果以供显示的基础资源。
这种方法既可以最大限度地提高这些工作流程的性能,又可以更轻松地设置它们。
但是,在某些情况下,使用 CIContext 类自行管理这些资源更为明智。
通过直接管理 Core Image 上下文,您可以精确控制应用的性能特征或将 Core Image 与较低级别的渲染技术集成。
Core Image 上下文表示执行滤镜和生成图像所需的 CPU 或 GPU 计算技术、资源和设置。
有几种上下文可供选择,因此您应该选择最适合您应用的工作流程和您可能使用的其他技术的选项。
以下部分讨论了一些常见场景;有关完整选项集,请参阅*CIContext 类参考*。
重要提示: Core Image 上下文是一个重量级对象,用于管理大量资源和状态。
反复创建和销毁上下文会严重影响性能,因此如果您计划执行多个图像处理操作,请尽早创建上下文并将其存储起来以供将来重复使用。
如果您对应用与其他图形技术的互操作方式没有任何限制,那么创建 Core Image 上下文很简单:只需使用基本init或initWithOptions:初始化程序即可。
当您这样做时,Core Image 会自动在内部管理资源,根据当前设备和您指定的任何选项选择合适或最佳可用的 CPU 或 GPU 渲染技术。
这种方法非常适合渲染处理后的图像以输出到文件(例如,使用writeJPEGRepresentationOfImage:toURL:colorSpace:options:error: 方法)等任务。
注意: 未明确指定渲染目标的上下文无法使用 drawImage:inRect:fromRect:方法,因为该方法的行为会根据所使用的渲染目标而变化。
相反,请使用名称以 render 或 create开头的 CIContext 方法来指定显式目标。
如果您打算实时渲染 Core Image 结果(即,以动画方式改变滤镜参数、产生动画过渡效果,或处理每秒已渲染多次的视频或其他视觉内容),请谨慎使用此方法。
尽管使用此方法创建的CIContext对象可以使用 GPU 自动渲染,但呈现渲染结果可能涉及 CPU 和 GPU 内存之间昂贵的复制操作。
Metal 框架提供对 GPU 的低开销访问,从而实现图形渲染和并行计算工作流的高性能。
此类工作流是图像处理不可或缺的一部分,因此 Core Image 尽可能地以 Metal 为基础构建。
如果您正在构建使用 Metal 渲染图形的应用,或者想要利用 Metal 获得动画滤镜输出或过滤动画输入(例如实时视频)的实时性能,请使用 Metal 设备来创建您的 Core Image 上下文。
清单 1-7和 清单 1-8 展示了使用 MetalKit 视图 (MTKView) ) 呈现 Core Image 输出的示例。
(每个清单中都对重要步骤进行了编号,并在之后进行了描述。)
清单 1-7 设置 Metal 视图以进行 Core Image 渲染
class ViewController: UIViewController, MTKViewDelegate { // 1
// Metal resources
var device: MTLDevice!
var commandQueue: MTLCommandQueue!
var sourceTexture: MTLTexture! // 2
// Core Image resources
var context: CIContext!
let filter = CIFilter(name: "CIGaussianBlur")!
let colorSpace = CGColorSpaceCreateDeviceRGB()
override func viewDidLoad() {
super.viewDidLoad()
device = MTLCreateSystemDefaultDevice() // 3
commandQueue = device.newCommandQueue()
let view = self.view as! MTKView // 4
view.delegate = self
view.device = device
view.framebufferOnly = false
context = CIContext(mtlDevice: device) // 5
// other setup
}
}
UIViewController子类。NSViewController 子类。sourceTexture属性包含一个 Metal 纹理,其中包含要由滤镜处理的图像。MTKTextureLoader 类加载图像文件,或将纹理用作您自己的早期渲染过程的输出。MTLDevice表示要使用的 GPU 的对象,以及一个用于在该 GPU 上执行渲染和计算命令的命令队列。framebufferOnly属性设置为NO。CIContext对象的创建成本很高,因此您只需创建一次,并在每次处理图像时重复使用它。每次需要显示视图时,MetalKit 都会调用 drawInMTKView:方法。
(默认情况下,MetalKit 每秒最多可以调用此方法 60 次。 有关详细信息,请参阅视图的preferredFramesPerSecond属性。)
清单 1-8展示了该方法从 Core Image 上下文进行渲染的基本实现。
清单 1-8 在 Metal 视图中使用 Core Image 过滤器进行绘图
public func draw(in view: MTKView) {
if let currentDrawable = view.currentDrawable { // 1
let commandBuffer = commandQueue.commandBuffer()
let inputImage = CIImage(mtlTexture: sourceTexture)! // 2
filter.setValue(inputImage, forKey: kCIInputImageKey)
filter.setValue(20.0, forKey: kCIInputRadiusKey)
context.render(filter.outputImage!, // 3
to: currentDrawable.texture,
commandBuffer: commandBuffer,
bounds: inputImage.extent,
colorSpace: colorSpace)
commandBuffer.present(currentDrawable) // 4
commandBuffer.commit()
}
}
Core Image 还可以使用 OpenGL (macOS) 或 OpenGL ES (iOS 和 tvOS) 实现基于 GPU 的高性能渲染。
如果您需要支持无法使用 Metal 的旧硬件,或者想要将 Core Image 集成到现有的 OpenGL 或 OpenGL ES 工作流程中,请使用此选项。
contextWithEAGLContext:options: 初始化 来从 EAGLContext 创建用于渲染的Core Image 上下文。contextWithCGLContext:pixelFormat:colorSpace:options:初始化程序 从用于渲染的 OpenGL 上下文中创建 Core Image 上下文。 (有关像素格式的重要详细信息,请参阅该方法的参考文档。)在任何一种情况下,都可以使用imageWithTexture:size:flipped:colorSpace:初始化程序从 OpenGL 或 OpenGL ES 纹理创建CIImage对象。
使用 GPU 内存中已有的图像数据可以消除冗余复制操作,从而提高性能。
要在 OpenGL 或 OpenGL ES 中渲染 Core Image 输出,请使您的 GL 上下文 成为当前上下文并设置目标帧缓冲区,然后调用 drawImage:inRect:fromRect:方法。
如果您的应用不需要实时性能并使用 CoreGraphics 绘制视图内容(例如,在drawRect:UIKit 或 AppKit 视图的方法中),请使用contextWithCGContext:options:初始化程序创建一个 Core Image 上下文,该上下文可直接与您已用于其他绘制的 Core Graphics 上下文配合使用。
(在 macOS 中,请改用CIContext当前NSGraphicsContext对象的属性。)有关 CoreGraphics 上下文的信息,请参阅*Quartz 2D 编程指南*。
Core Image 可以分析并找到图像中的人脸。它执行人脸检测,而不是识别。
人脸检测是 识别包含人脸特征的 矩形,而人脸识别 是识别特定的人脸(约翰、玛丽等)。
Core Image 检测到人脸后,可以提供有关人脸特征的信息,例如眼睛和嘴巴的位置。
它还可以跟踪视频中已识别人脸的位置。
图 2-1 Core Image 识别图像中的脸部边界

了解脸部在图像中的位置后,您可以执行其他操作,例如裁剪或调整脸部的图像质量(色调平衡、红眼校正等)。
您还可以对脸部执行其他有趣的操作;例如:
注意: iOS v5.0 及更高版本和 OS X v10.7 及更高版本支持人脸检测功能。
使用CIDetector该类在图像中查找人脸,如清单 2-1所示。
清单 2-1 创建人脸检测器
CIContext *context = [CIContext context]; // 1
NSDictionary *opts = @{ CIDetectorAccuracy : CIDetectorAccuracyHigh }; // 2
CIDetector *detector = [CIDetector detectorOfType:CIDetectorTypeFace
context:context
options:opts]; // 3
opts = @{ CIDetectorImageOrientation :
[[myImage properties] valueForKey:kCGImagePropertyOrientation] }; // 4
NSArray *features = [detector featuresInImage:myImage options:opts]; // 5
代码的作用如下:
nil在创建检测器时提供上下文。)CIDetectorAccuracyLow) 速度快;本例中显示的高精度更全面但速度较慢。CIImage 对象。CIFeature 对象数组,每个对象代表图像中的一张脸。获得一组人脸后,您可能想找出它们的特征,例如眼睛和嘴巴的位置。下一节将介绍如何操作。
面部特征包括:
从CIDetector 对象中获取脸部特征数组后,可以循环遍历该数组来检查每个脸部的边界以及脸部中每个特征,如清单 2-2所示。
清单 2-2 检查面部特征边界
for (CIFaceFeature *f in features) {
NSLog(@"%@", NSStringFromRect(f.bounds));
if (f.hasLeftEyePosition) {
NSLog(@"Left eye %g %g", f.leftEyePosition.x, f.leftEyePosition.y);
}
if (f.hasRightEyePosition) {
NSLog(@"Right eye %g %g", f.rightEyePosition.x, f.rightEyePosition.y);
}
if (f.hasMouthPosition) {
NSLog(@"Mouth %g %g", f.mouthPosition.x, f.mouthPosition.y);
}
}
Core Image 的自动增强功能 会分析图像的直方图、面部区域内容和元数据属性。
然后,它会返回一个CIFilter对象数组,其中的输入参数 已设置为 可改善所分析图像的值。
自动增强功能适用于 iOS v5.0 及更高版本以及 OS X v10.8 及更高版本。
表 3-1显示了 Core Image 用于自动增强图像的滤镜。
这些滤镜可以解决照片中最常见的一些问题。
| 筛选 | 目的 |
|---|---|
| CIRedEyeCorrection | 修复因相机闪光灯导致的红眼/琥珀色眼/白眼 |
| CIFaceBalance | 调整面部颜色,使肤色更加美观 |
| CIVibrance | 增加图像的饱和度而不扭曲肤色 |
| CIToneCurve | 调整图像对比度 |
| CIHighlightShadowAdjust | 调整阴影细节 |
自动增强 API 只有两种方法:autoAdjustmentFilters和autoAdjustmentFiltersWithOptions:。
大多数情况下,您会希望使用 提供选项字典的方法。
您可以设置以下选项:
kCIImageAutoAdjustEnhance为false。)kCIImageAutoAdjustRedEye为false。)autoAdjustmentFiltersWithOptions:方法返回一个选项过滤器数组,然后您需要将它们链接在一起并应用于已分析的图像,如清单 3-1所示。
代码首先创建一个选项字典。
然后它获取图像的方向并将其设置为键的值CIDetectorImageOrientation。
清单 3-1 获取自动增强滤镜并将其应用于图像
NSDictionary *options = @{ CIDetectorImageOrientation :
[[image properties] valueForKey:kCGImagePropertyOrientation] };
NSArray *adjustments = [myImage autoAdjustmentFiltersWithOptions:options];
for (CIFilter *filter in adjustments) {
[filter setValue:myImage forKey:kCIInputImageKey];
myImage = filter.outputImage;
}
回想一下,输入参数值已经由 Core Image 设置以产生最佳结果。
您不必立即应用自动调整滤镜。
您可以保存滤镜名称和参数值以供日后使用。
保存它们可让您的应用稍后执行增强功能,而无需再次分析图像。
Core Image 提供的方法可让您向系统 查询可用的内置滤镜,以及每个滤镜的相关信息(显示名称、输入参数、参数类型、默认值等)。
查询系统可为您提供 有关可用滤镜的最新信息。
如果您的应用支持让用户选择和设置滤镜,您可以在为滤镜创建用户界面时使用此信息。
使用filterNamesInCategory:和filterNamesInCategories:方法可以准确发现哪些过滤器可用。过滤器已分类,使列表更易于管理。
如果您知道过滤器类别,则可以通过调用 filterNamesInCategory: 方法来查找该类别可用的过滤器,方法是提供表 4-1、表 4-2或 表 4-3中列出的类别常量之一。
如果您想要查找类别列表的所有可用过滤器,可以调用 filterNamesInCategories: 方法,并提供表中列出的类别常量数组。
该方法返回一个 NSArray 对象,其中填充了每个类别的过滤器名称。
您可以通过提供 nil 而不是类别常量数组 来 获取所有类别的 所有过滤器列表。
一个过滤器可以是多个类别的成员。类别可以指定:
Table 4-1 Filter category constants for effect types
| 效果类型 | 表示 |
|---|---|
kCICategoryDistortionEffect | 扭曲效果,例如凹凸、旋转、孔洞 |
kCICategoryGeometryAdjustment | 几何调整,如仿射变换、裁剪、透视变换 |
kCICategoryCompositeOperation | 合成,例如源上方、最小、源上方、颜色减淡混合模式 |
kCICategoryHalftoneEffect | 半色调效果,例如屏幕、线屏幕、阴影线 |
kCICategoryColorAdjustment | 颜色调整,如伽马调整、白点调整、曝光调整 |
kCICategoryColorEffect | 色彩效果,如色调调整、色调分离 |
kCICategoryTransition | 图像之间的过渡,例如溶解、带蒙版分解、滑动 |
kCICategoryTileEffect | 平铺效果,如平行四边形、三角形 |
kCICategoryGenerator | 图像生成器,例如条纹、恒定颜色、棋盘格 |
kCICategoryGradient | 渐变,如轴向、径向、高斯 |
kCICategoryStylize | 风格化,例如像素化、结晶化 |
kCICategorySharpen | 锐化、亮度 |
kCICategoryBlur | 模糊,例如高斯模糊、缩放模糊、运动模糊 |
Table 4-2 Filter category constants for filter usage
| 使用 | 表示 |
|---|---|
kCICategoryStillImage | 可用于静态图像 |
kCICategoryVideo | 可用于视频 |
kCICategoryInterlaced | 可用于隔行图像 |
kCICategoryNonSquarePixels | 可用于非正方形像素 |
kCICategoryHighDynamicRange | 可用于高动态范围像素 |
Table 4-3 Filter category constants for filter origin
| 过滤原点 | 表示 |
|---|---|
kCICategoryBuiltIn | Core Image 提供的过滤器 |
获取过滤器名称列表后,您可以通过创建 CIFilter 对象 并调用 attributes 方法来检索过滤器的属性,如下所示:
CIFilter *myFilter = [CIFilter filterWithName:@"<# Filter Name Here #>"];
NSDictionary *myFilterAttributes = [myFilter attributes];
您可以将字符串 <# Filter Name Here #> 替换为您感兴趣的过滤器的名称。
属性包括名称、类别、类、最小值和最大值等。
请参阅*CIFilter 类参考*以获取可返回的属性的完整列表。
如果您的应用提供了用户界面,则可以查阅过滤器字典来创建和更新用户界面。
例如,布尔值的过滤器属性需要复选框或类似的用户界面元素,而范围连续变化的属性可以使用滑块。
您可以使用最大值和最小值作为文本标签的基础。
默认属性设置将决定用户界面中的初始设置。
过滤器名称和属性提供了构建用户界面所需的所有信息,该界面允许用户选择过滤器并控制其输入参数。
过滤器的属性会告诉您过滤器有多少个输入参数、参数名称、数据类型以及最小值、最大值和默认值。
注意: 如果您有兴趣为 Core Image 过滤器构建用户界面,请参阅*IKFilterUIView 类参考*,它提供了包含 Core Image 过滤器的输入参数控件的视图。
清单 4-1显示了获取滤镜名称 并按功能类别 构建滤镜词典的代码。
代码检索这些类别中的滤镜 : kCICategoryGeometryAdjustment 、kCICategoryDistortionEffect、kCICategorySharpen和kCICategoryBlur,但根据应用定义的功能类别(例如“失真(Distortion)”和“聚焦(Focus)”)构建词典。
功能类别对于 在菜单中 组织滤镜名称非常有用,这对用户来说很有意义。
代码不会遍历 所有可能的 Core Image 滤镜类别,但您可以按照相同的过程轻松扩展此代码。
清单 4-1 按功能类别构建过滤器词典的代码
NSMutableDictionary *filtersByCategory = [NSMutableDictionary dictionary];
NSMutableArray *filterNames = [NSMutableArray array];
[filterNames addObjectsFromArray:
[CIFilter filterNamesInCategory:kCICategoryGeometryAdjustment]];
[filterNames addObjectsFromArray:
[CIFilter filterNamesInCategory:kCICategoryDistortionEffect]];
filtersByCategory[@"Distortion"] = [self buildFilterDictionary: filterNames];
[filterNames removeAllObjects];
[filterNames addObjectsFromArray:
[CIFilter filterNamesInCategory:kCICategorySharpen]];
[filterNames addObjectsFromArray:
[CIFilter filterNamesInCategory:kCICategoryBlur]];
filtersByCategory[@"Focus"] = [self buildFilterDictionary: filterNames];
清单 4-2显示了清单 4-1中 buildFilterDictionary中调用的例程。
此例程为功能类别中 的每个过滤器 构建属性词典。
清单后面是每行带编号代码的详细说明。
清单 4-2 根据功能名称构建过滤器词典
- (NSMutableDictionary *)buildFilterDictionary:(NSArray *)filterClassNames // 1
{
NSMutableDictionary *filters = [NSMutableDictionary dictionary];
for (NSString *className in filterClassNames) { // 2
CIFilter *filter = [CIFilter filterWithName:className]; // 3
if (filter) {
filters[className] = [filter attributes]; // 4
} else {
NSLog(@"could not create '%@' filter", className);
}
}
return filters;
}
代码的作用如下:
注意: 在 OS X v10.5 及更高版本中 运行的应用程序 可以使用 CIFilter Image Kit 附加功能 来提供过滤器浏览器 和 用于设置过滤器输入参数的视图。
请参阅CIFilter Image Kit 附加功能和*ImageKit 编程指南*。
您可以使用一个图像滤镜的输出 作为另一个图像滤镜的输入来创建自定义效果,并将任意数量的滤镜串联在一起。
当您以这种方式创建想要多次使用的效果时,请考虑将其子类化CIFilter以将效果封装为滤镜。
本章介绍了 Core Image 如何子类化CIFilter以创建CIColorInvert滤镜。
然后,它描述了将各种滤镜串联在一起以实现有趣效果的方法。
通过遵循 子类化CIFilter 以创建 CIColorInvert 滤镜 中的子类化过程,您应该能够根据本章中的方法创建滤镜,或者大胆尝试创建您自己的 Core Image 提供的内置滤镜的有趣组合。
当您创建子类时,CIFilter您可以通过使用预设值对现有过滤器进行编码或将它们链接在一起来修改它们。
Core Image 使用此技术实现了其一些内置过滤器。
要对过滤器进行子类化,您需要执行以下任务:
input,例如inputImage。setDefaults方法。outputImage方法。Core Image 提供的 CIColorInvert 滤镜是 CIColorMatrix 滤镜的变体。
顾名思义,CIColorInvert 为 CIColorMatrix 提供反转输入图像颜色的向量。
按照清单 5-1和清单 5-2中所示的简单示例来构建您自己的滤镜。
清单 5-1 CIColorInvert 滤镜的接口
@interface CIColorInvert: CIFilter {
CIImage *inputImage;
}
@property (retain, nonatomic) CIImage *inputImage;
@end
清单 5-2 CIColorInvert 滤镜的 outputImage 方法
@implementation CIColorInvert
@synthesize inputImage;
- (CIImage *) outputImage
{
CIFilter *filter = [CIFilter filterWithName:@"CIColorMatrix"
withInputParameters: @{
kCIInputImageKey: inputImage,
@"inputRVector": [CIVector vectorWithX:-1 Y:0 Z:0],
@"inputGVector": [CIVector vectorWithX:0 Y:-1 Z:0],
@"inputBVector": [CIVector vectorWithX:0 Y:0 Z:-1],
@"inputBiasVector": [CIVector vectorWithX:1 Y:1 Z:1],
}];
return filter.outputImage;
}
从源图像中去除一种颜色 或一定范围的颜色,然后将源图像与背景图像合成。
图 5-1 色度键滤镜处理链

要创建色度键滤镜:
以下部分展示了如何执行每个步骤。
颜色立方体是一个 3D 颜色查找表。
核心图像滤镜 CIColorCube 将颜色值作为输入,并将查找表应用于这些值。
CIColorCube 的默认查找表是一个单位矩阵 — — 这意味着它不会对其提供的数据执行任何操作。
但是,此配方要求您从图像中删除所有绿色。(如果您愿意,可以删除其他颜色。)
您需要将绿色设置为 alpha = 0.0,使其透明,从而从图像中移除所有绿色。“绿色”涵盖一系列颜色。
最直接的方法是将图像中的颜色值从 RGBA 转换为 HSV 值。
在 HSV 中,色调表示为围绕圆柱体中心轴的角度。
在这种表示中,您可以将颜色可视化为饼状图,然后只需移除代表色度键颜色的饼状图即可。
要去除绿色,您需要定义中心通道周围包含绿色色调的最小和最大角度。
然后,对于任何绿色,您将其 alpha 值设置为 0.0。纯绿色的值对应于 120º。
最小和最大角度需要以该值为中心。
立方体贴图数据必须预乘 alpha,因此创建立方体贴图的最后一步是将 RGB 值乘以刚刚计算的 alpha 值,该值对于绿色色调为 0.0,否则为 1.0。
清单 5-3显示了如何创建此滤镜配方所需的颜色立方体。
清单 5-3 代码中的颜色立方体
// Allocate memory
const unsigned int size = 64;
float *cubeData = (float *)malloc (size * size * size * sizeof (float) * 4);
float rgb[3], hsv[3], *c = cubeData;
// Populate cube with a simple gradient going from 0 to 1
for (int z = 0; z < size; z++){
rgb[2] = ((double)z)/(size-1); // Blue value
for (int y = 0; y < size; y++){
rgb[1] = ((double)y)/(size-1); // Green value
for (int x = 0; x < size; x ++){
rgb[0] = ((double)x)/(size-1); // Red value
// Convert RGB to HSV
// You can find publicly available rgbToHSV functions on the Internet
rgbToHSV(rgb, hsv);
// Use the hue value to determine which to make transparent
// The minimum and maximum hue angle depends on
// the color you want to remove
float alpha = (hsv[0] > minHueAngle && hsv[0] < maxHueAngle) ? 0.0f: 1.0f;
// Calculate premultiplied alpha values for the cube
c[0] = rgb[0] * alpha;
c[1] = rgb[1] * alpha;
c[2] = rgb[2] * alpha;
c[3] = alpha;
c += 4; // advance our pointer into memory for the next color value
}
}
}
// Create memory with the cube data
NSData *data = [NSData dataWithBytesNoCopy:cubeData
length:cubeDataSize
freeWhenDone:YES];
CIColorCube *colorCube = [CIFilter filterWithName:@"CIColorCube"];
[colorCube setValue:@(size) forKey:@"inputCubeDimension"];
// Set data for cube
[colorCube setValue:data forKey:@"inputCubeData"];
现在您有了颜色图数据,请将前景图像(您想要从中去除绿色的图像)提供给 CIColorCube 过滤器并获取输出图像。
[colorCube setValue:myInputImage forKey:kCIInputImageKey];
CIImage *result = [colorCube valueForKey:kCIOutputImageKey];
设置CISourceOverCompositing滤镜的输入参数如下:
inputImage为由 CIColorCube 过滤器生成的图像。inputBackgroundImage为显示新背景的图像。前景图像现在看起来就像是在海滩上。
增加图像中检测到的脸部边缘的亮度。
图 5-2 白色晕影滤镜处理链

要创建白色晕影滤镜:
以下部分展示了如何执行每个步骤。
使用该类CIDetector在图像中定位人脸。
返回的数组中的第一个项featuresInImage:options:是过滤器操作的人脸。
获得人脸后,根据检测器提供的边界计算人脸的中心。
您需要中心值来创建阴影图。
清单 5-4显示了如何使用 CIDetector 定位人脸。
清单 5-4 使用 CIDetector 定位一张脸
IDetector *detector = [CIDector detectorOfType:CIDetectorTypeFace
context:nil
options:nil];
NSArray *faceArray = [detector featuresInImage:image options:nil];
CIFeature *face = faceArray[0];
CGFloat xCenter = face.bounds.origin.x + face.bounds.size.width/2.0;
CGFloat yCenter = face.bounds.origin.y + face.bounds.size.height/2.0;
CIVector *center = [CIVector vectorWithX:xCenter Y:yCenter];
使用 CIRadialGradient 滤镜创建以脸部为中心的阴影图。
阴影图的中心应为透明,这样图像中的脸部就不会受到影响。
阴影图的边缘应为不透明的白色。中间的区域应具有不同程度的透明度。
为了实现此效果,请将输入参数设置为 CIRadialGradient,如下所示:
inputRadius0为大于图像最长尺寸的值。inputRadius1为大于面的值,例如face.bounds.size.height + 50。inputColor0为不透明白色。inputColor1为透明白色。inputCenter计算的脸部边界的中心。设置CISourceOverCompositing滤镜的输入参数如下:
inputImage 为原始图像。inputBackgroundImage为上一步生成的阴影图。选择性聚焦图像来模拟微型场景。
图 5-3 移轴滤镜处理链

要创建移轴滤镜:
以下部分展示了如何执行每个步骤。
设置CIGaussianBlur滤镜的输入参数如下:
inputImage为您想要处理的图像。inputRadius为 10.0(这是默认值)。从单一颜色(例如绿色或灰色)创建一个从上到下变化的线性渐变。
设置 CILinearGradient 的输入参数如下:
inputPoint0为(0, 0.75 * h)inputColor0为 (0,1,0,1)inputPoint1为(0,0.5*h)inputColor1为 (0,1,0,0)创建一个从下到上变化的绿色线性渐变。
设置CILinearGradient的输入参数如下:
inputPoint0为(0,0.25 * h)inputColor0为 (0,1,0,1)inputPoint1为(0,0.5*h)inputColor1为 (0,1,0,0)要创建蒙版,请按如下方式设置 CIAdditionCompositing 滤镜的输入参数:
inputImage为您创建的第一个线性渐变。inputBackgroundImage为您创建的第二个线性渐变。最后一步是使用 CIBlendWithMask 滤镜,设置输入参数如下:
inputImage为图像的模糊版本。inputBackgroundImage为原始的、未处理的图像。inputMaskImage为蒙版,即组合渐变。蒙版只会影响图像的外部。
蒙版的透明部分将透过原始的、未处理的图像显示出来。
蒙版的不透明部分允许模糊的图像显示出来。
在图像中查找面部并将其像素化,以使之无法被识别。
图 5-4 匿名面孔过滤器处理链

要创建匿名面孔过滤器:
以下部分展示了如何执行每个步骤。
设置过滤器的输入参数CIPixellate如下:
inputImage为包含面部的图像。inputScale为max(width, height)/60或您认为合适的其他值,其中width和height指的是图像的宽度和高度。使用CIDetector类来查找图像中的脸部。
对于每张脸部:
CIRadialGradient过滤器创建一个围绕脸部的圆圈。CISourceOverCompositing过滤器为蒙版添加渐变。清单 5-5 为 图像中检测到的脸部构建掩码
CIDetector *detector = [CIDetector detectorOfType:CIDetectorTypeFace
context:nil
options:nil];
NSArray *faceArray = [detector featuresInImage:image options:nil];
// Create a green circle to cover the rects that are returned.
CIImage *maskImage = nil;
for (CIFeature *f in faceArray) {
CGFloat centerX = f.bounds.origin.x + f.bounds.size.width / 2.0;
CGFloat centerY = f.bounds.origin.y + f.bounds.size.height / 2.0;
CGFloat radius = MIN(f.bounds.size.width, f.bounds.size.height) / 1.5);
CIFilter *radialGradient = [CIFilter filterWithName:@"CIRadialGradient" withInputParameters:@{
@"inputRadius0": @(radius),
@"inputRadius1": @(radius + 1.0f),
@"inputColor0": [CIColor colorWithRed:0.0 green:1.0 blue:0.0 alpha:1.0],
@"inputColor1": [CIColor colorWithRed:0.0 green:0.0 blue:0.0 alpha:0.0],
kCIInputCenterKey: [CIVector vectorWithX:centerX Y:centerY],
}];
CIImage *circleImage = [radialGradient valueForKey:kCIOutputImageKey];
if (nil == maskImage)
maskImage = circleImage;
else
maskImage = [[CIFilter filterWithName:@"CISourceOverCompositing" withInputParameters:@{
kCIInputImageKey: circleImage,
kCIInputBackgroundImageKey: maskImage,
}] valueForKey:kCIOutputImageKey];
}
将 CIBlendWithMask 滤镜的输入参数设置为以下内容:
inputImage为图像的像素化版本。inputBackgroundImage为原始图像。inputMaskImage为合成的绿色圆圈。通过对每幅图像进行像素化,实现从一幅图像到另一幅图像的过渡。
图 5-5 Pixellate Transition 滤镜处理链

要创建像素化转换滤镜:
以下部分展示了如何执行每个步骤。
设置CIDissolveTransition滤镜的输入参数如下:
inputImage为您想要过渡的图像。inputTagetImage为您想要过渡到的图像。inputTime为类似于的值min(max(2*(time - 0.25), 0), 1),这是一个介于两个值之间的斜坡函数。通过设置 CIPixellate 滤镜的输入参数来改变像素的比例,如下所示:
inputImage为 CIDissolveTransition 过滤器的输出图像。inputScale通过提供三角函数的值来设置为随时间变化:90*(1 - 2*abs(time - 0.5))inputCenter。降低视频图像的质量,使其看起来像旧的、粗糙的模拟电影。
图 5-6 老电影滤镜处理链

要创建旧电影滤镜:
以下部分展示了如何执行每个步骤。
设置CISepiaTone的输入参数如下:
inputImage为要应用效果的视频图像。inputIntensity为 1.0。使用 CIRandomGenerator 过滤器,它会产生有色噪声。
它没有任何输入参数。
为了处理噪声以便只得到白色斑点,请使用 CIColorMatrix 滤镜,其输入参数设置如下:
inputImage为随机生成器产生的输出。inputRVector、inputGVector和inputBVector设为 (0,1,0,0)。inputBiasVector为 (0,0,0,0)。使用 CISourceOverCompositing 滤镜将斑点与视频图像混合,方法是设置滤镜的输入参数,如下所示:
inputImage为 CIColorMatrix 过滤器生成的白色斑点图像。inputBackgroundImage为 CISepiaTone 过滤器生成的图像。再次使用 CIRandomGenerator 滤镜生成有色噪声。
然后使用具有以下输入参数的 CIAffineTransform 滤镜处理其输出:
inputImage为 CIRandomGenerator 过滤器产生的噪声。inputTransform为将 x 缩放 1.5 倍,将 y 缩放 25 倍。使用 CIAffineTransform 的替代方法是使用 imageByApplyingTransform: 方法来转换噪声。
为了使像素变暗,请按如下方式设置 CIColorMatrix 滤镜的输入参数:
inputImage为转换后的视频图像。inputRVector为 (4,0,0,0)。inputGVector、inputBVector和inputAVector设为 (0,0,0,0)。inputBiasVector为 (0,1,1,1)。这会造成青色的划痕。
要使划痕变暗,请将 CIMinimumComponent 滤镜应用于青色划痕。
此滤镜使用 r、g、b 值的最小值来生成灰度图像。
设置CIMultiplyCompositing滤镜的输入参数如下:
inputBackgroundImage为处理后的视频图像(棕褐色调、白色斑点)。inputImage为深色划痕,即来自 CIMinimumComponent 过滤器的输出。Core Image 提供了许多用于创建图像、上下文和渲染内容的选项。
您选择如何完成任务取决于:
您应该阅读性能最佳实践,以确保您的应用尽可能高效运行。
遵循以下做法可获得最佳性能:
不要CIContext在每次渲染时都创建对象。
上下文存储了大量的状态信息;重用它们会更有效率。
评估您的应用是否需要色彩管理。
除非您需要,否则不要使用它。
请参阅您的应用是否需要色彩管理?。
CIImage在使用 GPU 上下文渲染对象时避免使用 Core Animation 动画。
如果需要同时使用两者,则可以将两者设置为使用 CPU。
确保图像不超过 CPU 和 GPU 限制。
对象的图像大小限制CIContext因 Core Image 使用 CPU 还是 GPU 而异。
使用方法inputImageMaximumSize和检查限制outputImageMaximumSize。
尽可能使用较小的图像。
性能随输出像素数量而变化。
您可以让 Core Image 渲染到较小的视图、纹理或帧缓冲区中。
允许 Core Animation 放大到显示尺寸。
使用 Core Graphics 或 Image I/O 函数来裁剪或下采样,例如函数CGImageCreateWithImageInRect或CGImageSourceCreateThumbnailAtIndex。
该UIImageView课程最适合用于静态图像。
如果您的应用需要获得最佳性能,请使用较低级别的 API。
避免 CPU 和 GPU 之间不必要的纹理传输。
在应用内容比例因子之前,渲染为与源图像大小相同的矩形。
考虑使用可以产生与算法过滤器类似的结果的更简单的过滤器。
例如,CIColorCube 可以产生类似于 CISepiaTone 的输出,而且效率更高。
利用 iOS 6.0 及更高版本对 YUV 图像的支持。
相机像素缓冲区本身是 YUV,但大多数图像处理算法都需要 RBGA 数据。
在两者之间进行转换需要付出代价。
Core Image 支持从CVPixelBuffer对象读取 YUB 并应用适当的颜色变换。
options = @{ (id)kCVPixelBufferPixelFormatTypeKey :
@(kCVPixelFormatType_420YpCbCr88iPlanarFullRange) };
默认情况下,Core Image 在光线性色彩空间中应用所有滤镜。
这可提供最准确和一致的结果。
与 sRGB 之间的转换增加了过滤器的复杂性,并且需要 Core Image 应用以下方程:
rgb = mix(rgb.0.0774, pow(rgb*0.9479 + 0.05213, 2.4), step(0.04045, rgb))
rgb = mix(rgb12.92, pow(rgb*0.4167) * 1.055 - 0.055, step(0.00313, rgb))
如果出现以下情况,请考虑禁用颜色管理:
要禁用颜色管理,请将kCIImageColorSpace键设置为null。
如果您使用的是 EAGL 上下文,请在创建 EAGL 上下文时将上下文颜色空间设置为null 。
请参阅使用核心图像上下文构建您自己的工作流程。
CIImageAccumulator 类非常适合基于反馈的处理。
顾名思义,它会随着时间的推移积累图像数据。
本章介绍如何使用对象实现一个名为 MicroPaint 的简单绘画应用程序,该应用程序允许用户在画布上 绘画以创建类似于图 7-1CIImageAccumulator所示的图像。
图 7-1 MicroPaint 的输出

“图像”一开始是一张空白画布。
MicroPaint 使用图像累加器 来收集用户涂抹的颜料。
当用户单击“清除”时,MicroPaint 会将图像累加器重置为白色画布。
颜色井允许用户更改颜料颜色。
用户可以使用滑块更改画笔大小。
为 MicroPaint 应用程序创建图像累加器的基本任务包括:
本章仅介绍创建图像累加器和支持在其上绘图所必需的代码。
这里不讨论绘制到视图和处理视图大小变化的方法。
有关这些内容,请参阅*CIMicroPaint*,这是一个完整的示例代码项目,您可以下载并更详细地查看。
CIMicroPaint有几个有趣的细节。
它展示了如何绘制到 OpenGL 视图并保持与以前版本的 OS X 的向后兼容性。
MicroPaint 的接口需要以下内容:
构建过滤器词典声明MircoPaintView为 SampleCIView的子类。
此处不讨论 SampleCIView 类;它是 NSOpenGLView 的子类。
有关详细信息,请参阅*CIMicroPaint*示例应用程序。
清单 7-1 MicroPaint 应用程序的界面
@interface MicroPaintView : SampleCIView {
CIImageAccumulator *imageAccumulator;
CIFilter *brushFilter;
CIFilter *compositeFilter;
NSColor *color;
CGFloat brushSize;
}
@end
初始化 MicroPaint 应用程序时(如清单 7-2所示),需要创建画笔和复合滤镜,并设置初始画笔大小和绘画颜色。
清单 7-2中的代码被创建并初始化为透明黑色,输入半径为 0。
当用户拖动光标时,画笔滤镜将采用当前的画笔大小和颜色值。
清单 7-2 初始化过滤器、画笔大小和油漆颜色
brushFilter = [CIFilter filterWithName: @"CIRadialGradient" withInputParameters:@{
@"inputColor1": [CIColor colorWithRed:0.0 green:0.0 blue:0.0 alpha:0.0],
@"inputRadius0": @0.0,
}];
compositeFilter = [CIFilter filterWithName: @"CISourceOverCompositing"];
brushSize = 25.0;
color = [NSColor colorWithDeviceRed: 0.0 green: 0.0 blue: 0.0 alpha: 1.0];
每当用户单击或拖动光标到画布上时,都会调用 mouseDragged: 方法。
它会更新画笔和合成过滤器的值,并向累积图像添加新的绘画操作。
设置图像后,您需要触发显示更新。
您的drawRect:方法处理图像的绘制。
绘制到对象时CIContext,请确保使用drawImage:inRect:fromRect:而不是已弃用的 drawImage:atPoint:fromRect: 方法。
清单 7-3 设置并将画笔滤镜应用于累积图像
- (void)mouseDragged:(NSEvent *)event
{
CGRect rect;
NSPoint loc = [self convertPoint: [event locationInWindow] fromView: nil];
CIColor *cicolor;
// Make a rectangle that is centered on the drag location and
// whose dimensions are twice of the current brush size
rect = CGRectMake(loc.x-brushSize, loc.y-brushSize,
2.0*brushSize, 2.0*brushSize);
// Set the size of the brush
// Recall this is really a radial gradient filter
[brushFilter setValue: @(brushSize)
forKey: @"inputRadius1"];
cicolor = [[CIColor alloc] initWithColor: color];
[brushFilter setValue: cicolor forKey: @"inputColor0"];
[brushFilter setValue: [CIVector vectorWithX: loc.x Y:loc.y]
forKey: kCIInputCenterKey];
// Composite the output from the brush filter with the image
// accummulated by the image accumulator
[compositeFilter setValue: [brushFilter valueForKey: kCIOutputImageKey]
forKey: kCIInputImageKey];
[compositeFilter setValue: [imageAccumulator image]
forKey: kCIInputBackgroundImageKey];
// Set the image accumluator to the composited image
[imageAccumulator setImage: [compositeFilter valueForKey: kCIOutputImageKey]
dirtyRect: rect];
// After setting the image, you need to trigger an update of the display
[self setImage: [imageAccumulator image] dirtyRect: rect];
}
Core Image 提供对编写自定义过滤器的支持。
自定义过滤器需要编写一个例程(称为内核),该例程指定对每个源图像像素执行的计算。
如果您打算使用内置的 Core Image 过滤器(无论是按原样使用还是通过子类化它们),则无需阅读本章。
如果您打算编写自定义过滤器,则应阅读本章,以便了解自定义过滤器中的处理路径和组件。
阅读完本章后,您可以在创建自定义过滤器中了解如何编写过滤器。
如果您对打包自定义过滤器以进行分发感兴趣,您还应该阅读打包和加载图像单元。
Core Image 是为两种类型的开发者设计的:滤镜客户端和滤镜创建者。
如果你只打算使用 Core Image 滤镜,那么你就是滤镜客户端(filter client)。
如果你打算编写自己的滤镜,那么你就是滤镜创建者(filter creator)。
图 8-1显示了典型过滤器的组件。
图中阴影区域表示“底层”部分,即过滤器客户端不需要知道但过滤器创建者必须了解的部分。
非阴影部分显示了两个方法(attributes和outputImage),它们为过滤器客户端提供数据。
过滤器的attributes方法返回描述过滤器的键值对列表。
该outputImage方法使用以下内容生成图像:
图 8-1 典型滤波器的组件

每个自定义过滤器的核心都是一个内核。
内核指定对每个源图像像素执行的计算。
内核计算可以非常简单,也可以非常复杂。
对于“不执行任何操作”的过滤器来说,一个非常简单的内核可以简单地返回源像素:
destination pixel = source pixel
滤镜创建者使用 OpenGL 着色语言 (glslang) 的变体来指定逐像素计算。(请参阅*核心图像内核语言参考*。)
内核对滤镜客户端来说是不透明的。
滤镜实际上可以使用多个内核例程,将一个例程的输出传递给另一个例程的输入。
有关如何编写自定义滤镜的说明,请参阅创建自定义滤镜。
注意: 内核是使用 glslang 的 Core Image 变体编写的实际例程,滤镜使用它来处理像素。
CIKernel 对象是包含内核例程的 Core Image 对象。
创建滤镜时,您会看到内核例程存在于其自己的文件中(具有 .cikernel 扩展名的文件)。
您可以通过传递 包含内核例程的字符串 以编程方式创建CIKernel对象。
滤镜创建者可以使用 NSBundle 类 指定的体系结构,将自定义滤镜打包为插件或图像单元,从而使任何应用程序都可以使用它们 。
一个图像单元可以包含多个滤镜,如图8-2所示。
例如,您可以编写一组执行不同类型边缘检测的滤镜,并将它们打包为一个图像单元。
滤镜客户端可以使用 Core Image API 加载图像单元并获取该图像单元中包含的滤镜列表。
有关基本信息,请参阅加载图像单元。
有关编写滤镜并将其打包为独立图像单元的深入示例和详细信息,请参阅 图像单元教程。
图 8-2 图像单元包含包装信息以及一个或多个过滤器定义

图 8-3显示了对两个源图像进行操作的滤镜的像素处理路径。
源图像始终指定为CIImage对象。
Core Image 提供了多种获取图像数据的方法。
您可以提供图像的 URL、读取原始图像数据(使用类NSData),或将 Quartz 2D 图像 ( CGContextRef)、OpenGL 纹理或 Core Video 图像缓冲区 ( CVImageBufferRef) 转换为CIImage对象。
请注意,实际输入图像的数量以及过滤器是否需要输入图像取决于过滤器。
过滤器非常灵活 - 过滤器可以:
CICheckerboardGenerator和 CIConstantColorGenerator 滤镜。)CIColorPosterize和 CICMYKHalftone 滤镜。)CIShadedMaterial 中的滤镜。)当您处理图像时,您有责任创建一个CIImage包含适当输入数据的对象。
注意: 尽管CIImage对象具有与之关联的图像数据,但它并不是图像。
您可以将CIImage对象视为图像“配方”。
对象CIImage具有生成图像所需的所有信息,但 Core Image 不会真正渲染图像,除非它被要求这样做。
图 8-3 像素处理路径

每个源图像的像素都由一个CISampler对象(或简称为采样器)获取。
顾名思义,采样器检索图像样本并将其提供给内核。
过滤器创建者为每个源图像提供一个采样器。
过滤器客户端不需要知道有关采样器的任何信息。
采样器定义:
滤镜创建器在内核中定义逐像素图像处理计算,但 Core Image 负责处理这些计算的实际实现。
Core Image 确定使用 GPU 还是 CPU 执行计算。
Core Image 根据设备功能使用 Metal、OpenGL 或 OpenGL ES 实现硬件光栅化。
它通过专门针对评估大型四边形(quads)上的非投影纹理查找的片段程序进行调整的模拟环境实现软件光栅化。
尽管像素处理路径是从源图像到目标,但 Core Image 使用的计算路径是从目标开始,然后返回到源像素,如图8-4所示。
这种反向计算可能看起来笨拙,但它实际上最大限度地减少了任何计算中使用的像素数量。
Core Image 不使用的替代方法是强力处理所有源像素,然后再决定目标需要什么。
让我们仔细看看图 8-4。
图 8-4 Core Image 计算路径

假设图 8-4中的过滤器执行某种合成操作,例如源合成。
过滤器客户端希望重叠两幅图像,以便每幅图像只有一小部分被合成,以实现图 8-4左侧所示的结果。
通过预测目标应该是什么,Core Image 可以确定源图像中的哪些数据会影响最终图像,然后将计算限制在这些源像素上。
因此,采样器仅从源图像中的阴影区域获取样本像素,如图8-4所示。
请注意图 8-4中标有定义域的框。
定义域只是进一步限制计算的一种方法。
它是一个区域,在该区域之外的所有像素都是透明的(即 alpha 分量等于 0)。
在此示例中,定义域与目标图像完全重合。
Core Image 允许您提供一个CIFilterShape对象来定义这个区域。
该类CIFilterShape提供了许多方法,可以定义矩形形状、变换形状以及对形状执行插入、并集和交集操作。
例如,如果您使用小于图 8-4中所示阴影区域的矩形定义过滤器形状,则 Core Image 将使用该信息来进一步限制计算中使用的源像素。
Core Image 以其他方式促进高效处理。
它执行智能缓存和编译器优化,使其非常适合实时视频处理和图像分析等任务。
它会缓存任何重复评估的数据集的中间结果。
每当添加新图像导致缓存变得太大时,Core Image 都会按最近最少使用的顺序逐出数据。
经常重用的对象会保留在缓存中,而偶尔使用的对象可能会根据需要移入和移出缓存。
您的应用可以从 Core Image 缓存中受益,而无需了解缓存如何实现的细节。
但是,只要有可能,您可以通过重用对象(图像、上下文等)来获得最佳性能。
Core Image 还通过在内核和通道级别使用传统编译技术获得了出色的性能。
Core Image 用于分配寄存器的方法最大限度地减少了临时寄存器(每个内核)和临时像素缓冲区(每个过滤器图)的数量。
编译器执行多项优化,并自动区分读取基于先前计算的数据相关纹理和不依赖于数据的纹理。
同样,您无需关心编译技术的细节。
重要的是 Core Image 精通硬件;它会尽可能地利用 GPU 和多核 CPU 的功能,并且以智能的方式做到这一点。
Core Image 在与设备无关的工作空间中执行操作。
理论上,Core Image 工作空间是无限的。
工作空间中的点由坐标对 ( x , y ) 表示,其中x表示沿水平轴的位置,y表示沿垂直轴的位置。
坐标是浮点值。默认情况下,原点是点 (0,0)。
当 Core Image 读取图像时,它会将像素位置转换为与设备无关的工作空间坐标。
当需要显示处理后的图像时,Core Image 会将工作空间坐标转换为适合目标(例如显示器)的坐标。
编写自己的过滤器时,您需要熟悉两个坐标空间:目标坐标空间和采样器空间。
目标坐标空间表示您要渲染的图像。
采样器空间表示您要从中进行纹理处理的内容(另一个图像、查找表等)。
您可以使用 destCoord 函数获取目标空间中的当前位置,而samplerCoord函数提供采样空间中的当前位置。
(请参阅*核心图像内核语言参考*。)
请记住,如果您的源数据是平铺的,则采样器坐标有一个偏移量(dx/dy)。
如果您的样本坐标有偏移量,则可能需要使用 samplerTransform 函数将目标位置转换为采样器位置。
虽然图 8-4中没有明确标注,但每个源图像中的阴影区域 都是图中所示采样器的关注区域。
关注区域 (ROI) 定义了源中的区域,采样器从该区域获取像素信息以提供给内核进行处理。
如果您是过滤器客户,则无需关心 ROI。
但如果您是过滤器创建者,您将需要了解关注区域和定义域之间的关系。
回想一下,定义域描述了过滤器的边界形状。
理论上,这个形状可以没有边界。
例如,考虑一个过滤器,它创建一个可以无限延伸的重复图案。
ROI 和定义域可以通过以下方式相互关联:
( r, s )。arcsin)的表提供的值与工作坐标概念无关。除非另有说明,Core Image 会假设 ROI 和定义域一致。
如果您编写的过滤器不符合此假设,则需要为 Core Image 提供一个例程来计算特定采样器的 ROI。
请参阅提供 ROI 函数以了解更多信息。
您可以根据自定义 Core Image 滤镜 是否需要将辅助二进制可执行文件 加载到客户端应用程序的地址空间 来对其进行分类。
使用 Core Image API 时,您会注意到这些滤镜被简称为可执行和不可执行。
滤镜创建者可以选择编写任一类型的滤镜。
滤镜客户端可以选择仅使用不可执行或同时使用两种类型的滤镜。
安全性是区分 CPU 可执行和 CPU 不可执行过滤器的主要动机。
不可执行过滤器仅包含一个 Core Image 内核程序来描述过滤器操作。
相比之下,可执行过滤器还包含在 CPU 上运行的机器代码。
Core Image 内核程序在受限环境中运行,不会构成病毒、特洛伊木马或其他安全威胁,而在 CPU 上运行的任意代码则可以。
不可执行过滤器有特殊要求,其中之一是不可执行过滤器必须作为图像单元的一部分进行打包。
过滤器创建者可以阅读编写不可执行过滤器以了解更多信息。
过滤器客户端可以在加载图像单元中找到有关加载每种过滤器的信息。
预乘 Alpha是用于描述源颜色的术语,其成分已与 Alpha 值相乘。
预乘通过消除对每个颜色成分执行乘法运算的需要来加快图像渲染速度。
例如,在 RGB 颜色空间中,使用预乘 Alpha 渲染图像可消除图像中每个像素的三次乘法运算(红色乘以 Alpha、绿色乘以 Alpha 和蓝色乘以 Alpha)。
滤镜创建者必须向 Core Image 提供预乘了 alpha 值的颜色分量。
否则,滤镜的行为将视为颜色分量的 alpha 值为 1.0。
确保颜色分量预乘对于处理颜色的滤镜来说非常重要。
默认情况下,Core Image 假设处理节点是使用 GenericRGB 颜色空间的 128 位/像素、线性光、预乘 RGBA 浮点值。
您可以通过提供 Quartz 2D CGColorSpace 对象来指定不同的工作颜色空间。
请注意,工作颜色空间必须基于 RGB。
如果您有 YUV 数据作为输入(或其他非基于 RGB 的数据),则可以使用 ColorSync 函数转换为工作颜色空间。
(有关创建和使用 CGColorspace 对象的信息,请参阅*Quartz 2D 编程指南*。)
使用 8 位 YUV 4:2:2 源,Core Image 可以每 GB 处理 240 个高清层。
8 位 YUV 是视频源(如 DV、MPEG、未压缩 D1 和 JPEG)的原生颜色格式。
您需要将 YUV 颜色空间转换为 Core Image 的 RGB 颜色空间。
Shantzis, Michael A.,“高效灵活的图像计算模型”(1994),第 21 届计算机图形学和交互技术年会论文集。
Smith,Alvy Ray,《图像合成基础》,Memo 4,Microsoft,1995 年 7 月。
可从 http://alvyray.com/Memos/MemosCG.htm#ImageCompositing 获取
如果 Core Image 提供的滤镜不能满足您的需求,您可以编写自己的滤镜。
您可以将滤镜作为应用程序项目的一部分,或者(仅限 macOS)将一个或多个滤镜打包为独立的图像单元。
图像单元使用 NSBundle 类 并允许应用程序托管外部插件滤镜。
以下部分提供有关如何创建和使用自定义过滤器和图像单元的详细信息:
Core Image 的工作原理是,内核(即逐像素处理例程)被编写为一种计算,其中输出像素使用反向映射回内核输入图像的相应像素来表示。
虽然您可以用这种方式表达大多数像素计算(有些比其他更自然),但有些图像处理操作很难,甚至不可能。
在编写过滤器之前,您可能需要考虑图像处理操作是否可以在 Core Image 中表达。
例如,计算直方图很难描述为对源图像的反向映射。
本节介绍如何创建具有 Objective-C 部分和内核部分的核心图像滤镜。
按照本节中的步骤,您将创建一个可由 CPU 执行的滤镜。
您可以按照打包和加载图像单元中的说明,将此滤镜(如果您愿意)与其他滤镜一起打包为图像单元。
或者,您也可以直接在自己的应用中使用该滤镜。
有关详细信息,请参阅使用您自己的自定义滤镜。
Core Image 提供三种基于内核的滤镜:颜色滤镜、变形滤镜和通用滤镜。
通用滤镜包含一个 GPU 内核例程,可以修改像素颜色和像素位置。
但是,如果您设计的滤镜仅修改像素颜色,或者更改图像几何形状而不修改像素,则创建颜色或变形滤镜可让 Core Image 在各种 iOS 和 Mac 硬件上提供更好的滤镜性能。
有关详细信息,请参阅CIColorKernel和CIWarpKernel类的参考文档。
本节中的通用过滤器假设感兴趣区域 (ROI) 和定义域重合。
如果您想编写一个不满足此假设的过滤器,请确保您还阅读了提供 ROI 函数。
在创建自己的自定义过滤器之前,请确保您了解 Core Image 坐标空间。
请参阅构建过滤器词典。
要创建自定义 CPU 可执行过滤器,请执行以下步骤:
后面几节将以除雾滤镜为例,详细描述每个步骤。
除雾滤镜的作用是调整图像的亮度和对比度,并对其进行锐化。
此滤镜可用于校正在轻雾或薄雾中拍摄的图像,这通常是从飞机上拍摄图像的情况。
图 9-1显示了使用除雾滤镜处理前后的图像。
使用该滤镜的应用程序提供了滑块,使用户可以调整滤镜的输入参数。
图 9-1 使用去雾滤镜处理前后的图像

执行逐像素处理的代码位于.cikernel扩展名为 的文件中。
您可以在此文件中包括多个内核例程。
如果要使代码模块化,还可以包括其他例程。
您可以使用 OpenGL 着色语言 (glslang) 的子集及其核心图像扩展来指定内核。
有关该语言的允许元素的信息,请参阅*核心图像内核语言参考。*
内核例程签名必须返回一个向量 ( vec4),其中包含将源像素映射到目标像素的结果。
Core Image 为每个像素调用一次内核例程。
请记住,您的代码无法逐个像素地积累知识。
编写代码时的一个好策略是将尽可能多的不变计算从实际内核移出,并将其放置在过滤器的 Objective-C 部分中。
清单 9-1显示了雾霾去除过滤器的内核例程。
清单后面是每行代码的详细说明。
(内核例程示例和*图像单元教程*中还有其他像素处理例程的示例。)
清单 9-1 雾霾去除过滤器的内核例程
kernel vec4 myHazeRemovalKernel(sampler src, // 1
__color color,
float distance,
float slope)
{
vec4 t;
float d;
d = destCoord().y * slope + distance; // 2
t = unpremultiply(sample(src, samplerCoord(src))); // 3
t = (t - d*color) / (1.0-d); // 4
return premultiply(t); // 5
}
代码的作用如下:
接受四个输入参数并返回一个向量。
声明过滤器的接口时,必须确保声明的输入参数数量与内核中指定的输入参数数量相同。
内核必须返回vec4数据类型。
根据目标坐标的y值以及斜率和距离输入参数计算一个值。
该destCoord例程(由 Core Image 提供)返回当前正在计算的像素在工作空间坐标中的位置。
src在应用与之关联的任何变换矩阵后,获取与当前输出像素关联的采样器在采样器空间中的像素值src。
回想一下,Core Image 使用预乘了 alpha 值的颜色分量。
在处理之前,您需要取消预乘从采样器接收到的颜色值。
通过应用除雾公式来计算输出矢量,该公式结合了斜率和距离计算并调整颜色。
根据需要返回一个vec4向量。
内核在返回结果之前执行预乘运算,因为 Core Image 使用预乘了 alpha 值的颜色分量。
关于采样器和样本坐标空间的几句话: 您为向自定义内核提供样本而设置的采样器可以包含过滤器计算所需的任何值,而不仅仅是颜色值。
例如,采样器可以提供数值表、x和y值分别由红色和绿色分量表示的矢量字段、高度字段等的值。
这意味着您可以在采样器中存储最多四个分量的任何矢量值字段。
为了避免过滤器客户端产生混淆,最好提供说明何时不使用矢量来表示颜色的文档。
当您使用不提供颜色的采样器时,您可以通过提供颜色空间来绕过 Core Image 通常执行的颜色校正nil。
Quartz Composer 是一个易于使用的开发工具,您可以用它来测试内核例程。
下载 Quartz Composer
Quartz Composer 提供了一个补丁——Core Image Filter——您可以将您的内核例程放入其中。
您只需打开 Core Image Filter 补丁的检查器,然后在文本字段中粘贴或输入您的代码,如图9-2所示。
图 9-2 粘贴到“设置”窗格中的除雾内核例程

输入代码后,补丁的输入端口会根据内核函数的原型自动创建,如图9-3所示。
补丁始终有一个输出端口,表示内核生成的结果图像。
图 9-3所示的简单合成使用 Image Importer 补丁导入图像文件,通过内核处理它,然后使用 Billboard 补丁将结果渲染到屏幕上。
内核可以使用多张图像,或者如果它生成输出,则可能不需要任何输入图像。
您为测试内核而构建的组合可能比图 9-3中所示的更复杂。
例如,您可能希望将内核例程与其他内置核心图像过滤器或其他内核例程链接在一起。
Quartz Composer 提供了许多其他补丁,您可以在测试内核例程的过程中使用它们。
图 9-3 测试内核例程的 Quartz Composer 组合

过滤器的文件.h包含指定过滤器输入的接口,如清单 9-2所示。
除雾内核有四个输入参数:源、颜色、距离和斜率。
过滤器的接口也必须包含这些输入参数。
输入参数的顺序必须与过滤器指定的顺序相同,并且数据类型必须兼容。
注意: 确保在输入参数名称前加上前缀,如清单 9-2input所示。
清单 9-2 声明 雾霾去除滤镜接口的代码
@interface MyHazeFilter: CIFilter
{
CIImage *inputImage;
CIColor *inputColor;
NSNumber *inputDistance;
NSNumber *inputSlope;
}
@end
过滤器的实现文件包含一个方法,该方法使用 .cikernel 文件中指定的内核例程初始化 Core Image 内核对象 ( CIKernel) 。
一个.cikernel文件可以包含多个内核例程。
清单后面列出了每行带编号的代码的详细说明。
清单 9-3 初始化内核的 init 方法
static CIKernel *hazeRemovalKernel = nil;
- (id)init
{
if(hazeRemovalKernel == nil) // 1
{
NSBundle *bundle = [NSBundle bundleForClass: [self class]]; // 2
NSString *code = [NSString stringWithContentsOfFile: [bundle
pathForResource: @"MyHazeRemoval"
ofType: @"cikernel"]]; // 3
NSArray *kernels = [CIKernel kernelsWithString: code]; // 4
hazeRemovalKernel = kernels[0]; // 5
}
return [super init];
}
代码的作用如下:
CIKernel对象是否已初始化。CIFilter。MyHazeRemoval.cikernel。code 参数指定的字符串,创建一个 CIKernel 对象。.cikernel 文件,都会标记为内核,返回到 kernels数组中。.cikernel 文件中只有一个内核,因此数组仅包含一个项目。hazeRemovalKernel为 kernels 数组中的第一个内核。.cikernel文件包含多个内核,您还将在此例程中初始化这些内核。方法customAttributes允许过滤器的客户端获取过滤器属性,例如输入参数、默认值以及最小值和最大值。
(有关属性的完整列表,请参阅*CIFilter 类参考*。)
过滤器无需提供除其类之外的任何属性信息,但如果不存在属性,则过滤器必须以合理的方式运行。
通常,您的 customAttributes 方法将返回以下属性:
清单 9-4显示了customAttributesHaze 滤镜的方法。
输入参数inputDistance和inputSlope每个都设置了最小值、最大值、滑块最小值、滑块最大值、默认值和标识值。
滑块最小值和最大值用于设置图 9-1所示的滑块。
inputColor参数设置了默认值。
清单 9-4customAttributes Haze 滤镜的 方法
- (NSDictionary *)customAttributes
{
return @{
@"inputDistance" : @{
kCIAttributeMin : @0.0,
kCIAttributeMax : @1.0,
kCIAttributeSliderMin : @0.0,
kCIAttributeSliderMax : @0.7,
kCIAttributeDefault : @0.2,
kCIAttributeIdentity : @0.0,
kCIAttributeType : kCIAttributeTypeScalar
},
@"inputSlope" : @{
kCIAttributeSliderMin : @-0.01,
kCIAttributeSliderMax : @0.01,
kCIAttributeDefault : @0.00,
kCIAttributeIdentity : @0.00,
kCIAttributeType : kCIAttributeTypeScalar
},
kCIInputColorKey : @{
kCIAttributeDefault : [CIColor colorWithRed:1.0
green:1.0
blue:1.0
alpha:1.0]
},
};
}
outputImage 方法为每个输入图像(或图像蒙版)创建一个 CISampler 对象,创建一个 CIFilterShape 对象(如果适用),并应用内核方法。
清单 9-5显示了雾霾去除滤镜的outputImage方法。
代码做的第一件事是设置一个采样器来从输入图像中获取像素。
由于此滤镜仅使用一个输入图像,因此代码仅设置一个采样器。
代码调用 CIFilter 的apply:arguments:options:方法来生成一个CIImage对象。
apply 方法的第一个参数是CIKernel包含除雾核函数的对象。(请参阅编写内核代码。)
回想一下,除雾核函数 接受四个参数:采样器、颜色、距离和斜率。
这些参数作为后四个参数传递给清单 9-5 中的 apply:arguments:options: 方法。
apply 方法的其余参数指定控制 Core Image 如何评估函数的选项(键值对)。
您可以传递三个键之一:kCIApplyOptionExtent、kCIApplyOptionDefinition 或 kCIApplyOptionUserInfo 。
此示例使用 kCIApplyOptionDefinition 键来指定输出图像的定义域 (DOD)。
有关这些键的描述以及有关使用 apply:arguments:options: 方法的更多信息,请参阅*CIFilter 类参考* 。
最后一个参数nil,指定选项列表的结束。
清单 9-5 返回雾霾去除滤镜输出的图像的方法
- (CIImage *)outputImage
{
CISampler *src = [CISampler samplerWithImage: inputImage];
return [self apply: hazeRemovalKernel, src, inputColor, inputDistance,
inputSlope, kCIApplyOptionDefinition, [src definition], nil];
}
清单 9-5是一个简单的示例。
outputImage 方法的实现需要针对过滤器进行量身定制。
如果您的过滤器需要循环不变计算,则应将它们包含在 outputImage 方法中,而不是内核中。
理想情况下,无论您是计划将滤镜分发给其他人,还是仅在自己的应用中使用它,您都会将滤镜打包为图像单元。
如果您计划将此滤镜打包为图像单元,则将使用打包和加载图像单元 中描述的 CIPlugInRegistration 协议注册您的滤镜。
您可以跳过本节的其余部分。
注意: 将自定义过滤器打包为图像单元,可促进模块化编程 和 代码可维护性。
如果出于某种原因您不想将滤镜打包为图像单元(不推荐),则需要使用清单 9-6 中所述 CIFilter 类的注册方法来注册滤镜。
初始化方法调用 registerFilterName:constructor:classAttributes:。
您应该只注册 显示名称(kCIAttributeFilterDisplayName)和滤镜类别( kCIAttributeFilterCategories)。
所有其他滤镜属性都应在customAttributes方法中指定。(请参阅编写自定义属性方法)。
滤镜名称是创建雾霾去除滤镜时要使用的字符串。
指定的构造函数对象实现该filterWithName:方法(请参阅编写方法来创建滤镜实例)。
滤镜类属性被指定为NSDictionary对象。
此滤镜的显示名称(即您在用户界面中显示的名称)为 Haze Remover。
清单 9-6 注册不属于图像单元的过滤器
+ (void)initialize
{
[CIFilter registerFilterName: @"MyHazeRemover"
constructor: self
classAttributes:
@{kCIAttributeFilterDisplayName : @"Haze Remover",
kCIAttributeFilterCategories : @[
kCICategoryColorAdjustment, kCICategoryVideo,
kCICategoryStillImage, kCICategoryInterlaced,
kCICategoryNonSquarePixels]}
];
}
如果您计划仅在自己的应用中使用此滤镜,则需要实现本节中描述的 filterWithName:方法。
如果您计划将此滤镜打包为图像单元供第三方开发人员使用,则可以跳过本节,因为您的打包滤镜可以使用 CIFilter 类提供的 filterWithName:方法。
清单 9-7中所示的 filterWithName: 方法在 收到请求时 创建过滤器的实例。
清单 9-7 创建过滤器实例的方法
+ (CIFilter *)filterWithName: (NSString *)name
{
CIFilter *filter;
filter = [[self alloc] init];
return filter;
}
按照以下步骤 创建滤镜后,您可以在自己的应用中使用该滤镜。
有关详细信息,请参阅使用您自己的自定义滤镜。
如果您想将一个或一组滤镜 作为插件提供给其他应用,请参阅打包和加载图像单元。
使用自定义滤镜的步骤与使用 Core Image 提供的任何滤镜的步骤相同,只是必须初始化滤镜类。
使用以下代码行初始化上一节中创建的除雾滤镜类:
[MyHazeFilter class];
清单 9-8展示了如何使用除雾滤镜。
请注意此代码与处理图像中讨论的代码之间的相似性。
注意: 如果您已将滤镜打包为图像单元,则需要加载它。
有关详细信息,请参阅处理图像。
清单 9-8 使用您自己的自定义过滤器
- (void)drawRect: (NSRect)rect
{
CGRect cg = CGRectMake(NSMinX(rect), NSMinY(rect),
NSWidth(rect), NSHeight(rect));
CIContext *context = [[NSGraphicsContext currentContext] CIContext];
if(filter == nil) {
NSURL *url;
[MyHazeFilter class];
url = [NSURL fileURLWithPath: [[NSBundle mainBundle]
pathForResource: @"CraterLake" ofType: @"jpg"]];
filter = [CIFilter filterWithName: @"MyHazeRemover"
withInputParameters:@{
kCIInputImageKey: [CIImage imageWithContentsOfURL: url],
kCIInputColorKey: [CIColor colorWithRed:0.7 green:0.9 blue:1],
}];
}
[filter setValue: @(distance) forKey: @"inputDistance"];
[filter setValue: @(slope) forKey: @"inputSlope"];
[context drawImage: [filter valueForKey: kCIOutputImageKey]
atPoint: cg.origin fromRect: cg];
}
感兴趣区域 (ROI) 定义源中的区域,采样器从该区域获取像素信息以提供给内核进行处理。
回想一下查询系统中的过滤器中关于感兴趣区域的讨论,ROI 和 DOD 的工作空间坐标要么完全重合,要么相互依赖,要么不相关。
Core Image 始终假设 ROI 和 DOD 重合。
如果您编写的过滤器是这种情况,则无需提供 ROI 函数。
但是,如果此假设不适用于您编写的过滤器,则必须提供 ROI 函数。
此外,您只能为 CPU 可执行过滤器提供 ROI 函数。
注意: CPU 不可执行过滤器的 ROI 和定义域必须一致。
对于 CIColorKernel 类描述的颜色内核也是如此。
您无法为这些类型的过滤器提供 ROI 函数。
请参阅编写不可执行过滤器。
您提供的 ROI 函数会计算内核使用的每个采样器的关注区域。
Core Image 会调用您的 ROI 函数,向其传递采样器索引、要渲染的区域范围以及例程所需的任何数据。
在 OS X v10.11 及更高版本和 iOS 8.0 及更高版本中,建议使用applyWithExtent:roiCallback:arguments:或applyWithExtent:roiCallback:inputImage:arguments:方法 应用过滤器,您可以向该方法提供回调函数作为块 (Objective-C) 或闭包 (Swift)。
注意: 在 OS X v10.10 及更早版本中,在调用过滤器apply:或apply:arguments:options:方法之前,使用 setROISelector: 方法提供 ROI 函数。
以下讨论假设使用 OS X v10.11 和 iOS 8.0 API;但是,每个示例 ROI 函数的内部工作原理对于新旧 API 都是相同的。
有关 ROI 函数的选择器形式的详细信息,请参阅该setROISelector:方法的参考文档。
ROI 回调是一个块或闭包,其签名符合 CIKernelROICallback 类型。
此块采用两个参数:第一个参数index,指定该方法为其计算 ROI 的采样器,第二个参数,rect指定需要 ROI 信息的区域范围。
Core Image 每次通过过滤器时 都会调用您的例程。
您的方法根据传递给它的矩形计算 ROI,并返回指定为 CGRect 结构的 ROI 。
接下来的部分提供了 ROI 函数的示例。
如果你的 ROI 函数 不需要在参数中 传递数据userInfo,那么你不需要包含该参数,如清单 9-9所示。
清单 9-9中的代码将采样器超出一个像素,这是边缘查找滤波器或任何 3x3 卷积使用的计算。
清单 9-9 一个简单的 ROI 函数
CIKernelROICallback callback = ^(int index, CGRect rect) {
return CGRectInset(rect, -1.0, -1.0);
};
请注意,此函数会忽略该index值。
如果您的内核仅使用一个采样器,那么您可以忽略索引。
如果您的内核使用多个采样器,则必须确保返回适合指定采样器的 ROI。
您将在后面的部分中看到如何做到这一点。
清单 9-10显示了玻璃失真滤镜的 ROI 函数。
此函数返回两个采样器的 ROI。
采样器0表示要失真的图像,采样器1表示用于玻璃的纹理。
与块 (Objective-C) 或闭包 (Swift) 的其他用法一样,ROI 回调可以从定义的上下文中捕获状态。
您可以使用此行为 为您的例程 提供其他参数,如本例所示:外部值scale控制 ROI 函数应用的插入。
(使用旧版 setROISelector: API 时,您可以使用传递给 apply:arguments:options: 方法 的 options 字典中的 kCIApplyOptionUserInfo 键来提供此类值。
需要引用所有玻璃纹理(采样器1),因为过滤器将纹理用作矩形图案。
因此,该函数返回一个无限矩形作为 ROI。
无限矩形是一种惯例,表示使用所有采样器。
(该常量CGRectInfinite在 Quartz 2D API 中定义。)
注意: 如果您使用无限 ROI,请确保采样器的定义域也不是无限的。
否则,Core Image 将无法渲染图像。
清单 9-10 玻璃失真滤波器的 ROI 函数
float scale = 1.0f;
CIKernelROICallback distortionCallback = ^(int index, CGRect rect) {
if (index == 0) {
CGFloat s = scale * 0.5f;
return CGRectInset(rect, -s,-s);
}
return CGRectInfinite;
};
清单 9-11显示了 ROI 函数,该函数返回使用三个采样器的内核的 ROI,其中一个是环境贴图。
sampler0和 sampler1 的 ROI 与 DOD 重合。
因此,对于除 sampler 2 之外的采样器,代码将返回 传递给它的 destination 矩形 。
采样器2使用 指定环境贴图大小的捕获值 来创建 指定感兴趣区域的矩形。
清单 9-11 提供计算感兴趣区域的例程
CGRect sampler2ROI = CGRectMake(0, 0, envMapWidth, envMapHeight);
CIKernelROICallback envMapROICallback = ^(int index, CGRect rect) {
if (samplerIndex == 2) {
return sampler2ROI;
}
return destination;
};
从前面的例子中可以看出,采样器有一个与之关联的索引。
当您提供 ROI 函数时,Core Image 会将采样器索引传递给您。
采样器索引是根据其传递给过滤器的 apply 方法时的顺序分配的。
您可以从过滤器的 outputImage 例程中 调用apply,如清单 9-12所示。
在此清单中,请特别注意 设置采样器,并显示 如何将它们提供给内核 的带编号的代码行。
清单 9-12后面列出了每行代码的详细说明。
列表 9-12 使用环境映射的过滤器的输出图像例程
- (CIImage *)outputImage
{
int i;
CISampler *src, *blur, *env; // 1
CIVector *envscale;
CIKernel *kernel;
src = [CISampler samplerWithImage:inputImage]; // 2
blur = [CISampler samplerWithImage:inputHeightImage]; // 3
env = [CISampler samplerWithImage:inputEnvironmentMap]; // 4
envscale = [CIVector vectorWithX:[inputEMapWidth floatValue]
Y:[inputEMapHeight floatValue]];
i = [inputKind intValue];
if ([inputHeightInAlpha boolValue]) {
i += 8;
}
kernel = roundLayerKernels[i];
return [kernel applyWithExtent: [self extent] // 5
roiCallback: envMapROICallback // 6
arguments: @[ // 7
blur,
env,
@( pow(10.0, [inputSurfaceScale floatValue]) ),
envscale,
inputEMapOpacity,
]];
}
代码的作用如下:
CIImage对象 )。采样器参数的顺序决定了它的索引。
提供给内核的第一个采样器是 index 0。在本例中,这就是src采样器。
提供给内核的第二个采样器blur——被分配了 index 1。
第三个采样器env——被分配了 index 2。
检查你的 ROI 函数以确保为每个采样器提供适当的 ROI 非常重要。
CPU 不可执行的过滤器保证是安全的。
由于这种类型的过滤器仅在 GPU 上运行,因此它无法参与病毒或特洛伊木马活动或其他恶意行为。
为了保证安全性,CPU 不可执行的过滤器具有以下限制:
.cikernel 文件中。color = sample (someSrc, samplerCoord(someSrc));
CIDemoImageUnit 示例在 MyKernelFilter.cikernel 文件中 包含一个不可执行的过滤器。
加载图像单元时,MyKernelFilter 过滤器会与图像单元中的 FunHouseMirror 过滤器 一起加载。
但是,FunHouseMirror 是一个可执行过滤器。
它有一个 Objective-C 部分和一个内核部分。
编写不可执行的过滤器时,需要在 Descriptions.plist 文件中,为图像单元包提供 所有过滤器属性。
清单 9-13显示了 CIDemoImageUnit 示例中 MyKernelFilter 的属性。
清单 9-13 MyKernelFilter 不可执行过滤器的属性列表
<key>MyKernelFilterkey>
<dict>
<key>CIFilterAttributeskey>
<dict>
<key>CIAttributeFilterCategorieskey>
<array>
<string>CICategoryStylizestring>
<string>CICategoryVideostring>
<string>CICategoryStillImagestring>
array>
<key>CIAttributeFilterDisplayNamekey>
<string>MyKernelFilterstring>
<key>CIInputskey>
<array>
<dict>
<key>CIAttributeClasskey>
<string>CIImagestring>
<key>CIAttributeDisplayNamekey>
<string>inputImagestring>
<key>CIAttributeNamekey>
<string>inputImagestring>
dict>
<dict>
<key>CIAttributeClasskey>
<string>NSNumberstring>
<key>CIAttributeDefaultkey>
<real>8real>
<key>CIAttributeDisplayNamekey>
<string>inputScalestring>
<key>CIAttributeIdentitykey>
<real>8real>
<key>CIAttributeMinkey>
<real>1real>
<key>CIAttributeNamekey>
<string>inputScalestring>
<key>CIAttributeSliderMaxkey>
<real>16real>
<key>CIAttributeSliderMinkey>
<real>1real>
dict>
<dict>
<key>CIAttributeClasskey>
<string>NSNumberstring>
<key>CIAttributeDefaultkey>
<real>1.2real>
<key>CIAttributeDisplayNamekey>
<string>inputGreenWeightstring>
<key>CIAttributeIdentitykey>
<real>1.2real>
<key>CIAttributeMinkey>
<real>1real>
<key>CIAttributeNamekey>
<string>inputGreenWeightstring>
<key>CIAttributeSliderMaxkey>
<real>3.0real>
<key>CIAttributeSliderMinkey>
<real>1real>
dict>
array>
dict>
<key>CIFilterClasskey>
<string>MyKernelFilterstring>
<key>CIHasCustomInterfacekey>
<false/>
<key>CIKernelFilekey>
<string>MyKernelFilterstring>
任何图像处理过滤器 的本质都是 执行像素计算的内核。
本节中的代码清单显示了 这些过滤器 的一些典型内核例程:增亮、乘法和孔失真。
通过查看这些代码,您可以了解 如何编写自己的内核例程。
但请注意,这些例程只是示例。
不要假设此处显示的代码就是 Core Image 用于其提供的过滤器的代码。
在编写自己的内核例程之前,您可能需要阅读《在 Core Image 中表达图像处理操作》,以了解哪些操作在 Core Image 中构成挑战。您还需要查看《Core Image 内核语言参考》。
*您可以在图像单元教程*中找到有关编写内核的详细信息以及更多示例。
清单 9-14计算了增亮效果。
清单后面列出了每行代码的详细说明。
清单 9-14 计算增亮效果的内核例程
kernel vec4 brightenEffect (sampler src, float k)
{
vec4 currentSource = sample (src, samplerCoord (src)); // 1
currentSource.rgb = currentSource.rgb + k * currentSource.a; // 2
return currentSource; // 3
}
代码的作用如下:
k通过像素的 alpha 值进行缩放,以确保像素值已预乘。清单 9-15显示了计算乘法效果的内核例程。
代码在采样器中查找源像素,然后将其乘以传递给例程的值。
清单 9-15 计算乘法效应的内核例程
kernel vec4 multiplyEffect (sampler src, __color mul)
{
return sample (src, samplerCoord (src)) * mul;
}
清单 9-16显示了计算孔洞失真的内核例程。
请注意,计算孔洞失真的方法有很多种。
清单后面列出了每行带编号的代码的详细说明。
清单 9-16 计算孔洞畸变的核函数
kernel vec4 holeDistortion (sampler src, vec2 center, vec2 params) // 1
{
vec2 t1;
float distance0, distance1;
t1 = destCoord () - center; // 2
distance0 = dot (t1, t1); // 3
t1 = t1 * inversesqrt (distance0); // 4
distance0 = distance0 * inversesqrt (distance0) * params.x; // 5
distance1 = distance0 - (1.0 / distance0); // 6
distance0 = (distance0 < 1.0 ? 0.0 : distance1) * params.y; // 7
t1 = t1 * distance0 + center; // 8
return sample (src, samplerTransform (src, t1)); // 9
}
代码的作用如下:
1/radius,radius)的 params 向量。t1。distance0变量。t1。(生成t1单位向量。)(distance squared * 1/distance) * 1/radius。x – 1/sqrt (x))图像单元代表核心图像滤镜的插件架构。
图像单元使用类NSBundle作为打包机制,以便您能够将自己创建的滤镜提供给其他应用。
图像单元可以包含可执行或不可执行的滤镜。
(有关详细信息,请参阅可执行和不可执行的滤镜。)
要从自定义过滤器创建图像单元,您必须执行以下任务:
阅读本章后,您可能还想
下载*CIDemoImageUnit*示例。
创建图像单元时,应该有类似的文件。
此图像单元包含一个过滤器 FunHouseMirror。
图像单元中的每个过滤器通常有三个文件:过滤器类的接口文件、相关的实现文件和内核文件。
正如您在示例代码项目中看到的,对于 FunHouseMirror 过滤器来说,情况确实如此:FunHouseMirrorFilter.h、FunHouseMirrorFilter.m和funHouseMirror.cikernel。
每个图像单元还应具有CIPlugInRegistration协议的接口和实现文件。
在图中,请参见MyPlugInLoader.h和MyPlugInLoader.m。
您需要修改的另一个重要文件是文件Description.plist。
现在您已经对图像单元项目中的文件有了一些了解,您可以开始创建一个图像单元项目了。
Xcode 提供了创建图像单元的模板。
创建图像单元项目后,您将拥有开始所需的大部分文件,并且该项目将链接到相应的框架。
在 Xcode 中创建图像单元项目
项目窗口打开,其中创建了以下文件:
MyImageUnitPlugInLoader.h 和 MyImageUnitPlugInLoader.m,CIPlugInRegistration 协议的接口和实现文件MyImageUnitFilter.h 和 MyImageUnit Filter.mMyImageUnitFilterKernel.cikernel图像单元项目中提供的 MyImageUnitKernelFilter.cikernel 文件是示例内核文件。
如果您已经创建了过滤器,则不需要此文件,因此您可以将其删除。
您稍后会将自己的文件添加到项目中。
打开实现 CIPlugInRegistration 协议的文件。
在其中你会发现一个 load 方法,如清单 10-1所示。
你可以选择 向此方法 添加代码,来执行所需的任何初始化,例如注册检查。
如果过滤器加载成功,则该方法将返回 true 。
如果你不需要任何自定义初始化,则可以保留加载方法。
清单 10-1 图像单元模板提供的 load 方法
-(BOOL)load:(void*)host
{
// Custom image unit initialization code goes here
return YES;
}
如果需要,您可以编写一种unload方法来执行过滤器可能需要的任何清理任务。
将之前创建的过滤器文件添加到图像单元项目中。
回想一下,您需要每个过滤器的接口和实现文件以及相关的内核文件。
如果您尚未编写过滤器,请参阅创建自定义过滤器。
请记住,您可以在一个图像单元中打包多个过滤器,并且可以根据需要为过滤器添加任意数量的内核文件。
只需确保包含要打包的所有过滤器和内核文件即可。
对于可执行过滤器,仅从 Description.plist 文件中读取版本号、过滤器类和过滤器名称。
您可以在代码中为过滤器提供属性列表(请参阅编写自定义属性方法)。
您需要检查图像单元模板中提供的 Description.plist 文件以确保过滤器名称正确并输入版本号。
对于 CPU 不可执行的过滤器,图像单元主机读取 Description.plist 文件以获取表 10-1中列出的过滤器属性的信息。
您需要修改 Description.plist 文件以使其包含适当的信息。
(有关过滤器键的信息,另请参阅*核心图像参考集合*。)
Table 10-1 Keys in the filter description property list
| 钥匙 | 关联值 |
|---|---|
CIPlugInFilterList | 过滤器字典的字典。如果存在此键,则表示图像单元中 至少定义了一个核心图像过滤器。 |
CIFilterDisplayName | Description.strings 文件中可用的本地化过滤器名称。 |
CIFilterClass | 包含过滤器实现的二进制文件中的类名(如果可用)。 |
CIKernelFile | 软件包中的过滤器内核的文件名(如果可用)。使用此键定义不可执行的过滤器。 |
CIFilterAttributes | 描述过滤器的属性字典。这与您编写过滤器时提供的属性字典相同。 |
CIInputs | 输入键和相关属性的数组。输入键的顺序必须与内核函数的参数相同。 每个属性必须包含其参数类(参见表10-2)和名称。 |
CIOutputs | 保留以供将来使用。 |
CIHasCustomInterface | 无。使用此键指定过滤器具有自定义用户界面。主机为用户界面提供视图。 |
CIPlugInVersion | CIPlugIn架构的版本为1.0。 |
表 10-2列出了输入参数类以及与每个类关联的值。
对于不可执行的过滤器,您需要为每个输入和输出参数提供参数类。
| 输入参数类 | 关联值 |
|---|---|
| 色彩 | 指定颜色的字符串。 |
| 向量 | 指定向量的字符串。请参阅vectorWithString:。 |
| 图像处理 | 描述图像 相对于捆绑包的相对路径 或 图像的绝对路径的 NSString 对象。 |
| 所有标量类型 | 一个NSNumber值。 |
在开始创建图像单元之前,您应该测试内核代码以确保其正常工作。(请参阅使用 Quartz Composer 测试内核例程。)
成功构建图像单元后,您需要将其复制到以下目录:
/Library/Graphics/Image Units~/Library/Graphics/Image Units然后,您应该尝试从应用程序加载图像单元并使用单元中打包的滤镜(或多个滤镜)。
请参阅加载图像单元、查询系统滤镜和处理图像。
Apple 提供的内置滤镜会被自动加载。
您唯一需要加载的滤镜是 打包为图像单元的第三方滤镜。
图像单元只是一个包,可以包含一个或多个图像处理滤镜。
如果图像单元安装在构建和测试图像单元中讨论的位置之一,那么任何应用程序都可以使用它,只要调用load该类提供的方法之一,如表 10-3CIPlugin所示。
您只需要加载图像单元一次。
例如,要加载所有全局安装的图像单元,您可以将以下代码行添加到应用程序的初始化例程中。
[CIPlugIn loadAllPlugIns];
调用该load方法后,您可以像使用 Apple 提供的任何图像处理滤镜一样继续操作。
请按照本章其余部分的说明进行操作。
| 方法 | 评论 |
|---|---|
loadAllPlugIns | 扫描图像单元目录(/Library/Graphics/Image Units和~/Library/Graphics/Image Units)以查找具有.plugin扩展名的文件,然后加载图像单元。 |
loadNonExecutablePlugIns | 扫描图像单元目录(/Library/Graphics/Image Units和~/Library/Graphics/Image Units)以查找具有.plugin扩展名的文件,然后仅加载图像单元的内核。也就是说,它只加载具有 .cikernel 扩展名的文件。此调用不会执行任何图像单元代码。 |
loadPlugIn:allowNonExecutable: | 在 url 参数指定的位置加载图像单元。如果只想加载图像单元的内核,而不执行任何图像单元代码,请传递 true给 allowNonExecutable 参数。 |
2024-06-02(日)