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
  • destCoord
  • 2. CIWarpKernel
  • CIColorKernel :
  • CIWarpKernel:
  • 注意点
  • 开发技巧
  • 工具介绍
  1. 每周阅读

CIKernel 介绍

在此之前,我们先了解下它的一些背景知识。

CIKernel 需要使用 Core Image Kernel Language (CIKL) 来编写,CIKL 是 OpenGL Shading Language (GLSL) 的子集,如果你之前有过 OpenGL 着色器编写的经验,这部分你会感觉格外亲切。CIKL 集成了 GLSL 绝大部分的参数类型和内置函数,另外它还添加了一些适应 Core Image 的参数类似和函数。

一个 kernel 的处理过程,可以用下面伪代码表示:

for i in 1 ... image.width
    for j in 1 ... image.height
        New_Image[i][j] = CustomKernel(Current_Image[i][j])
    end
end

也就是说,每个需要处理的 fragment 都会调用一次 kernel 相关操作,每次操作的目的就是返回当前 fragment 对应的结果 fragment,这里 fragment 可以理解为像素点。

所以我们的 kernel,应该是针对一个点,而不是一张图片。

Core Image 内置了3种适用于不同场景的 Kernel,可以根据实际需求来选择。

CIColorKernel:用于处理色值变化的 Filter。 CIWarpKernel:用于处理形变的 Filter。 CIKernel:通用。 CIColorKernel,CIWarpKernel 是官方推荐使用的。某个 Filter,在使用它们能实现的情况下,应该使用它们,即使是一个 CIKernel 拆分成多个 CIColorKernel 以及 CIWarpKernel,也应该用这种方式。因为 Core Image 内部对这两张 Kernel 做了优化。

当然,它们的使用时有限制的。目的一定要很纯粹,比如 CIColorKernel 只能处理色值上的变化。否则就算定义为 CIColorKernel,如果实现上涉及了其他 CIColorKernel 不允许的操作,Core Image 也会当做普通的 CIFilter 处理。 另外,kernel 的入参只支持下面这么几种:

描述

类型

Kernel routine input parameter

Object

sampler

CISampler

__table sampler

CISampler

__color

CIColor

float

NSNumber

vec2, vec3, or vec4

CIVector

简单说明一下:

sampler:可以理解成纹理,或者图片。外部以 CIImage 形式传入。 table sampler:表示颜色查找表(lookup table),虽然它也是图片,但是添加该声明可以避免被修改。外部以 CIImage 形式传入。 color:表示颜色。外部以 CIColor 形式传入。 float:kernel 内部处理都是 float 类型。外部以 NSNumber 形式传入。 vecN:表示一个多元向量。比如 vec2 可以表示一个点,vec4 可以表示一个色值。外部以 CIVector 形式传入

下面我会通过一个 Demo,讲解这三种 Kernel 的具体用法。

  1. CIColorKernel

首先看下官方的定义:

/*
 * CIColorKernel is an object that encapsulates a Core Image Kernel Language
 * routine that processes only the color information in images.
 *
 * Color kernels functions are declared akin to this example:
 *   kernel vec4 myColorKernel (__sample fore, __sample back, vec4 params)
 *
 * The function must take a __sample argument for each input image.
 * Additional arguments can be of type float, vec2, vec3, vec4, or __color.
 * The destination pixel location is obtained by calling destCoord().
 * The kernel should not call sample(), sampleCoord(), or samplerTransform().
 * The function must return a vec4 pixel color.
 */
NS_CLASS_AVAILABLE(10_11, 8_0)
@interface CIColorKernel : CIKernel

很重要的一点:processes only the color information in images,它只处理图片的颜色信息。

所以在使用它之前,一定要确保该 Filter 只涉及颜色处理。

CIKL 的语法和大多数 C 阵营一样,变量,运算符,控制结构,函数等都大同小异,所以它的学习成本是很低的。

真正的核心应该是:如果用这样的语言来实现这个滤镜,也就是我们经常说的算法。

下面我们以一个 Vignette 来实际讲解一下。

Vignette(A)= A * Darken = B; 而 Darken 的计算依赖 A 与中心点的距离。

如此,我们可以很容易的写出对应的 kernel:

