如何高效在NSView中绘制带Core Image滤镜的CIImage?解决拖动卡顿
我之前帮不少开发者解决过类似的Core Image性能卡顿问题,你的情况大概率是没踩对几个关键的优化点。针对拖动滑块时的滞后问题,给你几个实用的最佳实践:
复用全局的CIContext,别每次drawRect都创建
CIContext的初始化成本极高,如果每次绘制都新建一个,性能肯定会崩。一定要提前创建一个全局的、开启硬件加速的CIContext,比如:lazy var ciContext: CIContext = { let options: [CIContextOption: Any] = [ .useSoftwareRenderer: false, // 强制启用GPU硬件加速 .cacheIntermediates: true, // 缓存中间渲染结果 .highQualityDownsample: true ] return CIContext(options: options) }()这个实例要在View初始化时就创建好,全程复用。
提前创建并复用透视滤镜实例
每次在drawRect里调用CIFilter(name:)创建滤镜也会产生额外开销,建议把滤镜实例作为全局属性提前初始化,每次只更新参数:lazy var perspectiveFilter: CIFilter = { guard let filter = CIFilter(name: "CIPerspectiveTransform") else { fatalError("无法创建透视滤镜") } return filter }() // 在drawRect或参数更新方法里只修改参数 perspectiveFilter.setValue(self.mySourceImage, forKey: kCIInputImageKey) perspectiveFilter.setValue(CIVector(x: topLeftX, y: topLeftY), forKey: "inputTopLeft") // 依次设置inputTopRight、inputBottomLeft、inputBottomRight参数限制处理的像素量
如果你的源图像尺寸远大于NSView的显示尺寸,先把源图像缩放到和View匹配的大小再应用透视滤镜——处理1080p的图和4K图的性能差异天差地别。可以用CILanczosScaleTransform滤镜提前做高质量缩放:func scaledSourceImage(to size: CGSize) -> CIImage? { guard let source = self.mySourceImage else { return nil } let scaleFilter = CIFilter(name: "CILanczosScaleTransform")! scaleFilter.setValue(source, forKey: kCIInputImageKey) scaleFilter.setValue(size.width / source.extent.width, forKey: kCIInputScaleKey) return scaleFilter.outputImage }用CALayer替代drawRect手动绘制
自定义NSView的drawRect是CPU驱动的绘制流程,换成Core Animation的CALayer会更高效。你可以把View设置为支持图层,然后直接把渲染后的CGImage赋值给layer的contents:// 在View初始化时设置 override init(frame frameRect: NSRect) { super.init(frame: frameRect) self.wantsLayer = true } // 当滑块值变化时更新图层内容(不用在drawRect里处理) func updatePerspectiveWithSliderValues() { // 更新滤镜参数... guard let outputImage = perspectiveFilter.outputImage, let cgImage = ciContext.createCGImage(outputImage, from: outputImage.extent) else { return } self.layer?.contents = cgImage }这样Core Animation会帮你处理渲染优化,比如只更新变化的区域,卡顿感会明显降低。
利用dirtyRect减少重绘区域
如果坚持用drawRect,一定要利用方法传入的dirtyRect参数,只重绘需要更新的区域,避免每次都渲染整个View:override func draw(_ dirtyRect: NSRect) { super.draw(dirtyRect) NSRectClip(dirtyRect) // 只在脏区域内绘制 // 后续的渲染绘制逻辑... }
这些优化点组合起来,应该能让滑块拖动时的渲染流畅很多,毕竟Core Image在GPU上跑的话,透视变换这种操作本来就应该是很流畅的。
内容的提问来源于stack exchange,提问作者wcochran

