Core Image之自定 Filter

文章摘自 https://colin1994.github.io/2016/10/21/Core-Image-Custom-Filter/

自定义 Filter 流程

自定义的 Filter 和系统内置的各种 CIFilter,使用起来方式是一样的。我们唯一要做的,就是实现一个符合规范的 CIFilter 的子类,然后该怎么用怎么用。

这里总结起来就3步:

编写 CIKernel:使用 CIKL,自定义滤镜效果。 加载 CIKernel:CIFilter 读取编写好的 CIKernel。 设置参数:设置 CIKernel 需要的输入参数以及 DOD 和 ROI。 不难看出,这些操作都是围绕 CIKernel 展开的,那么,它是什么? CIKL,DOD,ROI 又是什么鬼? 先撇开这些麻烦的东西,我们先这样简单的认为:

CIKernel 是我们 Filter 对应的脚本,它描述 Filter 的具体工作原理。 CIKL (Core Image Kernel Language)是编写 CIKernel 的语言。 DOD,ROI 当做普通的参数处理。 弄清了这些,我们再来看具体操作过程。 拿一个图片翻转效果举例,效果如下:

  1. 编写 CIKernel File —> New —> File —> Empty, 创建一个名为 MirrorX.cikernel 的文件。 编辑 .cikernel 文件,比如:

    kernel vec2 mirrorX ( float imageWidth ) 
    {
       // 获取待处理点的位置
       vec2 currentVec = destCoord();
     // 返回最终显示位置
       return vec2 ( imageWidth - currentVec.x , currentVec.y ); 
    }

    PS:这个 kernel 如果有不懂的,可以先跳过。下文会重点说明。

  2. 加载 CIKernel

File —> New —> File —> Cocoa Touch Clas,新建一个继承自 CIFilter 的类,比如 MirrorXFilter。

在 MirrorXFilter.m 中,添加如下代码:

这段代码很简单,重写 init 方法,主要就是读取 .cikernel 文件中代表 CIKernel 的字符串(当然, CIKernel 也可以直接写在 NSString 里头,免去文件读取这步),然后使用 kernelsWithString

方法获取到真正的 CIKernel 对象。

至此,CIKernel 加载完毕。

  1. 设置参数

    在 MirrorXFilter.m 中,添加需要的成员变量。

    之后,就是设置参数,传入 kernel 中。

    ```objective-c

    // 使用

  2. (CIImage *)outputImage

    {

    CGFloat inputWidth = inputImage.extent.size.width;

    CIImage *result = [customKernel applyWithExtent: inputImage.extent roiCallback: ^( int index, CGRect rect ) {

    } inputImage: inputImage arguments: @[@(inputWidth)]];

    return result;

    }

    ```

这里只需要重写 outputImage 方法即可。

extent 用于返回 CIImage 对象对应的 bounds,通过它可以拿到图片的宽度

然后通过 applyWithExtent 来设置对应的参数。

这里有4个参数。

extent,也就是之前提到的 DOD,暂且略过。 callback,也就是之前提到的 ROI,暂且略过。 image,缺省的 inputImage,传入我们的成员变量 inputImage 即可。 args,输入参数数组,与 CIKernel 中定义的一一对应。这里只有一个 inputWidth。

PS:这里可能有同学会有疑惑,为什么 inputImage 可以缺省,inputWidth 就需要传入呢。这里暂且不纠结,下面会详细说明

如此,一个自定义 Filter 就完成了。简单吧~ 4. 使用

至于使用上,则和普通的 CIFilter 基本一致。

如此,我们便可得到翻转后的图片。

  1. 更多

    当然,如果你是一个完美主义者,我觉得你还还可以做更多~

    ```objective-c

  2. (NSDictionary *)customAttributes

    {

    return @{

    };

    }

    ```

    可以为自定义的 Filter 添加对应的参数描述,以及默认值,范围限制等。

这不是必须的,但却是可取的。至于如何设置,可以参考 CIFilter 对应的 attributes 属性,或者参照上面这个例子。 另外,iOS 9之后,引入了 registerFilterName , 你可以通过重写 + (CIFilter *)filterWithName: (NSString *)name;,然后外部使用的时候,跟CIFilter 一模一样。

不过需要 iOS 9以上才支持,另外一般用于打包成 Image Units 给他人使用。

正常情况下应该是用不到。如果真有这个需求,可以参考这篇文章: Packaging and Loading Image Units

至此,自定义 Filter 的流程就算走完了,我们很容易就可以配置好需要的环境。

然而,真正的自定义部分,才刚刚开始!

DOD & ROI

1. DOD

DOD ( domain of definition ) ,简单来说就是 Filter 处理后,输入的图片区域。

一般来说,Filter 操作都是基于原图,添加上效果,但是并不会改变图片的大小,显示区域。所以一般与原图的一致即可。

但是针对形变类的 Filter,则需要根据输出图片大小,设置正确的 DOD。

2. ROI

ROI ( region of interest ),在一定的时间内特别感兴趣的区域,即当前处理区域。

可以简单的理解为:当前处理区域对应于原图中的哪个区域。

ROI 的定义如下:

CIKernelROICallback 在 Core Image 内部进行处理的时候,会多次调用。

index 表示输入图片的下标,顺序和 kernel 中的入参顺序一致,从0开始。

destRect 表示输出图片的区域。 也就是我们先前设置的 DOD。

那,我们为什么要显示设置 ROI 呢 ?

因为输入图片中,参与处理的实际区域,Core Image 是无法知道的,我们需要显式的告诉 CI 这个区域。

这么讲可能有点难以理解,下面我们看两个具体的例子。

先看一个旋转的例子。 这里就是进行了 x,y 互换操作。很容易得到我们的 DOD:

那 ROI 应该怎么设置呢 ?我们之前说过,ROI 计算就是计算当前处理区域对应于原图中的哪个区域。

也就是一个逆向过程。

假如,A:输入图片中的某点 B:输出图片中的某点。那么 ROI 计算可以理解成 ROI(B)= A。

理解好这点,我们不难写出这个操作对应的 ROI:

另外,当输入图片不止一个的时候,则需要根据 index 来做区别。因为这里的 rect 每次都是返回 DOD,而不是当前图片的 extent。

Last updated