philm-iOS-wiki
  • 介绍
  • 网络层
    • 说明
  • UI
    • 说明
    • 在ios7以前使用ColorSpace的坑
    • UITableView偏移异常问题
    • drawRect时单独设置文字阴影无效
    • Xcode9下相册访问权限问题
    • 避免同时点击多个Button
    • scroll上的button延迟响应问题
    • uibutton触发边界事件
    • ios 11 上tableview 改动
    • YYImage 显示指定区域的图片
  • 数据持久化
    • 说明
  • 其它
    • 取消延迟执行之坑
    • NSString 转换 float 的精度问题
  • 每周阅读
    • 目录
    • 深入思考NSNotification
    • gitBook使用小助手
    • iOS App签名的原理
    • 响应链
    • iOS10跳转系统到设置页
    • SDWebImage下载高清图内存问题
    • iOS圆角避免离屏渲染
    • 常用的延时调用
    • iOS 神经网络
    • SDWebImage缓存策略
    • 3Dtouch
    • 为什么 Objective-C 对象存储在堆上而不是栈上
    • 深入浅出理解视频编码H264结构
    • CATextLayer学习
    • cocoaPods
    • 任意网站支持RSS
    • Metal简介
    • 动态更改icon
    • CAReplicatorLayer
    • 增加点击间隔
    • 勒索病毒当道的时代
    • iOS常用宏定义
    • Metal实现YUV转RGB渲染视频
    • 获取当前下载的app及下载进度
    • OpenGL ES 三种类型修饰 uniform attribute varying
    • 技术部门引入OKR
    • 基于runloop的线程保活、销毁与通信
    • 深入理解哈希表
    • TOLL-FREE BRIDGING 和 UNMANAGED
    • 开发者能拿到的标识符
    • Swift自定义LOG
    • 系统通知整理
    • iOS 中的 imageIO 与 image 解码
    • CGImageRef基本介绍及方法说明
    • Swift 3.0 语法
    • webview加载部分网页
    • 在CAAnimation中暂停动画
    • 把代码迁移到协调器上
    • ios11API更新整理
    • 非越狱iOS设备的远程控制实现原理
    • 关于本地化
    • swift命名空间
    • CoreML与coremltools体验
    • 力学动画
    • Swift 4官方文档中文版: The Basic(上)
    • swift 中的KVO用法
    • GPUImage的图像形变设计(简单形变部分)
    • iOS响应式架构
    • 移动端图片上传旋转、压缩的解决方案
    • AVFoundation使用指南AVAssert使用
    • 过渡动画
    • 谈谈 MVX 中的 Model
    • AVFoundation编程-AVPlayer使用
    • GPUImage的图像形变设计(复杂形变部分)
    • What's New in LLVM 9
    • ios的事件机制
    • GPUImage源码解读(一)
    • GPUImage源码解读(二)
    • iOS 启动优化
    • 模块化 Swift 中的状态
    • swift中的let和var背后的编程模式
    • Swift Runtime动态性分析
    • RAC下的响应式编程
    • GPUImage源码解读(三)
    • 如何准确判断webView是否加载完成
    • NSObject的+load和+initialize详解
    • ios8以后设置启动图
    • GPUImage源码解读(四)
    • Swift自动闭包
    • IOS11新特性
    • GPUImage源码解读(五)
    • 理解 OC 内部的消息调用、消息转发、类和对象
    • 修饰符
    • IOS 切面统计事件解耦
    • GPUImage源码解读(六)
    • CoreImage介绍
    • 影响Core Animation性能的原因
    • Instruments中的动画工具选项介绍
    • GPUImage源码解读(七)
    • Xcode 7新的特性Lightweight Generics 轻量级泛型与__kindof修饰符
    • GPUImage源码解读(八)
    • Core Image之自定 Filter
    • iOS通用链接
    • 谈nonatomic非线程安全问题
    • 深拷贝与浅拷贝
    • CIKernel 介绍
    • iOS11适配
    • GPUImage源码解读(九)
    • CVPixelBufferCreate使用的坑
    • ios一窥并发底层
    • ARKit进阶:物理世界
    • ARKit的工作原理及流程介绍
    • UI线程卡顿监控
    • FBKVOController使用
    • GPUImage源码解读(十)
    • WKWebView在ios11崩溃问题解决方法
    • 微信iOS SQLite源码优化实践
    • HEIF 和 HEVC 研究
    • 谈谈 iOS 中图片的解压缩
    • 提升 iOS 开发效率! Xcode 9 内置模拟器的9个技巧
    • ObjC和JavaScript的交互,在恰当的时机注入对象
    • iOS数据保护
    • iOS11中网络层的一些变化(Session707&709脱水版)
    • GPUImage源码解读(十一)
    • 一种避免 iOS 内存碎片的方法
    • pods的原理
    • GPUImage源码解读(十二)
    • GPUImage源码解读(十三)
    • iOS 11 Layout的新特性
    • iOS应用瘦身方法思路整理
    • GPUImage源码解读(十四)
    • CAEmitterLayer属性介绍
    • 浅析移动蜂窝网络的特点及其省电方案
    • 如何在 table view 中添加 3D Touch Peek & Pop 功能
    • iOS中锁的介绍与使用
    • NSLog效率低下的原因及尝试lldb断点打印Log
    • GPUImage源码解读(十五)
    • GPUImage源码解读(十六)
    • CADisplayLink
    • GPUImage源码解读(十七)
    • CADisplayLink
    • 老生常谈category增加属性的几种操作
    • 30行代码演示dispatch_once死锁
    • GPUImage源码解读(十八)
    • YYImage设计思路
    • GPUImage源码解读(十九)
    • 深入理解Tagged Pointer
    • iOS 11:WKWebView内容过滤规则详解
    • Swift语法对编译速度的影响
    • GPUImage源码解读(二十)
    • GPUImage源码解读(二十一)
    • iOS App间常用的五种通信方式
    • YYCache深入学习
    • 冲顶大会插件
    • iOS高性能图片架构与设计
    • YUV颜色编码解析
    • iOS传感器:App前后台切换后,获取敏感信息使用touch ID进行校验
    • GPUImage源码解读(二十二)
    • GPUImage源码解读(二十三)
    • 从零开始的机器学习 - Machine Learning(一)
    • 从零开始的机器学习 - Machine Learning(二)
    • GPUImage源码解读(二十四)
    • Objective-C消息转发机制
    • iOS 程序 main 函数之前发生了什么
    • MMKV--基于 mmap 的 iOS 高性能通用 key-value 组件
    • Objective-C 消息发送与转发机制原理
    • 谈Objective-C block的实现
    • GPUImage源码解读(二十五)
    • podfile语法
    • 轻量级低风险 iOS 热更新方案
    • 使用objection来模块化开发iOS项目
    • swift 中delegate的使用注意
    • 使用appledoc自动生成api文档
    • UITextChecker的使用
    • ARKit 如何给SCNNode贴Gif图片
    • Unity与iOS平台交互和原生插件开发
    • SceneKit编程珠玑
