我需要制作一个快速更新 UIImage 的功能,一开始使用 UIImageView 来显示图片,所以需要频繁的调用 UIImageView.image = newImage 方法来更新图片。代码看起来像这样。

func onSomethingChanged() {
    myUIImageView.image = newImage
}

这样更新图片非常的卡顿,操作和画面响应有一定的滞后,体验不好,因此考虑自己做一个 UIView 来绘制图片,看看是否可以解决。

于是自己自定义了一个 View,继承 UIView 来做,在 draw(rect:) 方法里面做文章。

首先当然是简单粗暴直接 UIImage.draw(in: bounds) 先看看效果。

var image: UIImage? {
    didSet {
        setNeedsDisplay()
    }
}
    
override func draw(_ rect: CGRect) {
    guard let ctx = UIGraphicsGetCurrentContext() else { return }
    ctx.addRect(bounds)
    ctx.setFillColor(UIColor.black.cgColor)
    ctx.fillPath()

    if var im = image {
        im.draw(in: bounds)
    }
}

很好,更新速度很快,是理想的效果,不过目前画面是拉伸的,同时还有锯齿。

下图中左边是理想的显示效果,右边则是实际的显示效果,可以看到明显的锯齿。

image.png

经过我一番搜索尝试了以下配置,均无任何帮助:

ctx.setShouldAntialias(true)
ctx.setAllowsAntialiasing(true)
ctx.interpolationQuality = .high
layer.allowsEdgeAntialiasing = true
layer.minificationFilter = .trilinear

我回忆起之前用 CGImageContext 缩放图片的时候也没有这个问题啊,难道是因为 UIView 自带的这个 CGContext 无法很好的缩放图片?

于是我想到一个方案:先用一个 CGImageContext 把图片画上去,再弄出一个 CGImage 来,再把这个 CGImage 放到 UIView 的 CGContext 上是否可以呢?

override func draw(_ rect: CGRect) {
    guard let ctx = UIGraphicsGetCurrentContext() else { return }
    ctx.addRect(bounds)
    ctx.setFillColor(UIColor.black.cgColor)
    ctx.fillPath()

    if var im = image {
        // 计算出在当前view的bounds内,保持图片比例最大的size
        let size = Math.getMaxSizeWithAspect(size: CGSize(width: bounds.width, height: bounds.height), radioWidthToHeight: im.size.width / im.size.height)
        // 再换算成pixel size
        let pixelSize = CGSize(width: size.width * layer.contentsScale, height: size.height * layer.contentsScale)
        
        // 创建一个和 pixel size 一样大的 ImageContext
        UIGraphicsBeginImageContextWithOptions(pixelSize, true, 1)
        guard let imgCtx = UIGraphicsGetCurrentContext() else { return }
        
        // 把 UIImage 画到这个 ImageContext 上
        im.draw(in: CGRect(x: 0, y: 0, width: pixelSize.width, height: pixelSize.height))
        
        // 再把 cgImg 搞出来
        guard let cgImg = imgCtx.makeImage() else { return }
        
        // 图片直接绘制的话,会上下翻转,因此先翻转一下
        ctx.scaleBy(x: 1, y: -1)
        ctx.translateBy(x: 0, y: -bounds.height)
        
        // 再把cgImg 画到 UIView 的 Context 上,大功告成
        ctx.draw(cgImg, in: CGRect(x: (bounds.width - size.width) / 2, y: (bounds.height - size.height) / 2, width: size.width, height: size.height))
    }
}

问题就此得到解决。

其实我的本身需求是能快速的更新 image,因为是在做一块类似后期调照片的软件,有很多滑块,拖动后通过 CIFilter 修改照片,所以需要一直更新图像,如果有更好的方法,不妨告诉我一下:D

如果以上内容对你有所帮助,请在这些平台上关注我吧,谢谢:P


krosshj
152 声望16 粉丝

Developer, Gamer, Artist


引用和评论

0 条评论