使用 CIFilter 实现 UIView 镂空效果

2021-11-17  本文已影响0人  JeremyTechBlog

在 iOS 中,并没有原生支持 UIView 镂空效果的接口。

目前网上能搜到的比较主流的一个方案是遍历每一个像素进行透明度反转,实现镂空效果:

一行代码实现 UIView 镂空效果

上述方案有一个非常明显的缺点,上述方案的作者本人也提出过:本质上是生成一个新的遮罩图的过程,该过程涉及图片像素的遍历转换,较为耗时,不宜频繁调用。

包括我自己也试过这个方案,在生成 size 较小的 view 时耗时较少,勉强可以接受。但在生成 size 较大的 view 时,比如做一个全屏的镂空遮罩,耗时就达到了几秒钟,到了一个无法接受的状态。

Core Image

Core Image 是 iOS5 新加入到 iOS 平台的一个图像处理框架,提供了强大高效的图像处理功能, 用来对基于像素的图像进行操作与分析, 内置了很多强大的滤镜(Filter) (目前数量超过了180种), 这些Filter 提供了各种各样的效果, 并且还可以通过 滤镜链 将各种效果的 Filter叠加 起来形成强大的自定义效果。

一个 滤镜 是一个对象,有很多输入和输出,并执行一些变换。例如,模糊滤镜可能需要输入图像和一个模糊半径来产生适当的模糊后的输出图像。

一个 滤镜链 是一个链接在一起的滤镜网络,使得一个滤镜的输出可以是另一个滤镜的输入。以这种方式,可以实现精心制作的效果。

iOS8 之后更是支持自定义 CIFilter,可以定制满足业务需求的复杂效果。

Core Image 的一个优势就是可以根据需求选择 CPU 或者 GPU 来处理。

Context 创建的时候默认就是是基于 GPU。

基于 GPU 的话,处理速度更快,因为利用了 GPU 硬件的并行优势。可以使用 OpenGLES 或者 Metal 来渲染图像,这种方式CPU完全没有负担,应用程序的运行循环不会受到图像渲染的影响。

实现方案

Core Image Filter Reference

在 CIFilter 的官方文档中,有一个 CIBlendWithAlphaMask 的滤镜。从文档示例中可以看到,该滤镜的效果就是:

在 inputBackgroundImage(兔子玩偶)中显示 inputMaskImage(Core Image 文字) 的 Mask 效果,并且透出 inputImage(墙)的图片。

通过上述例子,只需要把 inputImage 设置成一张透明的图片,即可实现 UIView 的镂空效果。

这里我们给 UIView 添加一个 extension:

extension UIView {
    var subtractMaskView: UIView? {
        set {
            mask = nil
            newValue?.frame = bounds
            let inputView = UIView(frame: bounds)
            inputView.alpha = 0

            let inputImage = inputView.convertedImage
            let inputMaskImage = newValue?.convertedImage
            let inputBackgroundImage = convertedImage

            let filter = CIFilter(name: "CIBlendWithAlphaMask")
            filter?.setValue(inputImage?.convertedCIImage, forKey: "inputImage")
            filter?.setValue(inputBackgroundImage?.convertedCIImage, forKey: "inputBackgroundImage")
            filter?.setValue(inputMaskImage?.convertedCIImage, forKey: "inputMaskImage")
            guard let outputImage = filter?.outputImage else { return }

            let imageView = UIImageView(frame: bounds)
            imageView.image = UIImage(ciImage: outputImage)
            mask = imageView
        }
        get {
            return mask
        }
    }

    var convertedImage: UIImage? {
        get {
            UIGraphicsBeginImageContextWithOptions(bounds.size, false, UIScreen.main.scale)
            defer { UIGraphicsEndImageContext() }
            guard let ctx = UIGraphicsGetCurrentContext() else { return nil }
            layer.render(in: ctx)
            return UIGraphicsGetImageFromCurrentImageContext()
        }
    }
}

使用只需要一行代码即可:

testView.subtractMaskView = label

效果图:

这样就实现了镂空效果,生成大图也基本上不会卡住主线程,并且可以很频繁地调用。

版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-ND 4.0 许可协议。转载请注明出处

上一篇 下一篇

猜你喜欢

热点阅读