kernel vec4 vignetteKernel(__sample image, vec2 center, float radius, float alpha)
{
    // 计算出当前点与中心的距离
    float distance = distance(destCoord(), center) ;
    // 根据距离计算出暗淡程度
    float darken = 1.0 - (distance / radius * alpha);
    // 返回该像素点最终的色值
    image.rgb *= darken;

    return image.rgba;
}

和 C 语言的一样,函数需要具备:

返回类型:vec4 函数名:vignetteKernel 参数列表:__sample image, vec2 center, float radius, float alpha) 函数体:{}中的具体实现 有所不同的,kernel 函数需要带上 kernel 关键字,与其它普通函数做区分。一个 .cikernel 文件中,允许包括多个函数,甚至是多个 kernel 函数,不过函数调用要出现在函数定义之后!

另外,这里有个特别的参数类型,sample ,和之前讲的 sampler 有所不同。因为这里我们使用的是 CIColorKernel,在得到高效性能的同时,也有一定的局限性。因为只是处理图片当前位置的颜色信息,所以 sample 提供的 rgba 变量足够了,无法获取一些其它的信息。

比如在 CIKernel 中,可以通过 sample() 等函数获取其它位置的色值,而在 CIColorKernel 中,无法使用 sample(), sampleCoord() 以及 samplerTransform() 。

下面逐行解释这个 kernel。

// 计算出当前点与中心的距离
float distance = distance(destCoord(), center) ;

destCoord

  • varying vec2 destCoord ()

    返回当前正在处理的像素点所处坐标。(working space coordinates)

    这里使用的 CIKL 内置的函数 destCoord,它返回的坐标是基于 working space 的。所谓 working space,即工作空间,它的取值范围对应图片实际大小。比如 inputImage 的大小为 300 * 200,那么 destCoord() 返回坐标的取值范围在 (0, 0) - (300, 200)。

    distance

  • float distance (vec2 p0, vec2 p1)

    计算向量p0,p1之间的距离

如此便能很容易得到当前点与中心的距离。

// 根据距离计算出暗淡程度
float darken = 1.0 - (distance / radius * alpha);

之后根据清晰程度与到中心距离呈反比这一原理,结合外部控制的 alpha 变量,计算出暗淡程度。

// 返回该像素点最终的色值
image.rgb *= darken;
return image.rgba;

这里之前提到,__sample 有个 rgba 变量,通过它可以获取到当前处理点的色值。

在 CIKL 中,vec4 的任何一个分量都可以单独获取,也可以组合获取,例如 image.a,image.rrgg 等,都是可行的。

CIColorKernel 是针对色值的处理,所以它的返回值必须是一个代表色值的 vec4 类型变量。

至此,这个 vignetteKernel 就分析完毕了。很简单吧~

2. CIWarpKernel

同样,先看下文档定义:

/*
 * CIWarpKernel is an object that encapsulates a Core Image Kernel Language
 * function that processes only the geometry of an image.
 *
 * Warp kernels functions are declared akin to this example:
 *   kernel vec2 myWarpKernel (vec4 params)
 *
 * Additional arguments can be of type float, vec2, vec3, vec4.
 * The destination pixel location is obtained by calling destCoord().
 * The kernel should not call sample(), sampleCoord(), or samplerTransform().
 * The function must return a vec2 source location.
 */
NS_CLASS_AVAILABLE(10_11, 8_0)
@interface CIWarpKernel : CIKernel

同样,它也有很重要一点:processes only the geometry of an image。它只处理图片的几何形状。

所谓的改变几何形状,也就是形变,把原本放置在 A 处的点,用 B 处的点去填充,或者反过来,把原本 B 处的点,挪到 A 处去,也是一样的。

它可以用这个表达式表示:Warp(A)= B;

所以它和之前的 CIColorKernel 不同,它的返回值是 vec2,代表点的坐标。另外它只允许传入一张图片,所以这里的 inputImage 缺省了。

依照这个简单算法,我们可以很容易的写出对应的 kernel:

kernel vec2 pixellateKernel(float radius)
{
    vec2 positionOfDestPixel, centerPoint;
    // 获取当前点坐标
    positionOfDestPixel = destCoord();
    // 获取对应晶格内的中心像素点
    centerPoint.x = positionOfDestPixel.x - mod(positionOfDestPixel.x, radius * 2.0) + radius;
    centerPoint.y = positionOfDestPixel.y - mod(positionOfDestPixel.y, radius * 2.0) + radius;

    return centerPoint;
}

同样的,先是获取到当前处理点的坐标,positionOfDestPixel

