SwiftUICoreImage

帮助在 SwiftUI 环境中使用 Core Image。 即使不使用 SwiftUI 也很有用。

简介

Core Image 是 macOS 和 iOS 中一个很棒的图像处理工具包,但使用起来有点笨拙。 即使在 Apple 为许多滤镜添加了 Swift API (CoreImage.CIFilterBuiltins) 之后,将滤镜链接到图像仍然很繁琐。

此软件包的目的是提供一种更简单的方法,将多个滤镜链接到 CIImage 实例,然后将它们渲染到 SwiftUI(或任何其他环境 - 不需要 SwiftUI)。

    Image(ciImage: CIImage("Bernie.jpeg")
        .sepiaTone(intensity: sepia)
        .recropping { image in
            image
                .clampedToExtent(active: clamped)
                .gaussianBlur(radius: gaussianBlurRadius)
        }
    )
        .resizable()
        .aspectRatio(contentMode: .fit)

清单

此软件包包含

工作原理

与 SwiftUI 视图修饰符每个都返回修改后的 View 实例类似,CIImage 上的这些修饰符通过创建相应的 CIFilter、为您连接 inputImage 并返回生成的 outputImage 来处理核心图像链接。

在创建 SwiftUI 代码时,我认为重要的是您可以使用 惰性修饰符,您可以在其中传入一些导致修饰符不起作用的参数。(例如,将视图的不透明度指定为 1.0 或填充指定为 0.0。)

在此代码中,我确保我们的每个图像修饰符都带有惰性修饰符:在某些情况下,它是传入一个显然不起作用的参数(例如,零强度、零半径);或者是在与另一个图像组合时使用 nil 背景图像;或布尔值 active 参数。 如果指定的参数不会导致图像发生任何变化,则会立即返回标识(self)。

CIImage-Filters.swift 的内容是使用我包含在此存储库中的代码生成的源代码(CIImage-Generation.swift,未包含在包导入中)。 这会循环遍历 Apple 提供的核心图像元数据 (CIFilter.filterNames(inCategories: nil))。 不幸的是,此列表有点过时,并且包含许多不一致之处,我已经尽力克服。 有一些 JSON 文件提供了额外的元数据,例如实际具有在线文档的函数列表 - 56 个函数没有文档,因此需要进行一些猜测 - 或修复丢失或过时的文档。 除非您有一些特殊要求或该列表在未来的操作系统版本中已更新,否则您可能不需要运行此代码。

与 SwiftUI 一起使用

请记住,Core Image 操作实际上只是处理步骤的“配方”; 实际工作直到图像需要渲染为位图时才会执行。

此代码提供了一个新的初始化器,用于从 CIImage 创建一个 Image,而不是使用 内置初始化器从资源名称或其他图像类型(CGImageNSImageUIImage)创建 SwiftUI Image。 当 SwiftUI 需要渲染图像时,Core Image 会渲染到屏幕。

然后,您的典型方法是创建一个 Image,传入使用 内置初始化器之一或此处包含的便捷方法创建的 CIImage,以便从资源名称或其他图像类型创建。

然后,只需将修饰符链接到该 CIImage 以指示要修改的内容。

许多修饰符都很简单。 例如

    Image(ciImage: CIImage("Halloween.jpeg")
        .xRay()
    )

