Apr 8, 2018 Tags: crystal, programming
This is a writeup for my fourth Crystal library: qrencode.cr.
qrencode.cr is a wrapper for libqrencode, the C library behind qrencode
. It supports QR
Code model 2 and Micro QR (the latter experimentally).
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:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@[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
enum
s equivalent to those in libqrencode:
QRecLevel
-> QRencode::ECLevel
QRencodeMode
-> QRencode::EncodeMode
…that’s about it!
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:
1
2
3
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:
1
2
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):
1
2
3
4
5
6
7
# 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:
1
2
3
4
5
6
7
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:
…which works as expected. Taken from my phone with a camera scanner:
Full (or nearly full) API documentation is also available here. I’ll try to keep it synced with the latest released version.
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!