// 获取对应晶格内的中心像素点
centerPoint.x = positionOfDestPixel.x - mod(positionOfDestPixel.x, radius * 2.0) + radius;
centerPoint.y = positionOfDestPixel.y - mod(positionOfDestPixel.y, radius * 2.0) + radius;

然后这里的 mod (x, y) 和平时使用的一样,计算 x / y 的余数。

至于为什么这个式子能获得中心像素点坐标,想必一看就懂了吧~(不懂的可以拿张纸画画)

最后返回中心点坐标,替换当前点。

如此,一个简单的马赛克就完成了~ 3. CIKernel 我们之前说过,CIColorKernel 和 CIWarpKernel 内部做了优化,要尽可能的使用它们。除非真的有特殊需求,是它们无法实现的。下面罗列了 CIColorKernel 和 CIWarpKernel 的一些局限:

CIColorKernel :

*只处理当前处理点色值,无法获取到其它点的状态。

CIWarpKernel:

1、判断当前点是否在传入点的处理范围内。 2、如果在,返回马赛克贴图中对应的像素点色值。 3、如果不在,返回当前点色值。 很明显,它需要两张图片,一张我们的待处理图片,一张马赛克贴图。所以 CIWarpKernel 不适用。

另外,待处理图片与马赛克贴图之前不是一一对应关系,在第二步,返回马赛克贴图中对应的像素点色值中,需要一个映射计算,即当前点对应马赛克贴图中的某点。所以 CIColorKernel 也不适用。

这种情况下,就要使用通用的 CIKernel 了。

下面是对应的 kernel

kernel vec4 mosaicKernel(sampler image, sampler maskImage, float radius, vec2 point, float maskWidth, float maskHeight)
{
    // 获取当前点坐标
    vec2 textureCoordinate = destCoord();
    // 计算当前点与传入点的距离
    float distance = distance(textureCoordinate, point);
    if (distance < radius) {
        // 在处理范围内, 计算对应马赛克贴图中的位置
        float resultX = mod(textureCoordinate.x, maskWidth);
        float resultY = mod(textureCoordinate.y, maskHeight);
        return sample(maskImage, samplerTransform(maskImage, vec2(resultX, resultY)));
    }
    else {
        // 返回原图对应像素点色值
        return sample(image, samplerTransform(image, textureCoordinate));
    }
}

这里参数比较多,分别对应: image:待处理图片 maskImage:马赛克贴图 radius:处理范围,半径 point:传入点,即当前触摸的点 maskWidth:马赛克贴图宽度 maskHeight:马赛克贴图高度 上面的 kernel,使用了两个新的函数,sample 和 samplerTransform。

vec4 sample (uniform sampler src, vec2 point) Returns the pixel value produced from sampler src at the position point, where point is specified in sampler space. 返回图片 src 指定点 point 处的色值。point 是基于 sampler space。 vec2 samplerTransform (uniform sampler src, vec2 point) Returns the position in the coordinate space of the source (the first argument) that is associated with the position defined in working-space coordinates (the second argument). (Keep in mind that the working space coordinates reflect any transformations that you applied to the working space.) For example, if you are modifying a pixel in the working space, and you need to retrieve the pixels that surround this pixel in the original image, you would make calls similar to the following, where d is the location of the pixel you are modifying in the working space, and image is the image source for the pixels. 返回图片 src 指定点 point 处坐标对应的基于 sampler space 的坐标。point 是基于working space。 sampler space 的取值是 0.0 - 1.0,左下角为原点,向右,向上递增。

了解了这两个函数的用法,想必这段代码就没什么需要特别说明的地方了,注释已经很清楚,不再累述。

注意点

  1. premultiply

    vec4 premultiply (vec4 color) Multiplies the red, green, and blue components of the color parameter by its alpha component.

将颜色变量的r、g、b元素值分别除以 alpha ,返回一个新的四维颜色向量。

pixel(R, G, B, A) —— (premultiply) ——> (R*A, G*A, B*A, A)

—— (unpremultiply) ——> (R, G, B, A)。

在 Core Image 中,默认颜色空间是 sRGB,在 kernel 中得到的色值,都经过了 Premultiplied Alpha 处理。

所以如果 kernel 涉及 alpha 相关操作,则需要先执行 unpremultiply,返回正确的 rgba。处理完之后,再执行 premultiply 操作。

