SkillAgentSearch skills...

Goban

A fast and efficient QR/Micro QR/rMQR Code implementation in Crystal lang

Install / Use

/learn @soya-daizu/Goban
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

Goban

A fast and efficient QR Code encoder/decoder library written purely in Crystal. It is significantly faster (4-10×) and uses fewer heap allocations (-95%) compared to the other implementation in Crystal (spider-gazelle/qr-code), and it supports wider QR Code standard features such as Kanji mode encoding. It also supports generating Micro QR Code and rMQR Code symbols.

The implementation aims compliance with following standards:

The name comes from the board game Go, which inspired the QR Code inventor to come up with a fast and accurate canvas barcode to read. 碁盤(Goban) literally means Go board in Japanese.

"QR Code" is a registered trademark of Denso Wave Incorporated. https://www.qrcode.com/en/patent.html

Benchmarks

vs spider-gazelle/qr-code:

text = ARGV[0]
Benchmark.ips do |x|
  x.report("goban") { Goban::QR.encode_string(text, Goban::ECC::Level::High) }
  x.report("qr-code") { QRCode.new(text, level: :h) }
end
❯ ./bin/qr_test "Hello World\!"
  goban  30.76k ( 32.51µs) (± 0.21%)  7.6kB/op        fastest
qr-code   4.59k (217.64µs) (± 0.64%)  149kB/op   6.69× slower
❯ ./bin/qr_test "こんにちは"
  goban  30.99k ( 32.26µs) (± 0.31%)  7.56kB/op        fastest
qr-code   3.08k (324.90µs) (± 0.59%)   198kB/op  10.07× slower

It goes even faster with a larger data and multithreading:

❯ shards build --release -Dpreview_mt
Dependencies are satisfied
Building: qr_test

❯ CRYSTAL_WORKERS=4 ./bin/qr_test "A fast and efficient QR Code encoder/decoder library written purely in Crystal. It is significantly faster (4-10×) and uses fewer heap allocations (-95%) compared to the other implementation in Crystal (spider-gazelle/qr-code), and it supports wider QR Code standard features such as Kanji mode encoding. It also supports generating Micro QR Code and rMQR Code symbols."
  goban   2.45k (408.47µs) (± 6.31%)  95.3kB/op        fastest
qr-code 168.07  (  5.95ms) (± 2.17%)  2.55MB/op  14.57× slower

❯ CRYSTAL_WORKERS=8 ./bin/qr_test "A fast and efficient QR Code encoder/decoder library written purely in Crystal. It is significantly faster (4-10×) and uses fewer heap allocations (-95%) compared to the other implementation in Crystal (spider-gazelle/qr-code), and it supports wider QR Code standard features such as Kanji mode encoding. It also supports generating Micro QR Code and rMQR Code symbols."
  goban   3.41k (292.99µs) (± 2.03%)  95.3kB/op        fastest
qr-code 173.34  (  5.77ms) (± 1.81%)  2.55MB/op  19.69× slower

vs woodruffw/qrencode.cr (Crystal bindings to libqrencode):

text = ARGV[0]
Benchmark.ips do |x|
  x.report("goban") { Goban::QR.encode_string(text, Goban::ECC::Level::High) }
  x.report("qrencode") { QRencode::QRcode.new(text, level: QRencode::ECLevel::HIGH) }
end
❯ ./bin/qr_test "Hello World\!"
   goban  30.31k ( 32.99µs) (± 0.20%)  7.6kB/op   1.46× slower
qrencode  44.18k ( 22.63µs) (± 0.28%)   112B/op        fastest
❯ ./bin/qr_test "こんにちは"
   goban  31.45k ( 31.80µs) (± 0.22%)  7.56kB/op   1.03× slower
qrencode  32.42k ( 30.84µs) (± 0.52%)    112B/op        fastest

* The heap allocation value reported for qrencode.cr is not accurate as it doesn't include memory allocations happened on the C library.

When compared to the C library, Goban is usually a bit slower, but it should be noted that Goban uses a more advanced algorithm for text segmentation and is more likely to produce a smaller QR Code symbol as a result.

Features

| QR Code Type | Encoding | Decoding | | ------------- | :------: | :------: | | QR Code* | ✓ | ✓ | | Micro QR Code | ✓ | ✓ | | rMQR Code | ✓ | ✓ |

* QR Code Model 1 will not be supported as it is considered obsolete.

Roadmap

  • Add ECI mode encoding
  • Support structured append of symbols

Installation

  1. Add the dependency to your shard.yml:

    dependencies:
      goban:
        github: soya-daizu/goban
    
  2. Run shards install

Usage

A simple example to generate a QR Code for the given string and output to the console:

require "goban"

