在NSImage上绘制文本性能低下,如何进行优化?
让我们一步步拆解这个场景的优化方案,既解决预览时的UI卡顿问题,又保证最终全分辨率输出的效果:
1. 分离预览与全分辨率处理逻辑
核心思路是预览用缩小图,全分辨率处理只在需要导出/保存时执行——毕竟用户输入文本时只需要看到实时预览,不需要立刻生成大尺寸的全分辨率图。
首先添加一个生成预览图的方法,把原图缩小到和预览视图一致的尺寸:
func generatePreviewImage(from originalImage: NSImage) -> NSImage { let targetSize = self.img_view.bounds.size let previewImage = NSImage(size: targetSize) previewImage.lockFocus() originalImage.draw(in: CGRect(origin: .zero, size: targetSize), from: .zero, operation: .copy, fraction: 1.0) previewImage.unlockFocus() return previewImage }
然后修改后台任务逻辑,预览时只处理缩小后的图,全分辨率操作留到导出时执行:
// 用户输入时仅更新预览 private func updatePreview() { let group = DispatchGroup() group.enter() self.progress_preview.isHidden = false self.progress_preview.startAnimation(self) DispatchQueue.global(qos: .userInitiated).async { [weak self] in guard let self = self, let originalImage = NSImage(byReferencingFile: self.selectedfilename) else { group.leave() return } // 用缩小的预览图做文本绘制 let previewImage = self.generatePreviewImage(from: originalImage) self.globaimage = self.drawText(image: previewImage) group.leave() } group.notify(queue: .main) { [weak self] in guard let self = self else { return } self.img_view.image = self.globaimage self.progress_preview.isHidden = true self.progress_preview.stopAnimation(self) } } // 当用户需要导出/保存时,再用原图生成全分辨率版本 func exportFullResolutionImage() { DispatchQueue.global(qos: .background).async { [weak self] in guard let self = self, let originalImage = NSImage(byReferencingFile: self.selectedfilename) else { return } let fullResImage = self.drawText(image: originalImage) // 执行保存/导出逻辑(比如写入文件) DispatchQueue.main.async { // 更新UI提示导出完成 } } }
2. 优化
drawText方法的性能 你的drawText里有不少可以复用和简化的地方,减少不必要的重复计算:
// 把字体属性缓存为类属性,避免每次调用都重复初始化 private lazy var textFontAttributes: [NSAttributedString.Key: Any] = { let defaultFont = NSFont.systemFont(ofSize: 50) let targetFont = NSFont(name: combo_font.stringValue, size: 50) ?? defaultFont let textStyle = NSMutableParagraphStyle() textStyle.alignment = .center // 直接设置居中,简化文本位置计算 return [ .font: targetFont, .foregroundColor: NSColor.white, .paragraphStyle: textStyle ] }() func drawText(image: NSImage) -> NSImage { let text = self.text let outputImage = NSImage(size: image.size) outputImage.lockFocus() // 先绘制原图 image.draw(in: CGRect(origin: .zero, size: image.size)) // 计算文本位置(利用居中样式,简化计算) let textSize = (text as NSString).size(withAttributes: textFontAttributes) let textRect = CGRect( x: (image.size.width - textSize.width) / 2, y: (image.size.height - textSize.height) / 2, width: textSize.width, height: textSize.height ) // 绘制文本 text.draw(in: textRect, withAttributes: textFontAttributes) outputImage.unlockFocus() return outputImage }
3. 避免频繁触发后台任务
用户输入文本时,每打一个字符就触发一次后台任务会造成大量冗余计算——可以用**防抖(Debounce)**延迟执行,等用户停止输入200-300毫秒再启动任务:
// 添加防抖定时器属性 private var debounceTimer: Timer? override func controlTextDidChange(_ obj: Notification) { guard let textField = obj.object as? NSTextField, textField.identifier?.rawValue == "txt_field" else { return } self.text = textField.stringValue // 取消之前的定时器,避免重复触发 debounceTimer?.invalidate() // 延迟300毫秒执行预览更新 debounceTimer = Timer.scheduledTimer(withTimeInterval: 0.3, repeats: false) { [weak self] _ in self?.updatePreview() } }
4. 额外优化点
- 缓存原图:把
NSImage(byReferencingFile: self.selectedfilename)的结果缓存为类属性,避免每次都从磁盘读取图片,尤其是用户频繁修改文本时。 - 字体 fallback:给
NSFont(name:size:)添加默认字体,避免字体不存在时返回nil导致无文本显示。 - Core Text 绘制:如果文本绘制还是性能瓶颈,可以尝试用Core Text直接绘制,比AppKit的
NSString.draw方法更底层高效。
内容的提问来源于stack exchange,提问作者techno
相关产品推荐
相关产品推荐