kernel vec4 _invertColor(sampler source_image)
{
    vec4 pixValue;
    // samplerCoord 返回当前像素点在 sampler space 中的位置
    // kernel 无法知道该图片是否进行了某些变换操作,所以确保转换为 sampler space 中的位置 是有必要的
    pixValue = sample(source_image, samplerCoord(source_image));
    // 执行 unpremultiply 操作, 得到真正的 RGB 值
    // (R*A, G*A, B*A, A) ——(unpremultiply)——> (R, G, B, A)
    // Core Image is always RGB based.
    unpremultiply(pixValue); 
    // invertColor
    pixValue.r = 1.0 - pixValue.r; 
    pixValue.g = 1.0 - pixValue.g;
    pixValue.b = 1.0 - pixValue.b;
    // premultiply. (R, G, B, A) —> (R*A, G*A, B*A, A)
    return premultiply(pixValue); 
}


// 优化:
// 避免了 unpremultiply 和 premultiply 操作,能更高效执行。
// pixValue 是 (R*A, G*A, B*A, A), pixValue.a - pixValue.r = (1-r)*a. 和最终 premultiply 得到的结果一样.
kernel vec4 _invertColor(sampler source_image)
{
    vec4 pixValue;
    pixValue = sample(source_image, samplerCoord(source_image));
    pixValue.rgb = pixValue.aaa - pixValue.rgb;
    return pixValue;
}
  1. 关键字

    和 C 语言等一样,CIKL 中变量的命名不能和关键字相同。

  1. Array, Mat

    In addition, the following are not implemented:

    *Data types:mat2, mat3, mat4, struct, arrays

    这些数据类型 Core Image 不支持。但是在 kernel 内部却可以使用 …

如果当做参数传入,则会报错:

invalid kernel parameter type; valid types are: ‘float’, ‘vec2’, ‘vec3’, ‘vec4’, ‘sampler’, ‘sample’, ‘color’ 这也导致了一些依赖关键点的算法无法实现。

  1. 坐标系

UIKit 坐标系,原点在屏幕左上,x轴向右,y轴向下。

Core Image 和 OpenGL 坐标系原点在屏幕的左下,x轴向右,y轴向上。

所以位置的处理上要注意。 6. 局限 kernel 的输入和输出像素可以相互映射。大多数像素处理都可以用这种方式表达,但是有的图像处理操作很困难,甚至不可能。

kernel 的使用上还是有一定的局限性。比如说通过输入图像映射计算直方图是很困难的。也不可以执行种子填充算法或者其他需要复杂条件语句的图像分析操作。 7. 性能优化

kernel 中的内容要尽可能简单,高效。

  • 展开循环操作会更快。

  • 外部能传入的变量,尽量不要在 kernel 中计算获取。

开发技巧

  1. Log

+(id)kernelsWithString:(id)arg1 messageLog:(id)arg2 ;

这是 CIKernel.h 里面的私有方法,在调试阶段可以利用它来打印 kernel 中的错误。

比如

NSMutableArray *messageLog = [NSMutableArray array];
NSArray *kernels = [[CIKernel class]          performSelector:@selector(kernelsWithString:messageLog:) withObject:kernelCode withObject:messageLog];
if ( messageLog.count > 0) 
      NSLog(@"Error: %@", messageLog.description);
customKernel = [kernels objectAtIndex:0];

// 错误 log
Error: (
        {
        CIKernelMessageLineNumber = 5;
        CIKernelMessageType = CIKernelMessageTypeError;
        kCIKernelMessageDescription = "unkown type or function name 'destCoordE'; did you mean 'destCoord'?";
        kCIKernelMessageOffset = 142;
    },
        {
        CIKernelMessageLineNumber = 7;
        CIKernelMessageType = CIKernelMessageTypeError;
        kCIKernelMessageDescription = "invalid operands to binary expression ('float' and 'int')";
        kCIKernelMessageOffset = 281;
    }
)
  1. CI_PRINT_TREE

这里 Core Image 中非常实用的一个环境变量,通过设置它,可以很方便的查看 Core Image 工作过程中到底做了什么。比如:

  • 工作在 GPU 还是 CPU 上?

  • 各个 kernel 的参数值?

  • Core Image 是如何链接 kernel?

  • DOD,ROI 如何设置的?

  • 对于大图如何拆分处理?

