Wednesday, March 3, 2010

Photobomb Featured App for Opp. Dev. Week

Jono asked me to do an Opportunistic Developer Week session "Application Showcase: Photobomb".

So I prepared a little retrospective of Photobomb development to walk through in the session, taking questions as I go. Fortunately I was blogging daily as I added features, so it was easy to pull out images to jog my memory.

This posting is the notes for the session, which is later today.
Inspiration


I wrote the first iteration of Photobomb one Saturday afternoon. I used Quickly to get the project started, so it was just a matter of laying out some UI in Glade, and then figuring out how to manipulate images. I used Python Imaging Library (PIL) to create a cropped version of that little squirrel, and to paste it onto the picture. PIL is a complete and power library. It was a bit tough figuring out the masking to crop the squirrel image as desired, but I eventually managed.
Creating a File Dialog Widget

It turned out to be tedious to write code to open an image file. Also, a lot of copy and pasting. So I added a prompt to quickly.widgets to handle opening files. Having your own library of reusable widgets is quite a luxury. Note that I didn't know the gtk.FileChooser API that well, so made a couple of early mistakes, such as handling the existing file replace functionality myself. Didn't realize that the API could handle it.

Switch from PIL to GooCanvas


When I tried to implement some captioning and other features, I came to realize that PIL was just not designed for the kind of stuff I wanted to do with it. When I switched to GooCanvas, it became much easier to add features.

I was able to add resizing, rotation, and drag to move in a matter of an hour or so. A couple of days later I fixed an issue with dragging rotated items that resulted from

Ink in GoogCanvas

For example, I could then go and the ability for users to use a pen tool in photobomb. This required managing mouse events and creating a PolyLine object from the collected mouse points. Later I found that a Path worked better than a PolyLine for creating the Ink. I still have not figured out how to add points to an existing Path. As a result, Photobomb recreates the Path with each mouse move. This gets slow if the Path is not short.

SVG Icons with Inkscape



I used Inkscape to create svg files for the custom icons. I simply sized the documents how I wanted, and told the toolbar buttons to load the icons from the path. The delete icon used gtk.STOCK_DELETE.

I used the gtk.ColorChooserDialog for color selection.

I also added color button and ink width button. These hosted a goocanvas, and I just added a path to each and set the properties of the Path to display the selected color and width. I had already created quickly.prompts to make it easy to collect a line width from the user. (quickly.prompts.integer()).

I could handle this with a GooCanvas.Widget object embedded in situ, but that's a lot of work and using quickly.prompts.string() is a one-liner.

Text




I added a gtk.STOCK_EDIT button to collect text from the user. I used a quickly.prompts.string() to collect the string to display from the user.

Selection



To display selection, I simply used a GooCanvas.Rect with a bit of buffer around the selected object. I could have added stock selection points from gtk, but that sounded like a lot of work.

Opacity and Directory Iteration



I added a bit of opacity to the selection box. This works better.

Then I added opacity to other selectable items. I sniped some code from segphault's grabbersnap app to figure out how to set up rgba colors. It's a bit of a hack, but seems to work well.

At this point in the project I also switched from having the user open individual images to add, to iterating through the Pictures directory and adding a button for each encountered image.

Web Cam

Web Cam support is an important feature for Photobomb, but the available APIs were daunting. That is, until I discovered that Pygame had a simplified web cam interface. I created a quickly.widget to handle web cam integration and added that widget to Photobomb. Note that this means quickly.widgets depends on PyGame. I think I shall move the webcam widget out of the default install and make it available in some other manner.

Having another source of images meant that I needed to add a gtk.Notebook to handle switching between the two sources.
Image Searching

I used the Yahoo! API to do image searching. I chose Yahoo! only because the API was quite easy to figure out.

Press and Hold

Changing size, rotation, and opacity required the user to click the button once for each increment. So rotating something to be upside required the user to go "click click click click", etc...

What I wanted was the user to click the button, and hold it down as the image rotated, and then to stop when it was positioned as desired.

Sadly, there is no "key-down" event on gtk.Toolbars. So I had to switch Photobomb from using toolbars to using regular gtk.Button. I also created a new quickly.widget.PressAndHold widget to handle this.

I accomplish the asynch behavior with gobject.timeout_add(250, self.__tick). At each tick the button emits a signal telling the consuming app to do something.

Gwibber Integration

Because Gwibber uses desktopcouch to store messages in Lucid, I could add the feature to grovel through the messages and extract images coming from social feeds to display. I also sniped Gwibber's fb credentials from desktopcouch, and used that and some FML to get images from Facebook.

Broadcasting

I used the Gwibber API to add broadcasting Photobomb images. The GooCanvas renders a png that is saved to a temp location. Then pycurl uploads the image to imgur.com, which returns a url. Gwibber's GwibberPosterVBox to broadcast the URL.

2 comments:

  1. Awesome post, Rick. I've been waiting for some insight into Quickly and their widgets since I used a text book to learn python recently after 20 years away from developing.

    Thank you.

    ReplyDelete
  2. Know this sounds lame, but where do I get Photobomb, the app, quickly?

    ReplyDelete