Having Rails & Cocoa play together

What our Captcha will look like What our Captcha will look like

When it comes to image manipulation, most Web Applications make use of ImageMagick, through a connector. (In the ruby world, RMagick). ImageMagick is a decent graphic framework, though it can be quite a time-consuming and error-prone process to go beyond the classical crop/scale/reformat image manipulations. And it is a pain to install, too. In today’s post we’ll build a captcha system. Not that we like captchas at all, but it is quite a good example to enlight our purpose: the use of the Cocoa API from a Ruby On Rails web application. Indeed, we’ll delegate all the graphic code to a brave soldier: Quartz Composer. As you will see, it will drastically lower the lines of code required for our service, and, as usual, the less code, the less bugs.

Important: As you would have guessed, you will need a mac with Leopard and the developer tools installed. No big deal however for most Rails developers.

Setup a Rails application.

From your terminal, type rails captcha to create a new rails Application. We now want to give our Rails Application access to the Quartz Composer API. To do that, add the following at the end of your config/environment.rb file:

Mime::Type.register "image/jpeg", :jpg
# Include your application configuration below
# May Cocoa be with us
require 'osx/cocoa'
include OSX
OSX.require_framework 'Quartz'

Thanks to RubyCocoa bridge (new with Leopard), all Cocoa API is now available to you using the Ruby language. We’ve included the Quartz Framework, that will be necessary for the rendering of our composition.

We have also registered the image/jpeg Mime-type, as we will use that in a respond_to block in our controller.

Create a Quartz Composition for your captcha.

Our captcha Quartz Composition Our captcha Quartz Composition

Open Quartz Composer, it should be in /Developer/Applications/ if you installed the developer tools in the standard location. Create your captcha, for example using the “Image With String” generator.

Publish an input by right-clicking on the “String” input of the “Image With String” generator. Name it “Captcha”. This step is important, because we will then be able to pass a hash of parameters to the Quartz composition from our Rails application. “Captcha” will be the key in the parameters hash.

  • If you are new to Quartz Composer, read the doc, play with it, it is an awesome “noodle-oriented” development tool.
  • If you are lazy, just download our ready-to-run project from the top of the pink sidebar on the right of this page.

Save the composition as RAILS_ROOT/qtz/captcha.qtz.

Now, move on to the code.

Create a Composition model

Create a new file composition.rb in RAILS_ROOT/app/models, and let’s start with the Cocoa fun:

class Composition
  # creates a composition from a .qtz file whose path is qtz_path
  def initialize(qtz_path, width = 350, height = 100)
    @qtz = QCComposition.compositionWithFile(qtz_path)
    raise "Can't find a valid composition at “#{qtz_path}”." if @qtz.nil?
    @renderer = QCRenderer.alloc.initOffScreenWithSize_colorSpace_composition(
                        [width, height],
                        CGColorSpaceCreateWithName(KCGColorSpaceGenericRGB),
                        @qtz)
  end

QCComposition and QCRenderer are two classes defined in the Quartz Framework. If you wan’t to know more, open Xcode, and open the Documentation window from the Help menu.

Have a look at the initOffScreenWithSize_colorSpace_composition method name: pretty long, huh ? Objective-C, the native language of Cocoa, uses named parameters in methods. As ruby does not support that, a convention has been established by the language bridge: replace all colons (:) for underscores (_) in the Objectice-C method signature, to get the Ruby method name. And pass the parameters in the order of the signature. (You can omit the last _). Refer to the Apple Documentation to learn more.

Now move on to rendering :

# renders the composition at time, with parameters, and return the jpeg data.
def render(time = 0, parameters = {})
  # Only pass in parameters expected by the Qtz composition
  input_keys = @qtz.inputKeys
  parameters.each do |key, value|
    @renderer.setValue_forInputKey(value, key) if input_keys.include? key
  end
  # And render the composition
  rendering_ok = @renderer.renderAtTime_arguments(time, nil)
  raise "Unable to render Quartz Composition" unless rendering_ok == 1
  # now convert the result to jpeg
  bitmapRep = @renderer.createSnapshotImageOfType('NSBitmapImageRep')
  jpegimagedata = bitmapRep.representationUsingType_properties(NSJPEGFileType, 
    {NSImageCompressionFactor => 0.8}
    ).rubyString 