PS : 至于 CI_PRINT_TREE 具体应该如何使用,没有找到相关资料,只是在 Session 中提到过。 包括 ObjC 中国 上的翻译:你可以通过在 Xcode 中设置计划配置(scheme configuration)里的 CI_PRINT_TREE 环境变量为 1 来决定用 CPU 还是 GPU 来渲染,也是很不准确的。 这里的结论都是自己摸索后的总结,所以可能存在错误或者遗漏,欢迎补充交流~

CI_PRINT_TREE 的设置大致是这样的:分成 A B 两部分,它们可以结合使用。

其中 A 是主要分类,B 是辅助功能。 A 包括:

  • 1 initial graph

  • 2 optimized graph

  • 4 tile graph

  • 8 programs graph

  • 16 timing graph

    B 包括:

  • graphviz

  • dump-inputs

  • dump-intermediates

  • skip-cpu

  • skip-gpu

  • skip-small

  • frame-

使用上,比如简单的查看 initial graph 做了什么,即我们添加这个 Filter 的时候,初始化过程执行了什么,传入了哪些参数。当然,这个过程它并没有真正得到渲染,只是一个操作流程列表。设置 CI_PRINT_TREE =

initial graph render_to_display (opengles2 context 1 frame 1) format=RGBA8 roi=[0 156 750 748] = 
  clamptoalpha roi=[0 156 750 748] extent=[0 156 750 748] opaque
    colormatch workingspace-to-devicergb roi=[0 156 750 748] extent=[0 156 750 748] opaque
      affine [2 0 0 2 0 156] roi=[0 156 750 748] extent=[0 156 750 748] opaque
        colorkernel 
  roi=[0 0 375 374] extent=[0 0 375 374] opaque
          affine [1 0 0 -1 0 374] roi=[0 0 375 374] extent=[0 0 375 374] opaque
            colormatch "sRGB IEC61966-2.1"-to-workingspace roi=[0 0 375 374] extent=[0 0 375 374] opaque
              CGImageRef 0x1701c4380 RGBX8 375x374  alpha_one roi=[0 0 375 374] extent=[0 0 375 374] opaque

这里有很多关键信息,十分详细。它的阅读顺序是从下往上,我们简单分析下:

  • CGImageRef: 指代我们传入的图片。

  • 每个阶段的 ROI,DOD。

  • colormatch “sRGB IEC61966-2.1”-to-workingspace :传入的颜色空间

  • vignetteKernel(image,center=[187.5 187],radius=187.5,alpha=0.0537634) :kernel 的每个参数

  • colormatch workingspace-to-devicergb: 输出的颜色空间

    opengles2 :工作在 GPU 上

  • context 1 frame 1 :分别指代当前 context 以及第几帧。每次渲染 frame + 1

    当然,这只是 CI_PRINT_TREE 的一部分功能,如果你设置 CI_PRINT_TREE = 8 (programs graph ),你又会得到这样的信息:

    programs graph render_to_display (opengles2 context 1 frame 4 tile 1) format=RGBA8 roi=[0 111 640 640] = 
    program affine(clamp_to_alpha(linear_to_srgb(vignetteKernel(affine(srgb_to_linear(swizzle_bgr1())))))) rois=[0 111 640 640] extent=[0 111 640 640]
      IOSurface 0x60000019ddc0 RGBA8 375x374 alpha_one edge_clamp rois=[0 0 375 374] extent=[infinite][0 0 375 374] opaque

    这里描述了程序图表,即真正涉及到的操作。

同样是从下往上看,各个操作的层级关系就很明显了。除了我们提供的 vignetteKernel,Core Image 内部还做了其他的操作,比如 linear_to_srgb,clamp_to_alpha 等。它们的具体实现如下:

Filter DAG:
Node: 0
  original source: vec4 _ci_clamp_to_alpha(vec4 s) { return clamp(s, 0.0, s.a); }
  printed AST: vec4 _ci_clamp_to_alpha(vec4 s) {
  return clamp(s, 0.000000e+00, s.a);
}
  children: 1
End Filter Node

Node: 1
  original source: vec4 _ci_premultiply(vec4 s) { return vec4(s.rgb*s.a, s.a); }
  printed AST: vec4 _ci_premultiply(vec4 s) {
  return vec4(s.rgb * s.a, s.a);
}
  children: 2
End Filter Node

