帮助在 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)
此软件包包含
CIImage 的修饰符,它们返回一个新的修改后的 CIImage(如果未修改,则返回原始图像)CIImageNSImage/UIImage 初始化 CIImage 的便捷初始化器CIImage 的修饰符,用于返回裁剪、缩放等,以便更轻松地与 SwiftUI 一起使用CIImage 修饰符函数,这些函数采用 active 布尔参数CIImage 创建 SwiftUI Image与 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 个函数没有文档,因此需要进行一些猜测 - 或修复丢失或过时的文档。 除非您有一些特殊要求或该列表在未来的操作系统版本中已更新,否则您可能不需要运行此代码。
请记住,Core Image 操作实际上只是处理步骤的“配方”; 实际工作直到图像需要渲染为位图时才会执行。
此代码提供了一个新的初始化器,用于从 CIImage 创建一个 Image,而不是使用 内置初始化器从资源名称或其他图像类型(CGImage、NSImage、UIImage)创建 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 提供了一个缩放操作 (CILanczosScaleTransform 和 lanczosScaleTransform()),但此软件包还包括更方便的替代方案: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)
}
完全不需要 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)
}
如果您发现滤镜(例如 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,请提出问题或提交拉取请求!