QR (de)Coding

Feb 26 2010

If you’ve ever come across our 404 page, you’ve no doubt noticed our QR code meant to help with your feeling of being lost. The code is generated with pure Ruby, with one of the many libraries a quick Google search brings up. However, when it comes to decoding QR codes, the findings are much slimmer.

Now that just ain’t right. Let’s fatten up them findings if just slightly.

  #!/usr/bin/env jruby --headless -rubygems
  require 'zxing'

  # We're gonna be doing one thing and one thing only: decodin' QR codes.
  include ZXing

  # We're workin' with the British on this mission.
  decode "http://2d-code.co.uk/images/bbc-logo-in-qr-code.gif"
  # => "http://bbc.co.uk/programmes"

  # Doesn't need to happen over the 'net. Lets go for something more local.
  decode "Desktop/qrcode.png"
  # => "http://rubyflow.com"

  # Of course, not all images got QR codes in 'em, ain't that right, soldier?
  if decode("http://www.google.com/logos/lunarnewyearseve10-hp.gif")
    puts "Whoa, is that a hidden QR code in one of Google's many logos?!"
  else
    puts "Nah, I'm just kiddin' with ya! No QR code here, no siree."
  end

  # And for those of ya that hate them rules and love them exceptions.
  decode! "http://www.rubyinside.com/wp-content/uploads/2007/10/ruby-logo.jpg"
  # => NativeException: com.google.zxing.ReaderException

  # Seems like the Ruby logo isn't hiding anything after all.

Learning JRuby

Ruby libraries for decoding QR codes may be nonexistant (or well hidden), but there are libraries out there nonetheless, specifically QRCode and ZXing. Didn’t take long to notice that both libraries were written in Java. Despite my constant dismissal of Java as a tedious language to work with (a dismissal that daicoden is all too familiar with), this was a perfect opportunity to complete the VM trifecta that’s been building up over the past few weeks: first Maglev, then MacRuby and now a chance to put JRuby to good use.

First step: pick a Java library. ZXing has the benefit of being Apache licensed (QRCode is GPLed), as well as handling formats beyond QR codes, so it’s an easy choice.

Step two: compile the Java code. I naively dived into the source code and started trying to compile the individual relevant classes needed before doing the smart thing and reading ZXing’s README:

  ant -f core/build.xml
  ant -f javase/build.xml

Which gives us core.jar and javase.jar. By require -ing these two files in our code, we can now start importing the Java classes that provide the functionality we’re after. So let’s dive into the documentation. We’re looking to decode QR codes, so I’m going to guess that QRCodeReader will do the trick.

  java_import com.google.zxing.qrcode.QRCodeReader

Now, its #decode method is likely to be exactly what we need. It takes a BinaryBitmap as a parameter, so let’s import that class as well.

  java_import com.google.zxing.BinaryBitmap

… only to noticed that BinaryBitmap ’s constructor takes a Binarizer as a parameter, which has two subclasses, one of which is still experimental, so we take the other one which is GlobalHistogramBinarizer, which in turn needs a LuminanceSource, which has three different subclasses, out of which BufferedImageLuminanceSource ends up being the subclass we need, which requires a BufferedImage to instantiate, which we can get from using ImageIO, which takes a URL or File parameter…

  # ZXing classes
  java_import com.google.zxing.BinaryBitmap
  java_import com.google.zxing.Binarizer
  java_import com.google.zxing.common.GlobalHistogramBinarizer
  java_import com.google.zxing.LuminanceSource
  java_import com.google.zxing.client.j2se.BufferedImageLuminanceSource

  # Standard Java classes
  java_import javax.imageio.ImageIO
  java_import java.net.URL
  # java_import java.io.File
  # Can't import java.io.File since it overwrites Ruby's File constant
  # which in turn messes up running tests with shoulda.
  # Instead, use Java::JavaIO::File to access Java's File class.

… and I hold my head and sigh. Oh, Java, why do you require an IDE to be of any fun to use?

Luckily, the story from here on out is a bit brighter. JRuby really does provide a nice interface to using Java classes, inserting the classes into the current namespace and modifying camel-case methods to provide Ruby’s underscore convention. All that’s missing is wrapping an initial URL or file path with the appropriate classes in the appropriate order.

    def decode!(descriptor)
    descriptor = case descriptor
    when URI.regexp(['http', 'https'])
      URL.new(descriptor)
    else
      Java::JavaIO::File.new(descriptor)
    end
    image = ImageIO.read(descriptor)
    bitmap = to_bitmap(image)
    @@decoder.decode(bitmap).to_s
  end

  private

  def to_bitmap(image)
    luminance = BufferedImageLuminanceSource.new(image)
    binarizer = GlobalHistogramBinarizer.new(luminance)
    BinaryBitmap.new(binarizer)
  end

Wanna Help?

This was just the first setup step, which in retrospect is prolly the most tedious one. Upcoming features should expand the library to allow decoding of all the formats ZXing allows, as well as figuring out a way to easily interface the library with streaming webcams (which is our main objective, actually). Code is up at GitHub for your perusal. In the meantime, I’ll keep researching how the BBC got their wicked QR code to work.