Node: 2
  original source: vec4 _ci_linear_to_srgb(vec4 s)
{
  s.rgb = sign(s.rgb)*mix(s.rgb*12.92, pow(abs(s.rgb), vec3(0.4166667)) * 1.055 - 0.055, step(0.0031308, abs(s.rgb)));
  return s;
}
  printed AST: vec4 _ci_linear_to_srgb(vec4 s) {
  s.rgb = sign(s.rgb) * mix(s.rgb * 1.292000e+01, (pow(abs(s.rgb), vec3(4.166667e-01)) * 1.055000e+00) - 5.500000e-02, step(3.130800e-03, abs(s.rgb)));
  return s;
}
  children: 3
End Filter Node

Node: 3
  original source: vec4 _ci_unpremultiply(vec4 s) { return vec4(s.rgb/max(s.a,0.00001), s.a); }
  printed AST: vec4 _ci_unpremultiply(vec4 s) {
  return vec4(s.rgb / max(s.a, 1.000000e-05), s.a);
}
  children: 6
End Filter Node

Node: 6
  <sample with transform>
  original source: vec4 read_pixel(sampler2D image, vec2 c, mat3 m){ return texture2D(image, (vec3(c, 1.0) * m).xy);}
  printed AST: vec4 read_pixel_6(sampler2D image, vec2 c, mat3 m) {
  return texture2D(image, (vec3(c, 1.000000e+00) * m).xy);
}
  children: 4 7 5
End Filter Node

Node: 4
  image: 6
  printed: uniform lowp sampler2D image6_0
End Filter Node

Node: 7
  position use <_dc>
End Filter Node

Node: 5
  <transform>
  uniform: 6
End Filter Node

这个 DAG(有向无环图),具体描述了相关操作的实现过程,比较简单,可以自己看看,这里不累述。

工具介绍

QC 已经内置了适合 Core Image 的模板,并且实现了动态模糊滤镜效果。不过这里为了了解 QC 的使用方式,不使用内置的模板,从头开始。File —> New Blank,创建一个空白的 QC 工程。

PS: QC 的功能很强大,这里只介绍 Core Image Filter 编辑过程中会用到的,以及我所掌握的…

  1. 概念介绍

    在讲解使用方式之前,介绍几个基本概念。

一次滤镜操作,可以简单理解成: 输入—>(Patch)—>输出。

Patch 可以理解成 Kernel。

输入则与 Kernel 的参数相对应,可以是 image,color,float…

输入这里一般就是处理后的图像。

还有一个比较特殊的 Patch,Layer。相当于画布,可以把结果图显示在上面,它也有层的概念。 1. 工作区介绍 编辑区: 这是主面板,主要衔接各个 Patch,以及它们的输入,输出。

Library: 这里陈列了 QC 内置的所有 Patch(也可以添加自定义的 Patch 进来),以及它们的详细使用介绍。(通过点击主面板左上角的 Patch Library 打开)

改成如下放大眼睛核心代码:

kernel vec4 coreImageKernel(sampler image, vec2 centerPostion, float radius, float scaleRatio, float aspectRatio)
{
    vec2 currentPosition = destCoord();
    vec2 positionToUse = currentPosition;

     vec2 currentPositionToUse = vec2(currentPosition.x, currentPosition.y * aspectRatio + 0.5 - 0.5 * aspectRatio);
     vec2 centerPostionToUse = vec2(centerPostion.x, centerPostion.y * aspectRatio + 0.5 - 0.5 * aspectRatio);

     float r = distance(currentPositionToUse, centerPostionToUse);

     if(r < radius)
     {
         float alpha = 1.0 - scaleRatio * (r / radius - 1.0)*( r / radius - 1.0);
         positionToUse = centerPostion + alpha * (currentPosition - centerPostion);
         return sample(image, samplerTransform(image, positionToUse));
     }
     else
     {
         return sample(image, samplerTransform(image, positionToUse));
     }
}

PS:这里不再讲解这个眼睛放大 kernel 的实现原理。 我强烈建议你在了解了前面的内容后,自己试着解读这个 kernel。 另外,这里还有几个需要说明的地方。

Define Outp Image Domain of Definition as Union of Input Sampler DODs:输入输出图片的 DOD 一致。

  • Show Advanced Input Sampler Options:显示更多选项。

  • Edit Filter Function:编辑 Filter 函数。

    一般选中第一项就好。 如果有特殊需求,需要自定义 DOD,ROI,则选择 Edit Filter Function,进入编辑模式。

    function __image main(__image image, __vec2 centerPostion, __number radius, __number scaleRatio, __number aspectRatio) {
        return coreImageKernel.apply(image.definition, null, image, centerPostion, radius, scaleRatio, aspectRatio);
    }

    这样就可以对默认的 function 进行编辑。在这个 Demo 里面我们不需要,感兴趣可以自己实践下,很简单。

