December 29th, 2005

Building a Railroad Programatically With RMagick

7 comments on 1168 words

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&#8230;) which should’ve been blurb.annotate(@canvas[0]&#8230;). 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 ;-).

Discussion

  1. topfunky topfunky said on December 29th

    Very nice. I’d love to see a wave of creative dynamically generated graphics happening. (I’m working on my own.)

    However, I noticed that I am no longer a “Swell Chap” here! What does it take?!!!

    Are there only four? It’s time to take down Ezra…

  2. Justin Palmer Justin Palmer said on December 29th

    Oops! I was doing some cleanup on the list and forgot to add you back. Doing to many things at once.

  3. PJ Hyett PJ Hyett said on December 29th

    Very interesting, thanks. I think I might have to start playing, too.

  4. Heiko Heiko said on December 29th

    Very nice idea ;o)

  5. Tim Hunter Tim Hunter said on December 30th

    Nice! I’m always interested in seeing ways to use RMagick that go beyond just making thumbnails.

  6. Justin Palmer Justin Palmer said on December 30th

    Thanks Tim! Not to shabby of a job you did on RMagick if I might add ;-)

  7. Amr Malik Amr Malik said on December 30th

    nifty!

Sorry, comments are closed for this article.