You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.07 10:42:33