qr = Goban::QR.encode_string("Hello World!", Goban::ECC::Level::Low)
qr.print_to_console
# => ██████████████  ████    ██  ██████████████
#    ██          ██    ██    ██  ██          ██
#    ██  ██████  ██  ██  ██  ██  ██  ██████  ██
#    ██  ██████  ██  ██    ██    ██  ██████  ██
#    ██  ██████  ██  ██████      ██  ██████  ██
#    ██          ██              ██          ██
#    ██████████████  ██  ██  ██  ██████████████
#                      ████
#    ████████    ██  ██  ██    ██    ██████  ██
#    ██████████    ██  ██    ██████████████  ██
#        ██    ████    ██    ████  ██      ████
#    ████  ██      ██████    ██    ██  ██  ██
#    ████  ██████████  ██████  ████          ██
#                    ████████    ████    ██  ██
#    ██████████████      ██    ████████
#    ██          ██    ██████████  ██  ████
#    ██  ██████  ██    ██  ██          ██████
#    ██  ██████  ██  ██  ██  ██  ██    ██████
#    ██  ██████  ██  ██████  ██    ██    ██
#    ██          ██  ██    ██  ████████      ██
#    ██████████████  ██    ██  ██████    ██

Goban::ECC::Level represents the ECC (Error Correction Coding) level to use when encoding the data. The available options are:

| Level | Error Correction Capability | | -------- | --------------------------- | | Low | Approx 7% | | Medium | Approx 15% | | Quartile | Approx 25% | | High | Approx 30% |

The default ECC level is Medium. Use Low if you want your QR Code to be as compact as possible, or increase the level to Quartile or High if you want it to be more resistant to damage.

Higher ECC levels are especially capable of interpolating a large chunk of loss in the symbol such as by tears and stains. Typically, it is not necessary to set the ECC level high for display purposes on the screen.

Using exporters to generate a PNG and SVG image

To generate a PNG image, add stumpy_png as a dependency in your shard.yml, and require "goban/exporters/png" to use Goban::PNGExporter:

require "goban/exporters/png"

qr = Goban::QR.encode_string("Hello World!")
puts "Exporting with targeted size: 500"
size = Goban::PNGExporter.export(qr, "output.png", 500)
puts "Actual QR Code size: #{size}"

Goban::SVGExporter requires no external dependency and can be used like below:

require "goban/exporters/svg"

qr = Goban::QR.encode_string("Hello World!")
# Get SVG string
puts Goban::SVGExporter.svg_string(qr, 4)
# or export as a file
Goban::SVGExporter.export(qr, "test.svg")

Alternatively, you can write your own export logic by iterating over the canvas of the QR Code object.

qr = Goban::QR.encode_string("Hello World!")
qr.canvas.each_row do |row, y|
  row.each do |mod, x|
    # mod is each module (pixel or dot in other words) included in the symbol
    # and the value is either 0 (= light) or 1 (= dark)
  end
end

About the encoding modes and the text segmentation

The Goban::QR.encode_string method under the hood encodes a string to an optimized sequence of text segments where each segment is encoded in one of the following encoding modes:

| Mode | Supported Characters | | ------------ | --------------------------- | | Numeric | 0-9 | | Alphanumeric | 0-9 A-Z \s $ % * + - . / : | | Byte | Any UTF-8 characters | | Kanji | Any Shift-JIS characters |

The Byte mode supports the widest range of characters but it is inefficient and produces longer data bits, meaning that when comparing the two QR Code symbols, one encoded entirely in the Byte mode and the other encoded in the appropriate mode for each character*, the former one can be more challenging to scan and decode than the other given that both symbols are printed in the same size.

* Because each text segment includes additional header bits to indicate its encoding mode, simply encoding each character in the supported mode that has the smallest character set may not always produce the most optimal segments. Goban addresses this by using the technique of dynamic programming.

Finding out the optimal segmentation requires some processing, so if you are generating thousands of QR Codes with all the same limited sets of characters, you may want to hard-code the text segments and apply the characters to those to generate the QR Codes.

This can be done by using the Goban::QR.encode_segments method, which is the lower-level method used by the Goban::QR.encode_string method.

segments = [
  Goban::Segment.kanji("こんにち"),
  Goban::Segment.byte("wa"),
  Goban::Segment.kanji("、世界!"),
  Goban::Segment.alphanumeric(" 123"),
]
# Note that when using this method, you have to manually assign the version (= size) of the QR Code.
qr = Goban::QR.encode_segments(segments, Goban::ECC::Level::Low, 2)

The optimal segments and version to hard-code can be figured out by manually executing the Goban::QR.determine_version_and_segments method.

Generating Micro QR Codes

Micro QR Codes can be generated just like regular QR Codes using the Goban::MQR.encode_string or Goban::MQR.encode_segments methods.

mqr = Goban::MQR.encode_string("Hello World!", Goban::ECC::Level::Low)
mqr.print_to_console
# => █
View on GitHub
GitHub Stars30
CategoryDevelopment
Updated5mo ago
Forks2

Languages

Crystal

Security Score

92/100

Audited on Oct 18, 2025

No findings