本文翻译自:Core Image(更新日期:2016-09-13
https://developer.apple.com/library/archive/documentation/GraphicsImaging/Conceptual/CoreImaging/ci_intro/ci_intro.html
文章目录
- 一、关于 Core Image
- 二、处理图像
- 三、检测图像中的人脸
- 四、自动增强图像
- 五、过滤器的系统查询
- 六、子类化 CIFilter:自定义效果的秘诀
- 七、获得最佳表现
- 八、使用反馈来处理图像
- 九、编写自定义过滤器之前需要了解的内容
- 十、创建自定义过滤器
- 十一、打包和加载图像单元
一、关于 Core Image
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 框架提供:
- 访问内置图像处理过滤器
- 特征检测能力
- 支持自动图像增强
- 能够将多个滤镜链接在一起以创建自定义效果
- 支持创建在 GPU 上运行的自定义过滤器
- 基于反馈的图像处理能力
在 macOS 上,Core Image 还提供了打包自定义过滤器 以供其他应用程序使用的方法。
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 以获取过滤器及其属性的列表
Core Image 为其滤镜“内置”了参考文档。您可以查询系统以找出哪些滤镜可用。
然后,对于每个滤镜,您可以检索 包含其属性的字典,例如其输入参数、默认参数值、最小值和最大值、显示名称等。
相关章节: 查询系统过滤器
Core Image 可实现实时视频性能
如果您的应用需要 实时处理视频,您可以采取多种措施来优化性能。
相关章节: 获得最佳性能
使用图像累加器支持基于反馈的处理
CIImageAccumulator
类专为高效的 基于反馈的图像处理而设计,如果您的应用程序 需要对动态系统进行成像,您可能会发现它很有用。
相关章节: 使用反馈处理图像
创建和分发 自定义内核 和 过滤器
如果内置滤镜都不能满足您的需求(即使组合在一起),请考虑创建自定义滤镜。
您需要了解内核(在像素级别运行的程序),因为它们是每个滤镜的核心。
在 macOS 中,你可以将一个或多个自定义滤镜打包为一个图像单元,以便其他应用程序可以加载和使用它们。
相关章节: 编写自定义过滤器前你需要知道的内容、创建自定义过滤器、打包和加载图像单元
也可以看看
Core Image 的其他重要文档包括:
- *Core Image 参考集合*提供了 Core Image 框架中可用的类的详细描述。
- *Core Image Filter Reference*描述了 Apple 提供的内置图像处理滤镜,并展示了图像在使用滤镜处理前后的样子。
- *核心图像内核语言参考*描述了为自定义过滤器创建内核例程的语言。
二、处理图像
处理图像意味着应用滤镜, 图像滤镜是一种软件,它逐个检查输入图像 并通过算法应用某种效果,以创建输出图像。
在 Core Image 中,图像处理依赖于和CIFilter
类CIImage
,它们描述滤镜及其输入和输出。
要应用滤镜并显示或导出结果,您可以利用 Core Image 与其他系统框架之间的集成,或使用 CIContext
类创建自己的渲染工作流程。
本章介绍使用这些类应用滤镜和渲染结果的关键概念。
1、概述
在您的应用中,有多种方法可以使用 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
对象(使用默认选项)。
您并不总是需要自己的 Core Image 上下文 - 通常您可以 与其他 为您管理渲染的系统框架集成。
创建自己的上下文 可以让您更精确地控制 渲染过程 和 渲染所涉及的资源。
上下文是重量级对象,因此如果您确实创建了一个上下文,请尽早创建,并在每次需要处理图像时重复使用它。
(请参阅使用 Core Image 上下文构建您自己的工作流程。) - 实例化
CIFilter
表示 要应用的滤镜的对象,并为其参数提供值。
(请参阅滤镜描述图像处理效果。) - 创建一个
CIImage
表示要处理的图像的对象,并将其作为输入图像参数提供给过滤器。
从 URL 读取图像数据只是创建图像对象的众多方法之一。
(请参阅图像是过滤器的输入和输出。) - 获取
CIImage
表示滤镜输出的对象。
此时滤镜尚未执行 — 图像对象是一个“配方”,指定如何使用指定的滤镜、参数和输入创建图像。
Core Image 仅在您请求渲染时执行此配方。
(请参阅图像是滤镜的输入和输出。) - 将输出图像渲染为 Core Graphics 图像,您可以显示该图像或将其保存到文件中。
(请参阅使用 Core Image Context 构建您自己的工作流程。)
2、图像是过滤器的输入和输出
Core Image 滤镜处理并生成 Core Image 图像。
CIImage
实例是代表图像的不可变对象。
这些对象并不直接表示图像位图数据,而是CIImage
生成图像的“配方”。
一个配方可能要求从文件加载图像;另一个配方可能表示来自滤镜或滤镜链的输出。
Core Image 仅在您请求渲染图像以供显示或输出时才执行这些配方。
要应用滤镜,请创建一个或多个CIImage
表示要由滤镜处理的图像的对象,并将它们分配给滤镜的输入参数(例如kCIInputImageKey
)。
您可以从几乎任何图像数据源创建 Core Image 图像对象,包括:
- 引用要加载的图像文件或
NSData
包含图像文件数据的对象的 URL - Quartz2D、UIKit 或 AppKit 图像表示(
CGImageRef
、UIImage
或NSBitmapImageRep
对象) - 金属、OpenGL 或 OpenGL ES 纹理
- CoreVideo 图像或像素缓冲区(
CVImageBufferRef
或CVPixelBufferRef
) IOSurfaceRef
在进程间共享图像数据的对象- 内存中的图像位图数据(指向此类数据的指针,或
CIImageProvider
按需提供数据的对象)
有关创建对象的方法的完整列表CIImage
,请参阅*CIImage 类参考*。
因为CIImage
对象描述了如何生成图像(而不是包含图像数据),所以它也可以表示滤镜输出。
当您访问对象outputImage
的属性CIFilter
时,Core Image 仅识别并存储执行滤镜所需的步骤。
只有当您请求渲染图像以供显示或输出时,才会执行这些步骤。
您可以使用或方法之一明确请求渲染CIContext``render
(draw
请参阅使用Core Image 上下文构建您自己的工作流程),也可以通过使用与 Core Image 配合使用的众多系统框架之一显示图像来隐式请求渲染(请参阅与其他框架集成)。
将处理推迟到渲染时,使 Core Image 变得快速而高效。
在渲染时,Core Image 可以查看是否需要对图像应用多个滤镜。
如果需要,它会自动连接多个“配方”并组织它们以消除冗余操作,这样每个像素只需处理一次,而不是多次。
3、滤镜描述图像处理效果
该类的实例CIFilter
是一个可变对象,表示图像处理效果以及控制该效果行为的任何参数。
要使用滤镜,您需要创建一个CIFilter
对象,设置其输入参数,然后访问其输出图像(请参阅下面的图像是滤镜的输入和输出)。
调用filterWithName:
初始化程序以使用系统已知的滤镜名称实例化滤镜对象(请参阅查询系统中的滤镜或*核心图像滤镜参考*)。
大多数滤镜都有一个或多个输入参数,可让您控制处理方式。
每个输入参数都有一个指定其数据类型的属性类,例如NSNumber
。
输入参数可以选择其他属性,例如其默认值、允许的最小值和最大值、参数的显示名称以及*CIFilter 类参考*中描述的其他属性。
例如,CIColorMonochrome滤镜有三个输入参数 - 要处理的图像、单色和颜色强度。
滤镜参数定义为键值对;要使用参数,您通常使用valueForKey:
和方法或其他基于键值编码的功能(例如 Core Animation)。
键是标识属性的常量,值是与键关联的设置。
Core Image 属性值通常使用属性值数据类型setValue:forKey:
中列出的数据类型之一。
数据类型 | 目的 | 描述 |
---|---|---|
字符串 | NSString | 文本,通常用于显示给用户 |
浮点值 | NSNumber | 标量值,例如强度级别或半径 |
向量 | CIVector | 一组浮点值,可以指定位置、大小、矩形或未标记的颜色分量值 |
颜色 | CIColor | 一组颜色分量值,标有颜色空间,指定如何解释它们 |
图片 | CIImage | 图像;请参阅图像是过滤器的输入和输出 |
变换 | NSData ,NSAffineTransform | 应用于图像的坐标变换 |
重要提示: CIFilter
对象是可变的,因此您无法安全地在不同线程之间共享它们。
每个线程都必须创建自己的CIFilter
对象。
但是,过滤器的输入和输出CIImage
对象是不可变的,因此可以在线程之间安全地传递。
3.1 链接过滤器以实现复杂效果
每个 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
。
(参见清单 1-1。)- 使用初始化程序在一次调用中创建一个
CIFilter
实例并设置其参数(包括输入图像)filterWithName:withInputParameters:
,然后使用该outputImage
属性访问输出。
(参见清单 1-2colorFilter
中的示例。) CIFilter
使用该imageByApplyingFilter:withInputParameters:
方法对对象应用滤镜而无需创建实例。
(参见清单 1-2CIImage
中的 bloomImage 示例。)- 对于某些常用的滤镜操作(如裁剪、限制和应用坐标变换),请使用 通过修改现有图像创建图像
CIImage
中列出的其他实例方法。
(请参阅清单 1-2中的 croppedImage 示例。)
3.2 使用特殊过滤器类型获得更多选项
大多数内置的 Core Image 滤镜都针对主输入图像(可能还会对处理产生影响的其他输入图像)进行操作,并创建单个输出图像。
但您可以使用其他几种类型的滤镜来创建有趣的效果或与其他滤镜结合使用,以产生更复杂的工作流程。
- 合成(或混合)滤镜根据预设公式将两幅图像组合在一起。
例如:- CISourceInCompositing滤镜将图像组合起来,使得只有两个输入图像中不透明的区域在输出图像中可见。
- CIMultiplyBlendMode过滤器将两个图像中的像素颜色相乘,产生变暗的输出图像。
有关合成过滤器的完整列表,请查询CICategoryCompositeOperation类别。
注意: 您可以在合成之前通过对每幅图像应用几何调整来排列输入图像。
请参阅CICategoryGeometryAdjustment滤镜类别或imageByApplyingTransform:
方法。
- 生成器滤镜不接受输入图像。
相反,这些滤镜使用其他输入参数从头开始创建新图像。
有些生成器产生的输出可以单独使用,而有些生成器可以组合成滤镜链以生成更有趣的图像。
内置 Core Image 滤镜中的一些示例包括:- CIQRCodeGenerator和CICode128BarcodeGenerator等过滤器会生成对指定输入数据进行编码的条形码图像。
- CIConstantColorGenerator、CICheckerboardGenerator和CILinearGradient等滤镜可根据指定颜色生成简单的程序图像。
您可以将这些滤镜与其他滤镜结合使用,以产生有趣的效果 - 例如,CIRadialGradient滤镜可以创建与CIMaskedVariableBlur滤镜一起使用的遮罩。 - CILenticularHaloGenerator和CISunbeamsGenerator等过滤器可以创建独立的视觉效果 - 将它们与合成过滤器相结合,为图像添加特殊效果。
要查找生成器过滤器,请查询CICategoryGenerator和CICategoryGradient类别。
- 缩减过滤器对输入图像进行操作,但其输出不是以传统意义上创建输出图像,而是描述有关输入图像的信息。例如:
- CIAreaMaximum过滤器输出一个单一颜色值,代表图像指定区域中所有像素颜色最亮的颜色值。
- CIAreaHistogram过滤器输出有关图像指定区域中每个强度值的像素数量的信息。
所有 Core Image 滤镜都必须生成一个CIImage
对象作为其输出,因此缩减滤镜生成的信息仍然是图像。
但是,您通常不会显示这些图像,而是从单像素或单行图像中读取颜色值,或将它们用作其他滤镜的输入。
要获取缩减过滤器的完整列表,请查询CICategoryReduction类别。
- 过渡滤镜接收两幅输入图像,并根据独立变量(通常,此变量是时间)在两幅图像之间改变其输出,因此您可以使用过渡滤镜创建一个动画,该动画从一幅图像开始,在另一幅图像上结束,并使用有趣的视觉效果从一幅图像过渡到另一幅图像。
Core Image 提供了几个内置过渡滤镜,包括:- CIDissolveTransition滤镜产生简单的交叉溶解,从一张图像淡入到另一张图像。
- CICopyMachineTransition过滤器模拟复印机,将一束明亮的光滑过一幅图像以显示另一幅图像。
要获取过渡过滤器的完整列表,请查询CICategoryTransition类别。
4、与其他框架集成
Core Image 可与 iOS、macOS 和 tvOS 中的其他几种技术进行互操作。
得益于这种紧密集成,您可以使用 Core Image 轻松地为应用用户界面中的游戏、视频或图像添加视觉效果,而无需构建复杂的渲染代码。
以下部分介绍了在应用中使用 Core Image 的几种常见方法以及系统框架为每种方法提供的便利。
4.1 在 UIKit 和 AppKit 中处理静态图像
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
对象的地方使用的图像。
4.2 使用 AV Foundation 处理视频
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滤镜)将图像的边缘像素无限延伸到所有方向。
钳制会创建无限大小的图像,因此您还应该在模糊后裁剪图像。
4.3 使用 SpriteKit 和 SceneKit 处理游戏内容
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 过渡滤镜创建过渡动画。
4.4 处理核心动画层 (macOS)
在 macOS 中,您可以使用该filters
属性将滤镜应用于任何支持滤镜的 CALayer
视图的内容,并添加随时间变化的滤镜参数的动画。
请参阅 Core Animation 编程指南 中的滤镜为 OS X 视图添加视觉效果和高级动画技巧。
5、使用 Core Image Context 构建自己的工作流程
当您使用上一节中列出的技术应用 Core Image 滤镜时,这些框架会自动管理 Core Image 用于处理图像和渲染结果以供显示的基础资源。
这种方法既可以最大限度地提高这些工作流程的性能,又可以更轻松地设置它们。
但是,在某些情况下,使用 CIContext
类自行管理这些资源更为明智。
通过直接管理 Core Image 上下文,您可以精确控制应用的性能特征或将 Core Image 与较低级别的渲染技术集成。
Core Image 上下文表示执行滤镜和生成图像所需的 CPU 或 GPU 计算技术、资源和设置。
有几种上下文可供选择,因此您应该选择最适合您应用的工作流程和您可能使用的其他技术的选项。
以下部分讨论了一些常见场景;有关完整选项集,请参阅*CIContext 类参考*。
重要提示: Core Image 上下文是一个重量级对象,用于管理大量资源和状态。
反复创建和销毁上下文会严重影响性能,因此如果您计划执行多个图像处理操作,请尽早创建上下文并将其存储起来以供将来重复使用。
5.1 使用自动上下文进行渲染
如果您对应用与其他图形技术的互操作方式没有任何限制,那么创建 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 内存之间昂贵的复制操作。
5.2 使用 Metal 进行实时渲染
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
}
}
- 此示例使用适用于 iOS 或 tvOS 的
UIViewController
子类。
若要与 macOS 一起使用,请改为使用NSViewController
子类。 - 该
sourceTexture
属性包含一个 Metal 纹理,其中包含要由滤镜处理的图像。
此示例未显示加载纹理内容的过程,因为填充纹理的方法有很多种 - 例如,您可以使用MTKTextureLoader
类加载图像文件,或将纹理用作您自己的早期渲染过程的输出。 - 创建渲染所需的 Metal 对象 - 一个
MTLDevice
表示要使用的 GPU 的对象,以及一个用于在该 GPU 上执行渲染和计算命令的命令队列。
(此命令队列可以处理由 Core Image 编码的渲染或计算命令以及来自您自己的任何其他渲染过程的命令。) - 配置 MetalKit 视图。
重要提示: 当使用 Metal 视图、层或纹理作为 Core Image 渲染目标时, 始终将framebufferOnly
属性设置为NO
。 - 创建使用相同 Metal 设备的 视图 的Core Image 上下文。
通过共享 Metal 资源,Core Image 可以处理纹理内容并渲染到视图,而无需在单独的 CPU 或 GPU 内存缓冲区之间复制图像数据,从而降低性能成本。
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()
}
}
- 获取要渲染的 Metal 可绘制纹理 和 用于编码渲染命令的命令缓冲区。
- 配置滤镜的输入参数,包括来自 Metal 纹理的输入图像。
此示例使用常量参数,但请记住,此方法每秒最多运行 60 次 - 您可以利用此机会随时间改变滤镜参数以创建流畅的动画。 - 告诉 Core Image 上下文 将滤镜输出 渲染到视图的可绘制纹理中。
bounds 参数告诉 Core Image 要绘制图像的哪一部分 - 此示例使用输入图像的尺寸。 - 告诉 Metal 在命令缓冲区完成执行时显示渲染的图像。
此示例仅显示了使用 Metal 渲染 Core Image 所需的最少代码。
在实际应用中,您可能会在 Core Image 管理的渲染过程之前或之后执行其他渲染过程,或者将 Core Image 输出渲染到辅助纹理中并在另一个渲染过程中使用该纹理。
有关使用 Metal 进行绘图的更多信息,请参阅*Metal 编程指南*。
5.3 使用 OpenGL 或 OpenGL ES 进行实时渲染
Core Image 还可以使用 OpenGL (macOS) 或 OpenGL ES (iOS 和 tvOS) 实现基于 GPU 的高性能渲染。
如果您需要支持无法使用 Metal 的旧硬件,或者想要将 Core Image 集成到现有的 OpenGL 或 OpenGL ES 工作流程中,请使用此选项。
- 如果您使用 OpenGL ES(在 iOS 或 tvOS 中)进行绘制,请使用
contextWithEAGLContext:options:
初始化 来从EAGLContext
创建用于渲染的Core Image 上下文。 - 如果您使用 OpenGL 进行绘制(在 macOS 中),请使用
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:
方法。
5.4 使用 Quartz 2D 进行基于 CPU 的渲染
如果您的应用不需要实时性能并使用 CoreGraphics 绘制视图内容(例如,在drawRect:
UIKit 或 AppKit 视图的方法中),请使用contextWithCGContext:options:
初始化程序创建一个 Core Image 上下文,该上下文可直接与您已用于其他绘制的 Core Graphics 上下文配合使用。
(在 macOS 中,请改用CIContext
当前NSGraphicsContext
对象的属性。)有关 CoreGraphics 上下文的信息,请参阅*Quartz 2D 编程指南*。
三、检测图像中的人脸
Core Image 可以分析并找到图像中的人脸。它执行人脸检测,而不是识别。
人脸检测是 识别包含人脸特征的 矩形,而人脸识别 是识别特定的人脸(约翰、玛丽等)。
Core Image 检测到人脸后,可以提供有关人脸特征的信息,例如眼睛和嘴巴的位置。
它还可以跟踪视频中已识别人脸的位置。
图 2-1 Core Image 识别图像中的脸部边界
了解脸部在图像中的位置后,您可以执行其他操作,例如裁剪或调整脸部的图像质量(色调平衡、红眼校正等)。
您还可以对脸部执行其他有趣的操作;例如:
- Anonymous Faces Filter Recipe 展示了如何将 像素化过滤器 仅应用于图像中的面孔。
- White Vignette for Faces Filter Recipe 展示了如何在脸部周围放置晕影。
注意: iOS v5.0 及更高版本和 OS X v10.7 及更高版本支持人脸检测功能。
1、检测人脸
使用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
) 速度快;本例中显示的高精度更全面但速度较慢。 - 创建面部检测器。
您唯一可以创建的检测器类型是人脸检测器。 - 设置用于查找人脸的选项字典。
让 Core Image 知道图像方向很重要,这样检测器才能知道 在哪里可以找到直立的人脸。
大多数情况下,您会从图像本身读取图像方向,然后将该值提供给选项字典。 - 使用检测器查找图像中的特征。
您提供的图像必须是CIImage
对象。
Core Image 返回一个CIFeature
对象数组,每个对象代表图像中的一张脸。
获得一组人脸后,您可能想找出它们的特征,例如眼睛和嘴巴的位置。下一节将介绍如何操作。
获取面部和面部特征边界
面部特征包括:
- 左眼和右眼位置
- 嘴部位置
- Core Image 用于跟踪视频片段中 人脸的跟踪 ID 和 跟踪帧数(适用于 iOS v6.0 及更高版本以及 OS X v10.8 及更高版本)
从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 及更高版本。
1、自动增强过滤器
表 3-1显示了 Core Image 用于自动增强图像的滤镜。
这些滤镜可以解决照片中最常见的一些问题。
筛选 | 目的 |
---|---|
CIRedEyeCorrection | 修复因相机闪光灯导致的红眼/琥珀色眼/白眼 |
CIFaceBalance | 调整面部颜色,使肤色更加美观 |
CIVibrance | 增加图像的饱和度而不扭曲肤色 |
CIToneCurve | 调整图像对比度 |
CIHighlightShadowAdjust | 调整阴影细节 |
2、使用自动增强过滤器
自动增强 API 只有两种方法:autoAdjustmentFilters
和autoAdjustmentFiltersWithOptions:
。
大多数情况下,您会希望使用 提供选项字典的方法。
您可以设置以下选项:
- 图像方向对于 CIRedEyeCorrection 和 CIFaceBalance 过滤器很重要,以便 Core Image 可以准确地找到人脸。
- 是否仅应用红眼校正。(设置
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 提供的方法可让您向系统 查询可用的内置滤镜,以及每个滤镜的相关信息(显示名称、输入参数、参数类型、默认值等)。
查询系统可为您提供 有关可用滤镜的最新信息。
如果您的应用支持让用户选择和设置滤镜,您可以在为滤镜创建用户界面时使用此信息。
1、获取过滤器和属性的列表
使用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 类参考*以获取可返回的属性的完整列表。
2、构建过滤器词典
如果您的应用提供了用户界面,则可以查阅过滤器字典来创建和更新用户界面。
例如,布尔值的过滤器属性需要复选框或类似的用户界面元素,而范围连续变化的属性可以使用滑块。
您可以使用最大值和最小值作为文本标签的基础。
默认属性设置将决定用户界面中的初始设置。
过滤器名称和属性提供了构建用户界面所需的所有信息,该界面允许用户选择过滤器并控制其输入参数。
过滤器的属性会告诉您过滤器有多少个输入参数、参数名称、数据类型以及最小值、最大值和默认值。
注意: 如果您有兴趣为 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;
}
代码的作用如下:
- 将滤镜名称数组作为输入参数。
回想一下清单 4-1,此数组可以是来自多个 Core Image 滤镜类别的滤镜名称的串联。
在此示例中,数组基于应用程序设置的功能类别(失真或焦点)。 - 迭代过滤器名称数组。
- 检索过滤器名称的过滤器对象。
- 检索过滤器的属性字典并将其添加到例程返回的字典中。
注意: 在 OS X v10.5 及更高版本中 运行的应用程序 可以使用 CIFilter Image Kit 附加功能 来提供过滤器浏览器 和 用于设置过滤器输入参数的视图。
请参阅CIFilter Image Kit 附加功能和*ImageKit 编程指南*。
六、子类化 CIFilter:自定义效果的秘诀
您可以使用一个图像滤镜的输出 作为另一个图像滤镜的输入来创建自定义效果,并将任意数量的滤镜串联在一起。
当您以这种方式创建想要多次使用的效果时,请考虑将其子类化CIFilter
以将效果封装为滤镜。
本章介绍了 Core Image 如何子类化CIFilter
以创建CIColorInvert
滤镜。
然后,它描述了将各种滤镜串联在一起以实现有趣效果的方法。
通过遵循 子类化CIFilter 以创建 CIColorInvert 滤镜 中的子类化过程,您应该能够根据本章中的方法创建滤镜,或者大胆尝试创建您自己的 Core Image 提供的内置滤镜的有趣组合。
1、子类化 CIFilter 来创建 CIColorInvert 过滤器
当您创建子类时,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;
}
2、Chroma Key Filter Recipe
从源图像中去除一种颜色 或一定范围的颜色,然后将源图像与背景图像合成。
图 5-1 色度键滤镜处理链
要创建色度键滤镜:
- 创建一个数据立方体贴图,映射您想要删除的颜色值,使它们透明(alpha 值为 0.0)。
- 使用 CIColorCube 过滤器和立方体贴图从源图像中去除色度键颜色。
- 使用 CISourceOverCompositing 滤镜将处理后的源图像与背景图像混合
以下部分展示了如何执行每个步骤。
2.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"];
2.2 从源图像中去除绿色
现在您有了颜色图数据,请将前景图像(您想要从中去除绿色的图像)提供给 CIColorCube 过滤器并获取输出图像。
[colorCube setValue:myInputImage forKey:kCIInputImageKey];
CIImage *result = [colorCube valueForKey:kCIOutputImageKey];
2.3 将处理后的源图像与背景图像混合
设置CISourceOverCompositing滤镜的输入参数如下:
- 设置
inputImage
为由 CIColorCube 过滤器生成的图像。 - 设置
inputBackgroundImage
为显示新背景的图像。
此示例使用海滩图像。
前景图像现在看起来就像是在海滩上。
3、面部白色晕影滤镜配方
增加图像中检测到的脸部边缘的亮度。
图 5-2 白色晕影滤镜处理链
要创建白色晕影滤镜:
- 在图像中查找人脸。
- 使用以脸部为中心的 CIRadialGradient 创建基础阴影图。
- 将基础阴影图与原始图像混合。
以下部分展示了如何执行每个步骤。
3.1 找到面孔
使用该类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];
3.2 创建阴影贴图
使用 CIRadialGradient 滤镜创建以脸部为中心的阴影图。
阴影图的中心应为透明,这样图像中的脸部就不会受到影响。
阴影图的边缘应为不透明的白色。中间的区域应具有不同程度的透明度。
为了实现此效果,请将输入参数设置为 CIRadialGradient,如下所示:
- 设置
inputRadius0
为大于图像最长尺寸的值。 - 设置
inputRadius1
为大于面的值,例如face.bounds.size.height + 50
。 - 设置
inputColor0
为不透明白色。 - 设置
inputColor1
为透明白色。 - 将设置为使用清单 5-4
inputCenter
计算的脸部边界的中心。
3.3 将渐变与脸部融合
设置CISourceOverCompositing滤镜的输入参数如下:
- 设置
inputImage
为原始图像。 - 设置
inputBackgroundImage
为上一步生成的阴影图。
4、移轴滤镜配方
选择性聚焦图像来模拟微型场景。
图 5-3 移轴滤镜处理链
要创建移轴滤镜:
- 创建图像的模糊版本。
- 创建两个线性渐变。
- 通过合成线性渐变来创建蒙版。
- 将模糊图像、蒙版和原始图像合成。
以下部分展示了如何执行每个步骤。
4.1 创建图像的模糊版本
设置CIGaussianBlur滤镜的输入参数如下:
- 设置
inputImage
为您想要处理的图像。 - 设置
inputRadius
为 10.0(这是默认值)。
4.2 创建两个线性渐变
从单一颜色(例如绿色或灰色)创建一个从上到下变化的线性渐变。
设置 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)
4.3 从线性渐变创建蒙版
要创建蒙版,请按如下方式设置 CIAdditionCompositing 滤镜的输入参数:
- 设置
inputImage
为您创建的第一个线性渐变。 - 设置
inputBackgroundImage
为您创建的第二个线性渐变。
4.4 合并模糊图像、源图像和渐变
最后一步是使用 CIBlendWithMask 滤镜,设置输入参数如下:
- 设置
inputImage
为图像的模糊版本。 - 设置
inputBackgroundImage
为原始的、未处理的图像。 - 设置
inputMaskImage
为蒙版,即组合渐变。
蒙版只会影响图像的外部。
蒙版的透明部分将透过原始的、未处理的图像显示出来。
蒙版的不透明部分允许模糊的图像显示出来。
5、匿名面孔过滤配方
在图像中查找面部并将其像素化,以使之无法被识别。
图 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
为合成的绿色圆圈。
6、像素化过渡滤镜配方
通过对每幅图像进行像素化,实现从一幅图像到另一幅图像的过渡。
图 5-5 Pixellate Transition 滤镜处理链
要创建像素化转换滤镜:
- 使用 CIDissolveTransition 在源图像和目标图像之间转换。
- 将过渡滤镜的结果像素化。
以下部分展示了如何执行每个步骤。
创建溶解过渡
设置CIDissolveTransition滤镜的输入参数如下:
- 设置
inputImage
为您想要过渡的图像。 - 设置
inputTagetImage
为您想要过渡到的图像。 - 设置
inputTime
为类似于的值min(max(2*(time - 0.25), 0), 1)
,这是一个介于两个值之间的斜坡函数。
将过渡结果像素化
通过设置 CIPixellate 滤镜的输入参数来改变像素的比例,如下所示:
- 设置
inputImage
为 CIDissolveTransition 过滤器的输出图像。 inputScale
通过提供三角函数的值来设置为随时间变化:90*(1 - 2*abs(time - 0.5))
- 使用 的默认值
inputCenter
。
7、旧胶片滤镜配方
降低视频图像的质量,使其看起来像旧的、粗糙的模拟电影。
图 5-6 老电影滤镜处理链
要创建旧电影滤镜:
- 将 CISepiaTone 滤镜应用于原始视频图像。
- 创建随机变化的白色斑点。
- 创建随机变化的深色划痕。
- 将斑点和划痕合成到棕褐色调的图像上。
以下部分展示了如何执行每个步骤。
将棕褐色应用于视频图像
设置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 提供了许多用于创建图像、上下文和渲染内容的选项。
您选择如何完成任务取决于:
- 您的应用需要多久执行一次任务
- 您的应用是否适用于静态图像或视频图像
- 无论您需要支持实时处理还是分析
- 色彩保真度对用户有多重要
您应该阅读性能最佳实践,以确保您的应用尽可能高效运行。
1、性能最佳实践
遵循以下做法可获得最佳性能:
-
不要
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) };
2、您的应用需要色彩管理吗?
默认情况下,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 的向后兼容性。
1、设置 MicroPaint 应用程序的界面
MicroPaint 的接口需要以下内容:
- 图像累积器
- 为用户提供“画笔”。
该画笔是一个 Core Image 滤镜 (CIRadialGradient),它以模拟喷枪的方式应用颜色。 - 复合过滤器(CISourceOverCompositing)允许将新的油漆与先前应用的油漆合成。
- 用于跟踪当前油漆颜色和画笔大小的变量。
构建过滤器词典声明MircoPaintView
为 SampleCIView
的子类。
此处不讨论 SampleCIView
类;它是 NSOpenGLView
的子类。
有关详细信息,请参阅*CIMicroPaint*示例应用程序。
清单 7-1 MicroPaint 应用程序的界面
@interface MicroPaintView : SampleCIView {
CIImageAccumulator *imageAccumulator;
CIFilter *brushFilter;
CIFilter *compositeFilter;
NSColor *color;
CGFloat brushSize;
}
@end
2、初始化绘画的过滤器和默认值
初始化 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];
3、跟踪并累积喷漆操作
每当用户单击或拖动光标到画布上时,都会调用 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 过滤器(无论是按原样使用还是通过子类化它们),则无需阅读本章。
如果您打算编写自定义过滤器,则应阅读本章,以便了解自定义过滤器中的处理路径和组件。
阅读完本章后,您可以在创建自定义过滤器中了解如何编写过滤器。
如果您对打包自定义过滤器以进行分发感兴趣,您还应该阅读打包和加载图像单元。
1、过滤器客户端和过滤器创建者
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 图像单元包含包装信息以及一个或多个过滤器定义
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 的功能,并且以智能的方式做到这一点。
3、坐标空间
Core Image 在与设备无关的工作空间中执行操作。
理论上,Core Image 工作空间是无限的。
工作空间中的点由坐标对 ( x , y )
表示,其中x表示沿水平轴的位置,y表示沿垂直轴的位置。
坐标是浮点值。默认情况下,原点是点 (0,0)。
当 Core Image 读取图像时,它会将像素位置转换为与设备无关的工作空间坐标。
当需要显示处理后的图像时,Core Image 会将工作空间坐标转换为适合目标(例如显示器)的坐标。
编写自己的过滤器时,您需要熟悉两个坐标空间:目标坐标空间和采样器空间。
目标坐标空间表示您要渲染的图像。
采样器空间表示您要从中进行纹理处理的内容(另一个图像、查找表等)。
您可以使用 destCoord
函数获取目标空间中的当前位置,而samplerCoord
函数提供采样空间中的当前位置。
(请参阅*核心图像内核语言参考*。)
请记住,如果您的源数据是平铺的,则采样器坐标有一个偏移量(dx/dy)。
如果您的样本坐标有偏移量,则可能需要使用 samplerTransform
函数将目标位置转换为采样器位置。
4、感兴趣的区域
虽然图 8-4中没有明确标注,但每个源图像中的阴影区域 都是图中所示采样器的关注区域。
关注区域 (ROI) 定义了源中的区域,采样器从该区域获取像素信息以提供给内核进行处理。
如果您是过滤器客户,则无需关心 ROI。
但如果您是过滤器创建者,您将需要了解关注区域和定义域之间的关系。
回想一下,定义域描述了过滤器的边界形状。
理论上,这个形状可以没有边界。
例如,考虑一个过滤器,它创建一个可以无限延伸的重复图案。
ROI 和定义域可以通过以下方式相互关联:
- 它们完全重合——源和目标之间存在 1:1 映射。
例如,色相过滤器处理ROI 中工作空间坐标 ( r , s ) 的像素,以在定义域中工作空间坐标 ( r , s ) 处生成一个像素。 - 它们相互依赖,但以某种方式进行调节。
一些最有趣的滤镜(例如模糊和失真)在计算一个目标像素时使用许多源像素。
例如,失真滤镜可能会使用 ROI 中工作坐标空间中的像素 ( r , s ) 及其相邻像素在定义域中生成单个像素( r, s )
。 - 定义域是根据采样器提供的查找表中的值计算得出的。
映射或表中值的位置与源图像和目标中的工作空间坐标无关。
阴影图像中 ( r , s ) 处的值不必是产生定义域中工作空间坐标 ( r , s ) 处像素的值。
许多过滤器将阴影图像或查找表中提供的值与图像源结合使用。
例如,颜色渐变或近似函数(如函数arcsin
)的表提供的值与工作坐标概念无关。
除非另有说明,Core Image 会假设 ROI 和定义域一致。
如果您编写的过滤器不符合此假设,则需要为 Core Image 提供一个例程来计算特定采样器的 ROI。
请参阅提供 ROI 函数以了解更多信息。
5、可执行和不可执行过滤器
您可以根据自定义 Core Image 滤镜 是否需要将辅助二进制可执行文件 加载到客户端应用程序的地址空间 来对其进行分类。
使用 Core Image API 时,您会注意到这些滤镜被简称为可执行和不可执行。
滤镜创建者可以选择编写任一类型的滤镜。
滤镜客户端可以选择仅使用不可执行或同时使用两种类型的滤镜。
安全性是区分 CPU 可执行和 CPU 不可执行过滤器的主要动机。
不可执行过滤器仅包含一个 Core Image 内核程序来描述过滤器操作。
相比之下,可执行过滤器还包含在 CPU 上运行的机器代码。
Core Image 内核程序在受限环境中运行,不会构成病毒、特洛伊木马或其他安全威胁,而在 CPU 上运行的任意代码则可以。
不可执行过滤器有特殊要求,其中之一是不可执行过滤器必须作为图像单元的一部分进行打包。
过滤器创建者可以阅读编写不可执行过滤器以了解更多信息。
过滤器客户端可以在加载图像单元中找到有关加载每种过滤器的信息。
6、颜色分量和预乘 Alpha
预乘 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 颜色空间。
7、也可以看看
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 中表达图像处理操作
- 创建自定义过滤器描述了您需要实现的方法和其他过滤器要求。
- 使用您自己的自定义过滤器告诉您在自己的应用中使用过滤器需要什么。
(如果您想将其打包为独立的图像单元,请参阅打包和加载图像单元。) - 提供 ROI 函数可提供有关感兴趣区域的信息,以及何时必须提供计算该区域的方法。
(并非总是需要。) - 对于任何计划编写 CPU 不可执行过滤器的人来说,编写不可执行过滤器都是必读章节,因为它列出了此类过滤器的要求。
图像单元可以包含两种过滤器。
CPU 不可执行过滤器是安全的,因为它们不能藏匿病毒和特洛伊木马。
有安全意识的过滤器客户端可能只想使用那些 CPU 不可执行的过滤器。 - 内核例程示例为三个示例过滤器提供了内核例程:增亮、乘法和孔洞扭曲。
1、在 Core Image 中表达图像处理操作
Core Image 的工作原理是,内核(即逐像素处理例程)被编写为一种计算,其中输出像素使用反向映射回内核输入图像的相应像素来表示。
虽然您可以用这种方式表达大多数像素计算(有些比其他更自然),但有些图像处理操作很难,甚至不可能。
在编写过滤器之前,您可能需要考虑图像处理操作是否可以在 Core Image 中表达。
例如,计算直方图很难描述为对源图像的反向映射。
2、创建自定义过滤器
本节介绍如何创建具有 Objective-C 部分和内核部分的核心图像滤镜。
按照本节中的步骤,您将创建一个可由 CPU 执行的滤镜。
您可以按照打包和加载图像单元中的说明,将此滤镜(如果您愿意)与其他滤镜一起打包为图像单元。
或者,您也可以直接在自己的应用中使用该滤镜。
有关详细信息,请参阅使用您自己的自定义滤镜。
Core Image 提供三种基于内核的滤镜:颜色滤镜、变形滤镜和通用滤镜。
通用滤镜包含一个 GPU 内核例程,可以修改像素颜色和像素位置。
但是,如果您设计的滤镜仅修改像素颜色,或者更改图像几何形状而不修改像素,则创建颜色或变形滤镜可让 Core Image 在各种 iOS 和 Mac 硬件上提供更好的滤镜性能。
有关详细信息,请参阅CIColorKernel
和CIWarpKernel
类的参考文档。
本节中的通用过滤器假设感兴趣区域 (ROI) 和定义域重合。
如果您想编写一个不满足此假设的过滤器,请确保您还阅读了提供 ROI 函数。
在创建自己的自定义过滤器之前,请确保您了解 Core Image 坐标空间。
请参阅构建过滤器词典。
要创建自定义 CPU 可执行过滤器,请执行以下步骤:
- 编写内核代码
- 使用 Quartz Composer 测试内核例程
- 声明过滤器的接口
- 为 CIKernel 对象编写 Init 方法
- 编写自定义属性方法
- 编写输出图像方法
- 注册过滤器
- 编写方法来创建过滤器的实例
后面几节将以除雾滤镜为例,详细描述每个步骤。
除雾滤镜的作用是调整图像的亮度和对比度,并对其进行锐化。
此滤镜可用于校正在轻雾或薄雾中拍摄的图像,这通常是从飞机上拍摄图像的情况。
图 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
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 对象编写 Init 方法
过滤器的实现文件包含一个方法,该方法使用 .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显示了customAttributes
Haze 滤镜的方法。
输入参数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;
}
按照以下步骤 创建滤镜后,您可以在自己的应用中使用该滤镜。
有关详细信息,请参阅使用您自己的自定义滤镜。
如果您想将一个或一组滤镜 作为插件提供给其他应用,请参阅打包和加载图像单元。
3、使用您自己的自定义过滤器
使用自定义滤镜的步骤与使用 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];
}
4、提供 ROI 函数
感兴趣区域 (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 函数
如果你的 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。
您将在后面的部分中看到如何做到这一点。
玻璃失真滤波器的 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;
};
环境地图的 ROI 函数
清单 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,
]];
}
代码的作用如下:
- 为内核所需的三个采样器中的每一个声明变量。
- 为输入图像设置采样器。
此采样器的 ROI 与 DOD 重合。 - 为用于输入高度的图像设置采样器。
此采样器的 ROI 与 DOD 一致。 - 为环境贴图设置采样器。
此采样器的 ROI 与 DOD 不一致,这意味着您必须提供 ROI 函数。 - 使用以下选项应用内核生成核心图像 (
CIImage
对象 )。 - ROI函数 及其需要使用的内核。
- 内核函数的参数,其类型必须与内核函数的函数签名兼容。
(此处未显示内核函数源;在此示例中假设它们是类型兼容的)。
采样器参数的顺序决定了它的索引。
提供给内核的第一个采样器是 index 0
。在本例中,这就是src
采样器。
提供给内核的第二个采样器blur
——被分配了 index 1
。
第三个采样器env
——被分配了 index 2
。
检查你的 ROI 函数以确保为每个采样器提供适当的 ROI 非常重要。
5、编写不可执行的过滤器
CPU 不可执行的过滤器保证是安全的。
由于这种类型的过滤器仅在 GPU 上运行,因此它无法参与病毒或特洛伊木马活动或其他恶意行为。
为了保证安全性,CPU 不可执行的过滤器具有以下限制:
- 这种类型的过滤器是纯内核,这意味着它完全包含在
.cikernel
文件中。
因此,它没有过滤器类,并且其所能提供的处理类型受到限制。
以下形式的采样指令是唯一对不可执行过滤器有效的采样指令类型:
color = sample (someSrc, samplerCoord(someSrc));
- CPU 不可执行过滤器 必须作为 图像单元的一部分 进行打包。
- Core Image 假设 ROI 与 DOD 重合。
这意味着不可执行的过滤器不适用于模糊或失真等效果。
CIDemoImageUnit 示例在 MyKernelFilter.cikernel
文件中 包含一个不可执行的过滤器。
加载图像单元时,MyKernelFilter 过滤器会与图像单元中的 FunHouseMirror 过滤器 一起加载。
但是,FunHouseMirror 是一个可执行过滤器。
它有一个 Objective-C 部分和一个内核部分。
编写不可执行的过滤器时,需要在 Descriptions.plist
文件中,为图像单元包提供 所有过滤器属性。
清单 9-13显示了 CIDemoImageUnit 示例中 MyKernelFilter 的属性。
清单 9-13 MyKernelFilter 不可执行过滤器的属性列表
<key>MyKernelFilter</key>
<dict>
<key>CIFilterAttributes</key>
<dict>
<key>CIAttributeFilterCategories</key>
<array>
<string>CICategoryStylize</string>
<string>CICategoryVideo</string>
<string>CICategoryStillImage</string>
</array>
<key>CIAttributeFilterDisplayName</key>
<string>MyKernelFilter</string>
<key>CIInputs</key>
<array>
<dict>
<key>CIAttributeClass</key>
<string>CIImage</string>
<key>CIAttributeDisplayName</key>
<string>inputImage</string>
<key>CIAttributeName</key>
<string>inputImage</string>
</dict>
<dict>
<key>CIAttributeClass</key>
<string>NSNumber</string>
<key>CIAttributeDefault</key>
<real>8</real>
<key>CIAttributeDisplayName</key>
<string>inputScale</string>
<key>CIAttributeIdentity</key>
<real>8</real>
<key>CIAttributeMin</key>
<real>1</real>
<key>CIAttributeName</key>
<string>inputScale</string>
<key>CIAttributeSliderMax</key>
<real>16</real>
<key>CIAttributeSliderMin</key>
<real>1</real>
</dict>
<dict>
<key>CIAttributeClass</key>
<string>NSNumber</string>
<key>CIAttributeDefault</key>
<real>1.2</real>
<key>CIAttributeDisplayName</key>
<string>inputGreenWeight</string>
<key>CIAttributeIdentity</key>
<real>1.2</real>
<key>CIAttributeMin</key>
<real>1</real>
<key>CIAttributeName</key>
<string>inputGreenWeight</string>
<key>CIAttributeSliderMax</key>
<real>3.0</real>
<key>CIAttributeSliderMin</key>
<real>1</real>
</dict>
</array>
</dict>
<key>CIFilterClass</key>
<string>MyKernelFilter</string>
<key>CIHasCustomInterface</key>
<false/>
<key>CIKernelFile</key>
<string>MyKernelFilter</string>
6、内核例程示例
任何图像处理过滤器 的本质都是 执行像素计算的内核。
本节中的代码清单显示了 这些过滤器 的一些典型内核例程:增亮、乘法和孔失真。
通过查看这些代码,您可以了解 如何编写自己的内核例程。
但请注意,这些例程只是示例。
不要假设此处显示的代码就是 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
。
该值在中心处为 0,在距离等于半径处为 1。 - 创建一个洞,其周围有适当的扭曲。(
x – 1/sqrt (x)
) - 确保洞内的所有像素都从中心像素映射,然后按半径扩大扭曲的距离函数。
- 缩放矢量以产生扭曲,然后将中心添加回来。
- 从源纹理返回扭曲的样本。
十一、打包和加载图像单元
图像单元代表核心图像滤镜的插件架构。
图像单元使用类NSBundle
作为打包机制,以便您能够将自己创建的滤镜提供给其他应用。
图像单元可以包含可执行或不可执行的滤镜。
(有关详细信息,请参阅可执行和不可执行的滤镜。)
要从自定义过滤器创建图像单元,您必须执行以下任务:
阅读本章后,您可能还想
- 阅读*图像单元教程*,了解有关编写内核和创建图像单元的深入信息。
- 访问 Apple 的图像单元许可和商标网页,了解如何验证图像单元并获取图像单元徽标。
1、开始之前
下载*CIDemoImageUnit*示例。
创建图像单元时,应该有类似的文件。
此图像单元包含一个过滤器 FunHouseMirror。
图像单元中的每个过滤器通常有三个文件:过滤器类的接口文件、相关的实现文件和内核文件。
正如您在示例代码项目中看到的,对于 FunHouseMirror 过滤器来说,情况确实如此:FunHouseMirrorFilter.h
、FunHouseMirrorFilter.m
和funHouseMirror.cikernel
。
每个图像单元还应具有CIPlugInRegistration
协议的接口和实现文件。
在图中,请参见MyPlugInLoader.h
和MyPlugInLoader.m
。
您需要修改的另一个重要文件是文件Description.plist
。
现在您已经对图像单元项目中的文件有了一些了解,您可以开始创建一个图像单元项目了。
2、在 Xcode 中创建图像单元项目
Xcode 提供了创建图像单元的模板。
创建图像单元项目后,您将拥有开始所需的大部分文件,并且该项目将链接到相应的框架。
在 Xcode 中创建图像单元项目
- 启动Xcode,然后选择 File > New Project。
- 在模板窗口中,选择 System Plug-in > Image Unit Plug-in 。然后单击“下一步”。
- 命名图像单元项目,然后单击“完成”。
项目窗口打开,其中创建了以下文件:
MyImageUnitPlugInLoader.h
和MyImageUnitPlugInLoader.m
,CIPlugInRegistration
协议的接口和实现文件MyImageUnitFilter.h
和MyImageUnit Filter.m
MyImageUnitFilterKernel.cikernel
图像单元项目中提供的 MyImageUnitKernelFilter.cikernel
文件是示例内核文件。
如果您已经创建了过滤器,则不需要此文件,因此您可以将其删除。
您稍后会将自己的文件添加到项目中。
3、自定义加载方法
打开实现 CIPlugInRegistration
协议的文件。
在其中你会发现一个 load
方法,如清单 10-1所示。
你可以选择 向此方法 添加代码,来执行所需的任何初始化,例如注册检查。
如果过滤器加载成功,则该方法将返回 true
。
如果你不需要任何自定义初始化,则可以保留加载方法。
清单 10-1 图像单元模板提供的 load 方法
-(BOOL)load:(void*)host
{
// Custom image unit initialization code goes here
return YES;
}
如果需要,您可以编写一种unload
方法来执行过滤器可能需要的任何清理任务。
4、将过滤文件添加到项目中
将之前创建的过滤器文件添加到图像单元项目中。
回想一下,您需要每个过滤器的接口和实现文件以及相关的内核文件。
如果您尚未编写过滤器,请参阅创建自定义过滤器。
请记住,您可以在一个图像单元中打包多个过滤器,并且可以根据需要为过滤器添加任意数量的内核文件。
只需确保包含要打包的所有过滤器和内核文件即可。
5、修改描述属性列表
对于可执行过滤器,仅从 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 值。 |
6、构建并测试图像单元
在开始创建图像单元之前,您应该测试内核代码以确保其正常工作。(请参阅使用 Quartz Composer 测试内核例程。)
成功构建图像单元后,您需要将其复制到以下目录:
/Library/Graphics/Image Units
~/Library/Graphics/Image Units
然后,您应该尝试从应用程序加载图像单元并使用单元中打包的滤镜(或多个滤镜)。
请参阅加载图像单元、查询系统滤镜和处理图像。
7、加载图像单元
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 参数。 |
8、也可以看看
- 图像单元教程 提供了编写各种内核并将其打包为图像单元的分步说明。
- CIDemoImageUnit 是 Xcode 项目的一个示例图像单元。
2024-06-02(日)