如何自定义Core Image Filter——Core Image 编程指南(下) Core Image Programming Guide
Core Image提供了编写自定义filter的支持。 自定义filter是你为其编写方法(称为内核)的filter,该过程指定要对每个源图像像素执行的计算。 如果你打算使用内置的Core Image filter,无论是本体还是继承它们,你都不需要阅读本文。 如果你打算编写自定义filter,则应阅读本章,以便了解自定义filter中的处理路径和组件。 如果你有意打包自定义filter进行分发,你还应该阅读Packaging and Loading Image Units。
Filter Clients and Filter Creators
Core Image 为两种开发者设计——filter clients和filter creators。如果你只使用filter,那么你是个client;如果你写自己的filter,那就是 creator。
下图显示一个典型的filter的结构组成。阴影区域标出部分是“under the hood”——filter clients不需要知道的,但是filter creator 必须理解的。没有阴影的部分显示了两个方法——attributes and outputImage——给filter client 提供数据。filter的 attributes方法返回一个描述filter的建值对列表。outputImage 方法产生一个图片,图片来自:
- 一个从源抓去像素的采样器
- 一个处理像素的核心
典型filter的结构
每个自定义filter的核心就是 kernel。kernel 在每个 源图像素上的计算。kernel的计算可以是非常简单和复杂的。一个非常简单的kernel可能仅仅把源图像素返回(do nothing):
destination pixel = source pixel
Filter creators 使用不同的 OpenGL Shading Language (glslang) 来指定每个像素计算。(See Core Image Kernel Language Reference.) Kernel 对于filter client是不透明的。一个filter可能实际上使用多个 kernel 方式,传递一个的输出给另一个作为输入。
注意: Kernel 是实际的例程,使用 Core Image glslang 编写,用于filter处理像素。一个CIKernel 对象是一个包含kernel 例程的Core Image 对象。当你创建一个filter,你会看到,kernel例程在 .cikernel 扩展名的文件中存在。你通过代码传递一个包含kernel 例程的字符串来创建一个CIKernel对象。
作为Filter Creator可以让他们的自定义filter对任何app都起作用,使用 NSBundle 类的指定架构,通过把他们打包成一个 plug-in,或者image unit。一个image unit 不止一个filter,如下图所示。你可以编写一组filters,执行不同的 edge detection、打包他们作为独立的image unit。Filter clients可以使用Core Image API记载image unit、得到一个包含在image unit中的filter列表。
一个包含一个或者多个filter定义的 image unit:
The Processing Path
下图显示了一个一个处理两个源图像素的处理路径。源图总是CIImage 对象。Core Image提供不同的方式得到图片数据。你可以给image 提供一个URL,读取 raw 数据(使用 NSData类);或者通过转换Quartz 2D image(CGContextRef)、一个 openGL 纹理、或者一个Core Image Video iage buffer(CVImageBufferRef)为CIimage对象。
注意,输出图片的实际数量、还有filter是否需要一个输入图片,依赖于filter本身。filter是非常灵活的,可以:
- 不需要input image。某些filter基于不是image的输入参数(比如, CICheckerboardGenerator and CIConstantColorGenerator filters in Core Image Filter Reference)
- 需要一个image(比如see the CIColorPosterize and CICMYKHalftone filters in Core Image Filter Reference.)
- 需要两个或者更多的images。组合图片的filter或使用一个image的值控制另一个图片像素处理的情况通常需要两个或者更多的images。一个input image可以作为一个shading image,一个可以是蒙板、背景图或者提供一个控制某些其他图处理的查找值的源(比如,see the CIShadedMaterial filter in Core Image Filter Reference.)。
当你处理一个image时,创建包含合适输入数据的CIImage对象是你的职责。
注意:尽管 CIImage对象有关联的image data,但是它不是一个image。你可以把CIImage看作一个image的“菜谱”。一个CIImage对象有所有创建image的信息,但是Core Image直到你通知它绘制时才会绘制image。
像素处理路径:
每个源图片的像素通过CISimpler对象取出,简称sampler采样者。按字面意思,一个sampler处理一个image的采样和把她们提供给kernel。filter creator为每个源图提供sampler。filter clients不需要知道sampler的信息。
sampler定义内容如下:
- a coordinate transform,如果不需要变换,它可以是恒等变换。
- an interpolation mode,能被nearest neighbor sampling or bilinear interpolation(默认的)
- a wrapping mode,指定当采样区域在源图外时如何生产像素(使用透明block或者缩到某个程度)
filter creator 在kernel中定义每像素图片计算,但是Core Image 处理实际计算的实现。Core Image决定计算在GPU还是CUP上执行。Core Image 在不同设备兼容性基础上,用Metal, OpenGL, or OpenGL ES 实现硬件栅格化rasterization。It implements software rasterization through an emulation environment specifically tuned for evaluating fragment programs with nonprojective texture lookups over large quadrilaterals (quads).它通过仿真环境实现软件光栅化,该环境专门用于在大四边形(四边形)上使用非投射纹理查找来评估片段程序(PS:完全不知道怎说什么……)。
尽管像素处理时从源图到目标图,但是Core Image 使用的计算路径从目标开始,然后返回到源像素。如下图。这个向后的计算可能看起来很笨拙,但是它实际上最小化了任何计算中使用的像素的数量。Core Image不使用的另一种方法是:强制处理所有源像素、然后再决定目标需要什么。
Core Image计算路径图:
假定上图的filter执行某种组合操作,和 soure-over compositing差不多。filter client想要堆叠两个图,这样每个图的一小部分会组合形成一个图中的结果。通过展望目标应该是什么样的,然后Core Image可以确定来自源图像的哪些数据影响最终输出图像,然后将计算仅限于那些需要的源像素。作为一个结果,samplers只从源图的阴影区域fetch 采样像素。
注意上图中的 Domain of definition。Domain of definition 是进一步约束计算的简单方式。它是一个外部的所有像素都是透明的(即 alpha 部分为0.0)的区域。这个例子中,Domain of definition 正好与目标图像一致。Core Image 让你提供一个 CIFilterShape 对象来定义这个区域。这个 CIFilterShape类提供了许多方法,能定义矩形、转换形状、执行 inset, union和intersection等操作。比如:如果你用一个矩形定义了一个filter shape,这个形状比阴影区域小(如图),这时Core Image 使用这个信息来进一步约束计算中的源像素。
Core Image 以其他方式促进高效处理。它执行智能缓存和编译器优化,使其非常适合实时视频处理和图像分析等任务。它为任何估计会repeat的数据集合缓存中间结果。无论何时添加新image,Core Image都会以最近最少使用的顺序挑出那些会导致缓存变得过大数据。频繁重用的数据仍然在缓存中。你的app从Core Image缓存中受益,而不需要知道缓存如何实现的。只要你可以,你可以通过重用对象(图像、上下文等等)来获得最佳性能。
Core Image 在内核和传递级别上使用传统的编译技术也获得了很大的性能。Core Image 用于分配寄存器的方法使临时寄存器(每个内核)和临时像素缓冲区(每个filter图)的数量最小化。 编译器执行几次优化,并自动区分基于以前的计算的与数据相关的纹理和不依赖于数据的纹理。 再次,你不需要关心编译技术的细节。 重要的一点是,Core Image 是硬件知识; 它可以随时使用GPU和多核CPU的功能,并且它以智能的方式实现
坐标空间 Coordinate Spaces
Core Image在独立于设备的工作空间中执行操作。理论上, Core Image的工作空间是无限扩展的。工作空间中的点由坐标(x,y)表示,其中x表示沿着水平轴的位置,并且y表示沿着垂直轴的位置。 坐标是浮点值。 默认情况下,原点是point(0,0)。
当Core Image读取一个image,他会把像素位置翻译成独立于设备的工作空间坐标。当显示一个处理图片的时机到来时,Core Image 翻译 工作空间坐标到目标实际坐标,比如一个显示器。
当你写自己的filter时,你需要对两个坐标系不陌生:目标坐标空间和 采样 空间。目标坐标空间代表你正绘制的image。采样坐标,sampler space表示你正在处理纹理的内容(另一个image,lookup table等)。通过使用destCoord函数得到当前在目标空间中的位置,同理samplerCoord函数提供采样空间里的位置。(See Core Image Kernel Language Reference.)
请记住,如果源数据平铺(tiled),则采样器坐标具有偏移量(dx / dy)。 如果你的采样坐标有偏移量,则可能需要使用函数samplerTransform将目标位置转换为采样器位置。
关注区域 The Region of Interest
尽管没有显示的在上面的图中注明,每个源图像的阴影区域是图中描述的采样器的关注区域。关注区域,或者说ROI,定义source 中的区域,从这个区域中,采样器获取像素信息以提供给内核进行处理。对于filter client,你也不需要关注这个ROI。但是如果时filter creator,你需要理解ROI和domain of definition的关系。
回忆一下 上文中的 domain of definition,它面熟了filter的范围阴影区域。理论上这片阴影可以没有范围。比如,一个filter可以创建重复的图案,可以无限扩展。
ROI和domain of definition 在以下几种方式中彼此关联:
- 他们完全一致——在source和destination间1:1映射。比如,一个 hue filter 从ROI中的工作空间坐标(r,s)处理一个像素,来产生在domain of definition 中的一个工作空间坐标(r,s)的像素。
- 他们互相依赖,但是有的方式不同。某些有趣的filter,比如blur和destortion,使用在一个目标像素计算中的许多源像素。例如,destortion filter可能会使用ROI中工作坐标空间中的像素(r,s)及其相邻的像素,在domain of definition中生成单个像素(r,s)。
- domain of definition由采样器提供的查找表中的值计算。 映射或表格中值的位置与源图像和目标中的工作空间坐标无关。 位于(r,s)中的阴影图像中的值不需要是在domain of definition中的工作空间坐标(r,s)处产生像素的值。 许多滤镜使用阴影图像或查找表中提供的值与图像源结合使用。 例如,color ramp或近似函数的table(例如arcsin函数)会提供与工作坐标概念无关的值。
除非另有说明,Core Image假定ROI和domain of definition重合。 如果编写的filter不适用此假定,则需要为 Core Image 提供计算特定采样器ROI的例程。
请参阅Supplying an ROI Function 获取更多信息。
可执行和不可执行的filters
你可以根据自定义 Core Image filter是否需要将辅助二进制可执行filter加载到客户端应用程序的地址空间中来对自定义filter进行分类。在你使用Core Image API时,你会注意到这些API简称为可执行的filter和不可执行的filter。filter creator可以选择编写任何一种filter。filter clients可以选择仅使用不可执行filter的或使用两种filter 都可以。
安全性是区分CPU可执行filter和不可执行filter的主要动机。不可执行的filter仅包含一个Core Image内核程序来描述filter操作。相比之下,可执行filter还包含在CPU上运行的机器代码。 Core Image内核程序在受限制的环境中运行,不能构成病毒、木马或其他安全威胁,而运行在CPU上的任意代码都可以。
不可执行的filter有特殊要求,其中之一是不可执行的filter必须作为图像单元的一部分进行打包。filter创建者可以读取写入不可执行filter以获取更多信息。filter客户端可以在加载图像单元中查找有关加载每种filter的信息。
色彩组件和Premultiplied alpha / Color Components and Premultiplied Alpha
Premultiplied alpha 是用于描述source color的术语,其组成部分已经与α值相乘。通过消除对每个颜色分量执行乘法操作的需要,预乘可以加快图像的渲染速度。例如,在RGB色彩空间中,使用预乘alpha生成图像会消除图像中每个像素的三次乘法运算 (red times alpha, green times alpha, and blue times alpha)。
filter creators必须提供包含由alpha值预乘的颜色分量给Core Image。否则,filter的行为就好像颜色分量的alpha值为1.0一样。确保颜色组件预乘对filter来说是非常重要的操作。
默认情况下,Core Image假定处理节点是128 bits-per-pixel,linear light,预先乘以使用GenericRGB色彩空间的RGBA浮点值。您可以通过提供Quartz 2D CGColorSpace对象来指定不同的工作色彩空间。请注意,工作色彩空间必须是基于RGB的。如果您有YUV数据作为输入(或其他不是基于RGB的数据),则可以使用ColorSync功能转换为工作色彩空间。 See Quartz 2D Programming Guide for information on creating and using CGColorspace objects.)
对于8-bit YUV 4:2:2的来源source,Core Image可以处理240 HD layers per gigabyte。8-bit YUV是视频源(如DV,MPEG,未压缩的D1和JPEG)的原生色彩格式。您需要将YUV色彩空间转换为Core Image的RGB色彩空间。
更多信息见两个paper:
Shantzis, Michael A., “A Model for Efficient and Flexible Image Computing,” (1994), Proceedings of the 21st Annual Conference on Computer Graphics and Interactive Techniques.
Smith, Alvy Ray, “Image Compositing Fundamentals,” Memo 4, Microsoft, July 1995. Available from http://alvyray.com/Memos/MemosCG.htm#ImageCompositing
打包和加载 Image Units
一个image unit 代表Core Image filter的一种插件技术。Image units使用NSBundle 类作为打包机制,允许你制作在其他app上可用的filter。image unit可以包含可执行和不可知性的filter。
从自定义filter创建image unit,有以下工作:
- 编写自定义filter
- Create an Image Unit Project in Xcode.
- Add Your Filter Files to the Project.
- Customize the Load Method.
- Modify the Description Property List.
- Build and Test the Image Unit
关于打包和加载方法,不翻译了,详情查看 https://developer.apple.com/library/archive/documentation/GraphicsImaging/Conceptual/CoreImaging/ci_image_units/ci_image_units.html#//apple_ref/doc/uid/TP30001185-CH7-SW12
另,两个 demo:
Image Unit Tutorial which provides step-by-step instructions for writing a variety of kernels and packaging them as image units.
CIDemoImageUnit is a sample image unit Xcode project.