Powered by GitBook
On this page
  • 自定义 Filter 流程
  • DOD & ROI
  • 1. DOD
  • 2. ROI
  1. 每周阅读

Core Image之自定 Filter

PreviousGPUImage源码解读(八)NextiOS通用链接

Last updated 7 years ago

文章摘自

自定义 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 中,添加如下代码:

static CIKernel *customKernel = nil;

- (instancetype)init {

    self = [super init];
    if (self) {
        if (customKernel == nil)
        {
            NSBundle *bundle = [NSBundle bundleForClass: [self class]];
            NSURL *kernelURL = [bundle URLForResource:@"MirrorX" withExtension:@"cikernel"];

            NSError *error;
            NSString *kernelCode = [NSString stringWithContentsOfURL:kernelURL
                                                            encoding:NSUTF8StringEncoding error:&error];
            if (kernelCode == nil) {
                NSLog(@"Error loading kernel code string in %@\n%@",
                      NSStringFromSelector(_cmd),
                      [error localizedDescription]);
                abort();
            }

            NSArray *kernels = [CIKernel kernelsWithString:kernelCode];
            customKernel = [kernels objectAtIndex:0];
        }
    }
    return self;
}

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

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

+ (nullable NSArray<CIKernel *> *)kernelsWithString:(NSString *)string  NS_AVAILABLE(10_4, 8_0);

