Drawing Text on NSImage is Slow - How to improve performance?

I use the following code to draw a text on an NSImage


func drawText(image :NSImage) ->NSImage
    {
        
        let text = text
        let font = NSFont(name:String(combo_font.stringValue), size: 50)
        let imageRect = CGRect(x: 0, y: 0, width: image.size.width, height: image.size.height)
     
        let fontAttributes = [NSAttributedStringKey.font: font]

        let fontsize = (text as NSString).size(withAttributes: fontAttributes)

        let textRect = CGRect(x: (image.size.width/2-fontsize.width/2), y: image.size.height/2, width: fontsize.width, height: fontsize.height)

        
        
        let textStyle = NSMutableParagraphStyle.default.mutableCopy() as! NSMutableParagraphStyle
        let textFontAttributes = [
            NSAttributedStringKey.font: font,
            NSAttributedStringKey.foregroundColor: NSColor.white,
            NSAttributedStringKey.paragraphStyle: textStyle
        ]
        let im:NSImage = NSImage(size: image.size)
        let rep:NSBitmapImageRep = NSBitmapImageRep(bitmapDataPlanes: nil, pixelsWide: Int(image.size.width), pixelsHigh: Int(image.size.height), bitsPerSample: 8, samplesPerPixel: 4, hasAlpha: true, isPlanar: false, colorSpaceName: NSColorSpaceName.calibratedRGB, bytesPerRow: 0, bitsPerPixel: 0)!
        im.addRepresentation(rep)
        im.lockFocus()
        image.draw(in: imageRect)
        text.draw(in: textRect, withAttributes: textFontAttributes)
        im.unlockFocus()
        return im
    }

To prevent UI Freezing i do the long running operation in background thread


override func controlTextDidChange(_ obj: Notification) {
  if(obj.object is NSTextField)
  {
  let textdata=obj.object as! NSTextField
  if(txtfield.identifier?.rawValue=="txt_field")
  {


  self.textdata=self.txtbox.stringValue
  DispatchQueue.global().async {
  self.img_view.image=self.drawText(image: NSImage(byReferencingFile: self.selectedfilename)!);

  }

  }

  }
  }


When processing an Image of 391KB in size, the process takes too long to update the UI.How can i improve the performance.I just need to display the preview to the user,resizing image to a smaller size is also an option to improve the performance;But the same look should be achieved in full resolution image as well when processing later.

You should update in the main thread ; here you create a new thread each time. And UI must occur in main thread.


You could also simplify code:


override func controlTextDidChange(_ obj: Notification) {
    if let textdata = obj.object as! NSTextField {
        if txtfield.identifier?.rawValue == "txt_field"
        {
            DispatchQueue.main.async {
                self.textdata = self.txtbox.stringValue // To be done on main thread as well
                self.img_view.image = self.drawText(image: NSImage(byReferencingFile: self.selectedfilename)!);
            }
            
        }
    }
}


But a question: why do you call textDidChange ? Can user modify ?

Why not call at the end, once editing is finished ?


In drawText:

why do you need the conversion, which may be time consuming:

let rep : NSBitmapImageRep = NSBitmapImageRep(bitmapDataPlanes: nil, pixelsWide: Int(image.size.width), pixelsHigh: Int(image.size.height), bitsPerSample: 8, samplesPerPixel: 4, hasAlpha: true, isPlanar: false, colorSpaceName: NSColorSpaceName.calibratedRGB, bytesPerRow: 0, bitsPerPixel: 0)!

Note: you could make the signature of drawText more explicit, such as:

func drawText(over image :NSImage) ->NSImage


in order to call:


self.img_view.image = self.drawText(over: NSImage(byReferencingFile: self.selectedfilename)!);

Thanks for your response.


The user can modify the text and based on that the drawn string should be modified.

Can this be called when the editing of NSTextBox if finished? rather than for every character change?


I will modify the drawtext based on your comments and get back.

I am not sure what explains the slowliness.

You could use profiler, but I would suggest a simple start:


Test with different code to see when it slows down

I have a question: what is the type of text ? NSAttributedString ?


1. just simpler text


func drawText(image :NSImage) ->NSImage
    { 
        let text = text          // useless ?
        let textRect = CGRect(x: (image.size.width/2-fontsize.width/2), y: image.size.height/2, width: fontsize.width, height: fontsize.height)
        text.draw(in: textRect)
        return im
    }

2. Attributed text and basic image

func drawText(image :NSImage) ->NSImage
    {
        let text = text          // useless ?
        let font = NSFont(name:String(combo_font.stringValue), size: 50)
        let imageRect = CGRect(x: 0, y: 0, width: image.size.width, height: image.size.height)
  
        let fontAttributes = [NSAttributedStringKey.font: font]
        let fontsize = (text as NSString).size(withAttributes: fontAttributes)

        let textRect = CGRect(x: (image.size.width/2-fontsize.width/2), y: image.size.height/2, width: fontsize.width, height: fontsize.height)
     
        let textStyle = NSMutableParagraphStyle.default.mutableCopy() as! NSMutableParagraphStyle
        let textFontAttributes = [
            NSAttributedStringKey.font: font,
            NSAttributedStringKey.foregroundColor: NSColor.white,
            NSAttributedStringKey.paragraphStyle: textStyle
        ]
        image.draw(in: imageRect)
        text.draw(in: textRect, withAttributes: textFontAttributes)
        return im
    }

If this is not slow, the problem comes probably from the bitmap handling in NSBitmapImageRep (which would not be totally surprising for very large images)


3 If that's the case,

Why do you need this bitmap processing ?

you may do the drawing at the end of text change, not evrytime some text change

Removal of the conversion code has speed up things a little bit. The type of text is string,the assignation is useless.Will use the global variable.How can i hook to end of textchange?

Drawing Text on NSImage is Slow - How to improve performance?
 
 
Q