如果您希望切换是否应用滤镜,请使用 active 参数(默认值为 true

    Image(ciImage: CIImage("Halloween.jpeg")
        .xRay(active: isMachineOn)
    )

链接在 CIImage-Filters.swift 中找到的任意数量的修饰符以构建所需的结果。

图像缩放

许多 Core Image 滤镜使用像素值作为参数。 因此,可能需要先将图像缩放到适当的大小应用操作。 例如,将 10 像素半径的模糊应用于 6000⨉4000 的图像,然后将其缩小到 300⨉200 可能无法产生您想要的结果; 也许您想先将图像缩放到 300⨉200,然后再应用 10 像素半径的模糊。

Core Image 提供了一个缩放操作 (CILanczosScaleTransformlanczosScaleTransform()),但此软件包还包括更方便的替代方案:scaledToFill()scaledToFit(),您可以在其中传入所需的尺寸。

这种典型的用法与 GeometryReader 结合使用效果很好。 例如

    GeometryReader { geo in
        let geoSize: CGSize = geo.frame(in: .local).integral.size
        // Resize image to double the frame size, assuming we are on a retina display
        let newSize: CGSize = CGSize(width: geoSize.width * 2,
                                    height: geoSize.height * 2)

        Image(ciImage: CIImage("M83.jpeg")
            .scaledToFit(newSize)
            .sharpenLuminance(sharpness: 1.0, radius: 5)
        )
            .resizable()    // Make sure retina image is scaled to fit
            .aspectRatio(contentMode: .fit)
    }

Compare original, sharpened without pre-scaling, sharpened after pre-scaled

不使用 SwiftUI

完全不需要 SwiftUI。 只需创建一个 CIImage 并执行操作。 然后,渲染到位图。

    let tiledImage: CIImage = CIImage("HeyGoodMorning.png").
        .triangleTile(center: .zero, angle: 0.356, width: 2.0)

    imageView.image = UIImage(CIImage: tiledImage)

其他说明

如果您使用过 Core Image,您就会知道有时需要操作图像的范围,例如在应用高斯模糊之前将图像钳制为具有无限边缘,然后重新裁剪到图像的原始范围。 为此,您可以使用 recropping 修饰符,后跟一个闭包。 该操作保存图像的范围,应用闭包中的任何内容,然后重新裁剪到该范围。 在下面的示例中,ciImage 中的图像被转换为像素颜色沿其边缘在所有方向上无限延伸的图像,然后对其进行模糊处理,然后在退出闭包时,重新裁剪返回的图像。

    ciImage
        .recropping { image in
            image
                .clampedToExtent()
                .gaussianBlur(radius: 10)
        }

Compare unblurred, improper blurring, and proper blurring

如果您发现滤镜(例如 comicEffect())略微增加了图像的范围并且您想将其钳制到其原始大小,则 recropping 修饰符也很有用。

另一个有用的操作是 replacing。 非常类似于 recropping,不同之处在于它不会影响图像的范围。 您传入一个闭包,该闭包以您正在使用的图像开始; 您的闭包返回一个新图像。 这在使用 Core Image 中的合成操作时非常有用,这些操作需要传入背景图像。 如果您的操作链在背景图像上,并且您想在顶部叠加一些东西怎么办? 只需将您的操作包装在 .replacing 中并返回合成图像。

    ciImage
        .replacing { backgroundImage in
            ciImage2
                .sourceAtopCompositing(backgroundImage: backgroundImage)
        }

在这种情况下,ciImage2 中的图像是前景图像,放置在 backgroundImage 之上,然后返回到操作链。

使用包

在 Xcode 中,选择“文件”>“添加包…”然后在搜索栏中输入此存储库的 URL,然后从那里继续。

在您的代码中

import SwiftUICoreImage

就是这样!


未来改进

与其生成重复的代码,不如定义一些扩展为重复代码的宏! 这样做的好处是,您可以只导入宏包,然后只定义您想要的过滤器,而不是定义所有 200 多个主要未使用的过滤器。

显然 这需要 函数体宏,Swift 5.x 中没有这些宏,但可能会进入 Swift 6.0。

理想情况下,我们会指定如下内容

 	@CoreImageExtension
	func pixellate(center: CGPoint, scale: Float, active: Bool = true) -> CIImage

这会将主体填充为执行以下操作的一些代码


如果您能想到代码或生成的滤镜文档的改进,或者发现任何其他有用的实用程序来操作此工具包中的 Core Image,请提出问题或提交拉取请求!