cocoaimageannotationspyobjcruby-cocoa

Error drawing text on NSImage in PyObjC


I'm trying to overlay an image with some text using PyObjC, while striving to answer my question, "Annotate images using tools built into OS X". By referencing CocoaMagic, a RubyObjC replacement for RMagick, I've come up with this:

#!/usr/bin/env python

from AppKit import *

source_image = "/Library/Desktop Pictures/Nature/Aurora.jpg"
final_image = "/Library/Desktop Pictures/.loginwindow.jpg"
font_name = "Arial"
font_size = 76
message = "My Message Here"

app = NSApplication.sharedApplication()  # remove some warnings

# read in an image
image = NSImage.alloc().initWithContentsOfFile_(source_image)
image.lockFocus()

# prepare some text attributes
text_attributes = NSMutableDictionary.alloc().init()
font = NSFont.fontWithName_size_(font_name, font_size)
text_attributes.setObject_forKey_(font, NSFontAttributeName)
text_attributes.setObject_forKey_(NSColor.blackColor, NSForegroundColorAttributeName)

# output our message
message_string = NSString.stringWithString_(message)
size = message_string.sizeWithAttributes_(text_attributes)
point = NSMakePoint(400, 400)
message_string.drawAtPoint_withAttributes_(point, text_attributes)

# write the file
image.unlockFocus()
bits = NSBitmapImageRep.alloc().initWithData_(image.TIFFRepresentation)
data = bits.representationUsingType_properties_(NSJPGFileType, nil)
data.writeToFile_atomically_(final_image, false)

When I run it, I get this:

Traceback (most recent call last):
  File "/Users/clinton/Work/Problems/TellAtAGlance/ObviouslyTouched.py", line 24, in <module>
    message_string.drawAtPoint_withAttributes_(point, text_attributes)
ValueError: NSInvalidArgumentException - Class OC_PythonObject: no such selector: set

Looking in the docs for drawAtPoint:withAttributes:, it says, "You should only invoke this method when an NSView has focus." NSImage is not a subclass of NSView, but I would hope this would work, and something very similar seems to work in the Ruby example.

What do I need to change to make this work?


I rewrote the code, converting it faithfully, line for line, into an Objective-C Foundation tool. It works, without problems. [I would be happy to post if here if there is a reason to do so.]

The question then becomes, how does:

[message_string drawAtPoint:point withAttributes:text_attributes];

differ from

message_string.drawAtPoint_withAttributes_(point, text_attributes)

? Is there a way to tell which "OC_PythonObject" is raising the NSInvalidArgumentException?


Solution

  • Here are the problems in the above code:

    text_attributes.setObject_forKey_(NSColor.blackColor, NSForegroundColorAttributeName)
    ->
    text_attributes.setObject_forKey_(NSColor.blackColor(), NSForegroundColorAttributeName)
    
    bits = NSBitmapImageRep.alloc().initWithData_(image.TIFFRepresentation)
    data = bits.representationUsingType_properties_(NSJPGFileType, nil)
    ->
    bits = NSBitmapImageRep.imageRepWithData_(image.TIFFRepresentation())
    data = bits.representationUsingType_properties_(NSJPEGFileType, None)
    

    Minor typos indeed.

    Note that the middle portion of the code can be replaced with this more-readable variant:

    # prepare some text attributes
    text_attributes = { 
        NSFontAttributeName : NSFont.fontWithName_size_(font_name, font_size),
        NSForegroundColorAttributeName : NSColor.blackColor() 
    }
    
    # output our message 
    NSString.drawAtPoint_withAttributes_(message, (400, 400), text_attributes)
    

    I learned this by looking at the source code to NodeBox, the twelve lines of psyphography.py and cocoa.py, particularly the save and _getImageData methods.