end

The language bridge makes a very good work in letting you swap any Objective-C basic structure by its ruby counterpart (and vice-versa). In the above code, we first filter the parameters to make sure the composition accepts them. So we first read the inputKeys dictionary (Objective-C word for Hash) from the composition, and then we fill the hash provided that inputKeys includes the keys from the parameters hash. include? is defined in Ruby’s Hash, but it is ok to call it on an NSDictionary. Thanks, RubyCocoa !

Wrap it up !

Now, we need a resource to wrap-up our stuff. It will generate a jpeg image on the fly with the captcha. Create captchas_controller.rb under RAILS_ROOT/app/controllers/, and write the following:

class CaptchasController < ApplicationController
  def show
    # Read the captcha composition
    captcha = Composition.new(File.join(RAILS_ROOT, 'qtz', 'captcha.qtz'))
    # encode our string (rot13 used here to keep our sample concise:. 
    # think to something robust for any public use - left as an exercise)
    captcha_string = params[:id].tr "A-Za-z", "N-ZA-Mn-za-m"
    # render the captcha
    respond_to do |format|
      format.text { render :text => captcha_string }
      format.jpg  { send_data captcha.render(0, {'Captcha' => captcha_string}), :disposition => 'inline' }
    end
  end
end

As you can see in the source code, you would need some kind of obfuscation to prevent automatic “guessing” of the captcha, but that is out of scope of this tutorial. (We use rot13 here, do not do that for production code !)

Add a map.resources :captchas in config/routes.rb, and edit RAILS_ROOT/public/index.html to make a form with a captcha. For the captcha image, just write: <img src="http://0.0.0.0:3000/captchas/sometext.jpg" alt="captcha" /> and replace sometext with something randomly generated.

You’re done !

Final thoughts

  • All graphic code has been delegated to Quartz Composer. Once you get used to it, you’ll appreciate the power of this fantastic tool. Moreover, you can train your favorite graphic designer to use it. It will be easier to generate a new look for each client, without changing your project, as it does not include any single line related to the captcha appearance. And it will look a lot nicer !
  • Quartz Composer relies on OpenGL and the graphic card for most of its work, so it is very efficient. Incredibly more than ImageMagick.
  • OK, perhaps you don’t host your web apps on a mac. But what about a spare mac that would host several services, like this one, or some other that could be written in just a few lines of Cocoa-Ruby ? Cocoa is a very rich framework, have a look at it ! Examples that come to my mind are: URL-to-PDF service (a dozen of lines), Image filters (QC again)... Tell us what you’ve done with it ! and… Happy new Year !

    Download the complete sample code: captcha.zip

Posted on January 02, 2008 by Pierre-Loïc Raynaud - permalink

4 comments

Written on January 05, 2008, 17h29 by Arthur

Nice.
As you talked to me about it before this post, I found someone else who is using CoreImage for image resizing & co :
http://redartisan.com/2007/12/12/attachment-fu-with-core-image

I did not realize that cocoa was that simple to call in Ruby !

Do you have the dozen of lines for URL-to-PDF ;-)

And Happy new year !

Written on January 06, 2008, 08h23 by Marcus Crafter

Nice work mate, and great use of quartz composer, certainly opens up many possibilities for the future! ;)

Cheers,

Marcus

Written on January 12, 2008, 08h50 by Pierre-Loïc Raynaud

A new documentation on "Using Scripting Languages for
Cocoa Development" is available from Apple : http://developer.apple.com/leopard/overview/scriptingcocoa.html

Written on January 19, 2009, 00h00 by willy

mag ulan og tae karon

Add your comment

Name
Email address
(your email won't be displayed)
Web site
Comment

Are you able to answer this simple question?

Release of the SearchAPI plugin Offre d’emploi — développeur