帮助在 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
(如果未修改,则返回原始图像)CIImage
NSImage
/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,请提出问题或提交拉取请求!