当然,放大眼睛这里需要定位到眼睛的位置,是否可以通过鼠标操作来获取点呢?再或者,眼睛放大效果不够直观,有没有办法鼠标按下显示效果图,松开显示原图呢?在 QC 里头,这些都不是问题~不过工具类的使用,更多的还是得靠自己去摸索,这里不再累述。可以参考 EnlargeEyes.qtz 文件,了解更多的操作。

Previous深拷贝与浅拷贝NextiOS11适配

Last updated 7 years ago

至于 kernel 中可以使用的函数,那就太多了。这里不一一枚举,在下面的具体讲解中,会说明几个常用的。如果想了解更多,可以参考 ,以及

PS:建议阅读之前,下载 配合着看。

它的效果如下所示: 不难看出,Vignette 滤镜,它实际上就是一个FOV(Field of View) 的效果,即视野中央看的最清楚,清晰程度与到中心距离呈反比,与人类的视觉是类似的。 所以针对图片上的每个像素点 A,经过 Vignette 滤镜处理后得到的 B,应该满足:

同样的,在 CIWarpKernel 中,无法使用 sample(), sampleCoord() 以及 samplerTransform() 。 下面以一个马赛克,像素化(Pixellate)的例子来讲解。它的效果如下: 马赛克,比较简单的一种算法是按照固定的间隔取像素点,将图片分割成一些小块,然后每个小块内选择一个像素点,然后把这个区域全部用这个像素点填充即可。这里的每个小块,称作晶格,晶格越大,马赛克效果越好。

只处理当前处理点位置,无法获取到其它点的状态。 只能传入一张图片。 *比如说,美图秀秀里面的一些简单马赛克,效果如下: 它的实现方式,我们可以简单的这么理解:

至于为什么要执行 Premultiplied Alpha 操作,具体的可以参考这篇文章:?

比如一个反相滤镜, 它对应的 kernel 应该是这样的:

下面是官方 Session 中翻转对应的 kernel 脚本,这里用到了 input 关键字,导致整个 kernel 错误 3. GLSL CIKL 是 GLSL 的子集,所以不是 GLSL 中定义的任何东西在 CIKL 中都适用。但是 glsl 中大多数关键字都是可以用的。另外,CIKL 还提供了 glsl 不支持的,额外的数据类型,关键字,方法,来完善 CIKernel。 4. Array, Mat In addition, the following are not implemented:

1,如下: 它的结果如下:

如果觉得这样看比较杂乱,可以试试添加 B 类辅助功能。 比如:CI_PRINT_TREE = 8 graphviz ,这样就可以导出 DOT 语言脚本。然后使用 工具,即可绘制这个 DOT 语言脚本描述的图形。

比如上面 Log 对应绘制得到的图形如下:

Quartz Composer 是一款图形化的编程工具,专门用来生成各种动态视觉效果,包括可交互的界面原型。当然,它也支持 Core Image 滤镜图表的原型。 另外,在 QC 上编写 Kernel,除了代码高亮,实时调整效果也很棒。

参数区: 这里设置各个 Patch 需要的输入参数。(通过点击主面板工具栏上的 Parameters 打开)

Viewer: 显示窗口,这里可以对 Layer 做处理,也可以响应用户操作。比如鼠标点击,移动,滑动等。 2. Filter 编辑 & 放大眼睛实战 首先,点击 Patch Library,添加一个 Core Image Filter。 选中这个 Filter,点击 Patch Inspector,选择 Settings,进入编辑页面。

这个时候,主面板应该长这样: 然后拖拽一张图片到主面板中,把图片的 Output Image 与 Filter 的 Input Image 想连接。

再从 Patch Library 中选择 Billboard。把 Filter 的 Output Image 与 Billboard 的 Input Image 相连接。 然后选中 Filter,打开 Parameters 面板,输入参数值,即可。

最终的效果应该是这样的:

Core Image Kernel Language Reference
OpenGL ES Shading Language Reference
源码
Graphviz
为什么要PREMULTIPLIED ALPHA呢