ENOSUCHBLOG

Programming, philosophy, pedaling.


Introducing qrencode.cr

Apr 8, 2018

Tags: programming, crystal

This is a writeup for my fourth Crystal library: qrencode.cr.

Background

qrencode.cr is a wrapper for libqrencode, the C library behind qrencode. It supports QR Code model 2 and Micro QR (the latter experimentally).

Implementation

Binding libqrencode to Crystal was nice and straightforward, as there are only a handful of methods needed for the most common QR encoding tasks. The main libqrencode structure QRcode is also primitive, with no references to other structures:

@[Link("qrencode")]
lib LibQRencode
  struct QRcode
    version : LibC::Int
    width : LibC::Int
    data : LibC::UChar*
  end

  fun encode_string = QRcode_encodeString(string : LibC::Char*, version : LibC::Int, level : ::QRencode::ECLevel, hint : ::QRencode::EncodeMode, casesensitive : LibC::Int) : QRcode*
  fun encode_string_mqr = QRcode_encodeStringMQR(string : LibC::Char*, version : LibC::Int, level : ::QRencode::ECLevel, hint : ::QRencode::EncodeMode, casesensitive : LibC::Int) : QRcode*
  fun encode_data = QRcode_encodeData(size : LibC::Int, data : LibC::UChar*, version : LibC::Int, level : ::QRencode::ECLevel) : QRcode*
  fun encode_data_mqr = QRcode_encodeDataMQR(size : LibC::Int, data : LibC::UChar*, version : LibC::Int, level : ::QRencode::ECLevel) : QRcode*
  fun free = QRcode_free(qrcode : QRcode*) : Void
end

From there, I just wrapped the bindings into a QRencode::QRcode object and introduced some enums equivalent to those in libqrencode:

...that's about it!

Installation and usage

The easiest way to install qrencode.cr is via Crystal's shards dependency manager. You can find the relevant steps in the repository README.

Once installed, generating a QR code involves a single instantiation:

require "qrencode"

qr = QRencode::QRcode.new("this is my QR code!")

Arbitrary binary data can be passed as well, via a Bytes (i.e., Slice(UInt8)) object:

data = "this is a slice-ified string".to_slice
qr = QRencode::QRcode.new(data)

Once created, the QRcode instance contains the actual QR symbol data in the data field. This field can be accessed directly, or via each_row (which yields each row):

# Since QR codes are square, `qr.data.size == qr.width * qr.width`.
puts qr.data.inspect

qr.each_row do |row|
  # Each row is `qr.width` bytes.
  puts row.inspect
end

Each byte in data has various bits set to indicate state, all of which are documented here. The most important one for most applications is the LSB, which indicated whether the module/dot corresponding to that byte is black or white. QRencode::Util provides two simple methods for exactly that:

qr.data.each do |byte|
  if QRencode::Util.black? byte
    puts "Black"
  else
    puts "White"
  end
end

This forms the basis for most QR rendering code. Importantly, neither libqrencode nor qrencode.cr provides rendering functions (e.g., to a PNG or SVG) — it's up to the user (or a QR rendering library) to take the data inside a QRCode object and turn it into something presentable.

By way of education, the examples/ansiqr.cr program is a simple example of this sort of rendering. It takes an input string and writes a QR code to the terminal using ANSI color codes:

ansiqr

...which works as expected. Taken from my phone with a camera scanner:

QR scan

Full (or nearly full) API documentation is also available here. I'll try to keep it synced with the latest released version.

Summary

This was a fun little afternoon project — after reading the libqrencode project, writing these bindings only took about an hour. It also gave me a bit of insight into the structure of QR codes (e.g., the QR "version," which actually corresponds to the size of the code and not its specification).

Thanks for reading!


Reddit discussion