至此,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 ) {

      return rect;

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

    return result;

    }

    ```

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

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

/* Return a rect the defines the bounds of non-(0,0,0,0) pixels */
@property (NS_NONATOMIC_IOSONLY, readonly) CGRect extent;

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

- (nullable CIImage *)applyWithExtent:(CGRect)extent
                          roiCallback:(CIKernelROICallback)callback
                           inputImage:(CIImage*)image
                            arguments:(nullable NSArray<id> *)args;

这里有4个参数。

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

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

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

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

#import "MirrorXFilter.h"

// 1. 将UIImage转换成CIImage
CIImage *ciImage = [[CIImage alloc] initWithImage:self.imageView.image];

// 2. 创建滤镜
self.filter = [[MirrorXFilter alloc] init];
// 设置相关参数
[self.filter setValue:ciImage forKey:@"inputImage"];

// 3. 渲染并输出CIImage
CIImage *outputImage = [self.filter outputImage];

// 4. 获取绘制上下文
self.context = [CIContext contextWithOptions:nil];

// 5. 创建输出CGImage
CGImageRef cgImage = [self.context createCGImage:outputImage fromRect:[outputImage extent]];
UIImage *image = [UIImage imageWithCGImage:cgImage];
// 6. 释放CGImage
CGImageRelease(cgImage);

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

  1. 更多

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

    ```objective-c

  2. (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]
         },

    };

    }

    ```

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

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

/** Publishes a new filter called 'name'.

 The constructor object 'anObject' should implement the filterWithName: method.
 That method will be invoked with the name of the filter to create.
 The class attributes must have a kCIAttributeFilterCategories key associated with a set of categories.
 @param   attributes    Dictionary of the registration attributes of the filter. See below for attribute keys.
*/
+ (void)registerFilterName:(NSString *)name
               constructor:(id<CIFilterConstructor>)anObject
           classAttributes:(NSDictionary<NSString *,id> *)attributes NS_AVAILABLE(10_4, 9_0);

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

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

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

DOD & ROI

1. DOD

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

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

CGRect dod = inputImage.extent;

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

2. ROI

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

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

ROI 的定义如下:

/* Block callback used by Core Image to ask what rectangles of a kernel's input images
 * are needed to produce a desired rectangle of the kernel's output image.
 *
 * 'index' is the 0-based index specifying which of the kernel's input images is being queried.
 * 'destRect' is the extent rectangle of kernel's output image being queried.
 *
 * Returns the rectangle of the index'th input image that is needed to produce destRect.
 * Returning CGRectNull indicates that the index'th input image is not needed to produce destRect.
 * The returned rectangle need not be contained by the extent of the index'th input image.
 */
typedef CGRect (^CIKernelROICallback)(int index, CGRect destRect);

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

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

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

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

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

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

CGRect dod = CGRectMake(inputImage.extent.origin.y, inputImage.extent.origin.x, inputImage.extent.size.height, inputImage.extent.size.width);

// e.g.
// 原图片extent (0, 0, 200, 300)
// 旋转后的输出图片 (0, 0, 300, 200),也就是 DOD

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

也就是一个逆向过程。

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

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

CIKernelROICallback callback = ^(int index, CGRect rect) {
    return CGRectMake(rect.origin.y, rect.origin.x, rect.size.height, rect.size.width);
};

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

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

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

Packaging and Loading Image Units
https://colin1994.github.io/2016/10/21/Core-Image-Custom-Filter/