使用Core Image Kernel Language的自定义过滤器
如果您曾经玩过Core Image的过滤器API,您可能会想知道“制作过滤器并启动我自己的Snapchat需要什么!”。 有多种方法可以构建和链接核心图像过滤器以创建自定义过滤器。 但是,与自己编写相比,这有点昂贵。 当我第一次进行此旅程时,我发现API文档的许多问题仅存在于Objective-C和台式机中。 事实证明,将其转移到iOS和Swift上确实是一项艰巨的任务。
创建我们的自定义过滤器
让我们深入探讨一下Core Image Kernel文件。 在本文中,我们将创建一个CI彩色内核,该组件在名为HazeRemove.cikernel.
的文件中应用雾霾滤镜HazeRemove.cikernel.
那么这是怎么回事? 我们将在每一行中对其进行分解。 但是,请务必注意此逐像素更改。 我们一次在单个像素上运行此代码,然后返回修改后的像素。 对于语法,Core Image Kernel Language位于OpenGL Shading Language的顶部,因此它将具有与Objective-C的Swift不同的规则。
kernel vec4 HazeRemovalKernel(
我们指定的第一行是内核例程,因此系统知道将其交给CIKernel类执行。我们指定vec4
的返回类型,因为Core Image要求我们返回此类型以进行更改输入像素正确地适合您的输出像素。
sampler src, __color color, float distance, float slope)
在我们的HazeRemovalKernel
函数中,我们传入一个CISampler
对象,该对象被视为源像素。 __color
是一种与CIContext
的色彩空间匹配的颜色,如果用户启用了“ True Tone”或“ Night Swift”,则可以帮助我们使该颜色保持预期的外观。 同样对于我们的过滤器,我们以斜率和距离作为浮点。 这些只是影响典型除雾算法的过滤器参数的方式。
接下来,我们定义一些将在例程中使用和修改的变量。 首先是我们修改后的像素; 我们将此作为vec4
。 在OpenGL中, vec4
是向量类型,具有4个单精度浮点数分量。 因此,在我们的情况下,它保持RGBA值。 接下来,我们定义一个浮点数,用于保存除雾算法的计算值。
d = destCoord().y * slope + distance;
为了弄清楚我们要失真的量,我们使用一种简单的算法,将斜率添加到距离中。 为了获得考虑整个图像的斜率,我们采用传入的斜率值,并乘以destCoord().y
。 destCoord
返回像素在当前工作空间中的位置,这是创建坡度的良好基础。
t = unpremultiply(sample(src, samplerCoord(src)));
我们需要做的下一件事是考虑到可能对图像应用了某些透明度的事实。 因此,在进行颜色校正之前,我们要删除Alpha以获得纯色。 为此,我们使用一种称为unpremultiply
的方法。 这采用vec4
颜色,因此要获得此颜色,我们使用一种称为sample的方法,该方法返回包含给定像素颜色的vec4
。 为了帮助sample完成其工作,我们传入了sampler类型的src
变量。 另外,包含该像素坐标的vec2
。 我们通过调用samplerCoord(src)
获得vec2
,该方法使用sampler变量并为我们找到其坐标。
所有这些完成后,我们现在有了两个设置变量。 d
是失真, t
是我们尝试更改的像素的纯色值。
t = (t - d*color) / (1.0-d);
现在,让我们创造那种阴霾! 雾霾对我们而言是一个非常简单的计算。 t
和color
都是vect4类型,因此它们很容易相互减去。
return premultiply(t);
移除了新的雾度像素后,如果需要,我们需要重新应用该透明度并返回结果。 我们可以通过使用premultiply
并返回生成的vect4
来实现。
构建我们的核心图像过滤器
太好了,我们有CI内核文件,但是要使用它,我们需要将其包装到CI过滤器中,以便可以从我们的应用中调用它。 现在警告我们正在使用C级API,正因为如此,在编写本文时,这是Swift的C知识,并且需要Objective-C互操作性。
首先,我们将通过导入诸如CoreImage之类的要素并创建一个继承自CIFilter的新类来设置过滤器。 然后,我们将定义我们要传递到Filter中的变量,并为所有除inputImage之外的值提供默认值。
下一部分有点棘手。 因为我们定义了一些自定义输入(即距离和坡度),所以我们需要重写CIFilters属性,以便它知道它们。 通过覆盖属性,我们还必须定义CIFilter已经拥有的属性,即显示名称,图像和颜色。 我不会涉及所有细节,主要要知道的是我们正在做的是设置映射,以便C级API知道如何解释Objective-C对象,以及是否有最小最大信息,默认值等我们也希望它坚持下去。 Apple提供了许多有关CIFilter中的“过滤器属性键”的文档。
现在让我们加载该过滤器! 但是,让我们对此保持懒惰。 这样,直到我们知道需要首次使用它时,我们才加载文件。 同样,我们制作了一个CI颜色内核,因此我们确保滤镜的类型相同。 要加载CI Color Kernel,您需要将其源代码作为字符串传递给CIColorKernel
,因此我们只需使用bundle
加载文件并检查以确保它已作为字符串加载。 在生产应用程序中,我们可能不希望出现致命错误,但是出于我们的目的,它可以正常工作。
终于开始生意了。 为了实际过滤图像,我们将覆盖称为outputImage
的计算方法。 在这里,我们可以使用hazeRemovalKernel
并调用CIColorKernel方法,将其与输入图像和参数一起应用,并返回新的过滤输出。
注册我们的过滤器
为了使我们的新闪亮过滤器可用于Core Image客户端,我们需要创建一个实现CIFilterConstructor
协议的供应商。
https://gist.github.com/9f6b18347f6a71703995fdd4f4e6d67b
我们将使用真棒名称CusomFilterVendor
设置过滤器,因为这是一个Objective-C协议,我们必须继承NSObject
。 Swift互操作性的新手? 我们在高级iOS Bootcamp中教它!
我不太喜欢使用字符串输入名称。 因此,让我们用过滤器名称定义一个公共静态变量,这样我们就不会偶然拼错任何东西。
接下来,我们将以非常简单的方式注册过滤器。 如果您有更多的过滤器,则可以通过在其自己的过滤器对象上调用registerName
来将它们放入此处。 这是一种告诉Core Image的方法,该过滤器是否被称为由哪个供应商负责。
最后,如果Core Image告诉我们的供应商,其中一个过滤器已被调用,它将使用过滤器名称调用其过滤器方法,以确定应该返回什么过滤器。 在这里,我们假设您将非常喜欢编写过滤器,因此您将想启动我们自己的Snapchat。 为此,我们设置了一个开关,以便您可以轻松地继续添加更多内容。
使用我们的自定义过滤器
对于使用自定义过滤器,我想以这样的方式扩展CIImage:我们已经可以访问根映像了,它就像在过滤器处理的任何CIImage上调用方法一样简单。
首先,我想定义一个计划使用的过滤器的枚举器,在这里我们可以传入诸如坡度和距离之类的参数,但是在我的实现中,我对我们之前定义的默认值感到满意。
在这里,我们设置一个过滤函数,该函数采用我们定义的Enum类型,并通过switch语句找到相应的过滤器。 我们称其为我们设置了要传递的参数,因为我们使用的是默认值,即传递self
的ImageKey。 此外,请确保将我们的filterName设置为我们在CustomFiltersVendor中定义的名称。
设置好filterName和参数后,我们将调用CIFilter,其余的工作将由我们完成。 假设没有任何问题并且我们没有抛出错误,我们通过在新的过滤器对象上调用outputImage
来获得输出。 一些过滤器可能会在原始边界之外编辑输出图像,因此我们定义了shouldCrop
属性,因此,如果我们拥有这些过滤器之一,则可以将图像裁剪为原始大小。 扩展图像不是我们的过滤器执行的操作,而是以CIGaussianBlur
为例进行说明。
毕竟,过滤器完成了! 您可以从任何CIImage调用我们的新过滤器,就像运行let newImage = image.filtered(.removeHaze)
一样简单。
我希望您喜欢在Swift for iOS中实现的自定义过滤器隧道。
如果要了解有关编写着色器和制作更复杂的滤镜的更多信息,请查看Pearson Education发布的OpenGL Shading Language。 Apple的文档中还有其他种类的着色器,您可以在CI滤色器之外创建。