Building a Railroad Programatically With RMagick

—Thursday, December 29 2005

While talking with Rick Olson (technoweenie) about Rails Weenie and how he could increase participation from people within the Ruby/Rails community we discussed having Feedburner style badges that let you show your score on your blog. We took this concept and pushed it one step further and also made it a little more interesting. We decided to use a stretch of railroad for the badges.

Here’s how it works:

  • Users start with a set amount of points that they can wager for an answer to their question
  • Users score points for answering questions and posting tips.
  • For each X amount of points a user can grow their level and also start building their railroad.
  • When a user reaches say 200 points, they get two crossties put on their railroad badge and some fancy status title such as “Master Rubyist”.

Cool huh? Now all that’s left is to actually make it work with Ruby and RMagick. It’s fairly simple, but I thought it’d be fun to share how I did it.

The Graphics

Rail-Parts

I started out with 3 png graphics that I put together in Photoshop. The semi-transparent cross ties function much like grayed out stars in a rating system. Basically we lay down the semi-transparent crossties first, then depending on a users score we overlay opaque crossties on top of those, next we overlay the rails, and finally we will write some text on the canvas.

3Drailroad

The Code

  def draw
    @canvas = ImageList.new
    @canvas.new_image(@width, @height) {
      self.background_color = '#ffffff'
    }
    
   # Load up semi-transparent crossties
   @canvas << Image.read("#{@asset_dir}/crossties.png").first
   
   # Place opaque crossties  
   %w(3, 12, 20, 28, 36, 44, 52, 60, 68, 76, 84).each do |x|
     crosstie = Image.read("#{@asset_dir}/crosstie.png").first
     crosstie.page = Rectangle.new(5, 18, x.to_i, 0)
     @canvas << crosstie
   end
   
   # Overlay rails on top of crossties
   @canvas << Image.read("#{@asset_dir}/rails.png").first
   
   # Write the text
   blurb = Draw.new
   blurb.font = "#{@asset_dir}/#{@font}"
   blurb.fill('black')
   blurb.annotate(@canvas[0], @width, @height, 0, 0, "RAILS WEENIE\n GRAND MASTER\n 200") do |b| 
     b.gravity = SouthGravity
     b.fill_opacity(1)
     b.pointsize = 8
   end
      
   # Make it so
   @canvas.flatten_images.write('omg.gif')
  end

This is pulled from the code and tweaked to just show you how to compose the Railroad, I left out all the score stuff for the sake of simplicity.

  1. First we create a canvas to draw on and set it’s background color to white.
  2. Then we load up the transparent crossties.
  3. Now we place our individual crossties at predefined points. The numbers in the array represent offset pixel values from the left of the canvas edge. All of this gets appended to the @canvas ImageList.
   # Place opaque crossties  
   %w(3, 12, 20, 28, 36, 44, 52, 60, 68, 76, 84).each do |x|
     crosstie = Image.read("#{@asset_dir}/crosstie.png").first
     crosstie.page = Rectangle.new(5, 18, x.to_i, 0)
     @canvas << crosstie
   end

The page attribute here takes a Rectangle object, which in turn takes 4 arguments: (width, height, x, y). The width and height are that of a single crosstie, and the x and y values are the offset of each crosstie in respect to the underlying image.

  1. Next we create a new Draw object that will serve as the annotation for our graphic.
   # Write the text
   blurb = Draw.new
   blurb.font = "#{@asset_dir}/#{@font}"
   blurb.fill('black')
   blurb.annotate(@canvas[0], @width, @height, 0, 0, "RAILS WEENIE\n GRAND MASTER\n 200") do |b| 
     b.gravity = SouthGravity
     b.fill_opacity(1)
     b.pointsize = 8
   end
 

The only interesting thing here is the annotate method. While doing this step, I kept having my text cut off right below the transparent crossties and pulled out a bit of hair in the process. It turns out that I wasn’t drawing on the bottom canvas, so I originally had blurb.annotate(@canvas...) which should’ve been blurb.annotate(@canvas[0]...). You can read more about annotate here.

Wrapping up

Thats it! You could probably take something like this to the extreme. It might be fun to supply a graphic starter kit with pieces of a structure and see who could build the coolest bridge or house with RMagick and Ruby. Sorta like programmatic legos ;-).