█▀▀▀▀▀█ ▀▀█▄█ █▀ ▄ █▀▀▀▀▀█
█ ███ █ ▄▀██ ▄ ▀ ▄▀ ▄ █ ███ █
█ ▀▀▀ █ ▀▄█ ▀█▄ █ ▀▀█ █ ▀▀▀ █
▀▀▀▀▀▀▀ ▀▄▀ █ █▄█ ▀▄▀ ▀▀▀▀▀▀▀
▀▀▄ ▄▀▀█ ▀█ ▀██▄▄██▄ ▄█▀▄▄
▀ ▀█▄▀█▄▀▄█▀ ▀█▄▀ ▄▄ ▀▄ ▄
▄▀▀▄▀▄▀▀▄▀█ ▄▄ ▄█▄ ▄▀ █▀▄ ▄█
▄ ▄▀▀▀ ▄ ▄▀▀▀▀▀▄ ██ ▄▀█ ▄
▄▀█▀ ▀▀ ▀▀▀ ▀▀▀█▄▀▄█▄ ▄▄▄▀█▄▄
█▀▀ ▀▀▀▀ ▄▄▀ ▄█▀ █▀ ▄ ▄█▀ ▀
▀ ▀ ▀▀█ ▀██▄█▀█ █▀▀▀█▄▀▀▀
█▀▀▀▀▀█ █▀▄▄ ▀█▀█ ▀██ ▀ █▀▀▄
█ ███ █ █▄█ ▄█▄ ▄▀██▀▀▀█▄ ▄
█ ▀▀▀ █ ▄▀█▄█ ██ ▀▀▀▀▀█▀██▀▄
▀▀▀▀▀▀▀ ▀▀▀ ▀▀ ▀ ▀ ▀▀▀▀
https://web.liminal.cafe/~byakuren/prolog/qr/
qr.pl is a pure Prolog QR Code Model 2 implementation
that is much more interesting than a normal one-shot QR generator.
It can still do the ordinary thing: turn "byakuren" into
a scannable QR code, pick a sensible version / error-correction level /
mask, print it to the terminal, and decode text-art QR codes copied from
other tools.
But the useful part is that it stays relational.
The same module can:
It works in both Scryer and Trealla.
Load the module:
?- use_module(qr).
true.Generate the best QR for byakuren and print it:
?- qr("byakuren", Spec).
█▀▀▀▀▀█ ▀▀█▄█ █▀▀▀▀▀█
█ ███ █ █▀▄ ▄ █ ███ █
█ ▀▀▀ █ ▄▄█▄▀ █ ▀▀▀ █
▀▀▀▀▀▀▀ █▄█ █ ▀▀▀▀▀▀▀
▀▄▀ ▀▀▀▀█▀█▀▀██▄██ █
▄▄▄█ ▄▀▄ █▄ █▄▀ █▀▄▀
▀ ▀▀▀ ▄ ▄██▄██▀▄ █
█▀▀▀▀▀█ █▄▄▄█ ███ ▀█
█ ███ █ ▄█ ▀ ██ ▄ ▄█▄
█ ▀▀▀ █ ▄█▄█▄▀▀▀█▀ ▀
▀▀▀▀▀▀▀ ▀ ▀ ▀▀ ▀
Spec = [version(1), level(q), mode(byte), mask(7)].Get the pure binary grid instead of rendering:
?- qr_code("byakuren", Grid, Spec).
Grid = [[1,1,1,...], ...],
Spec = [version(1), level(q), mode(byte), mask(0)] ;
... .Run the relation backwards from a grid:
?- qr_code("byakuren", Grid, Spec),
qr_code(Text, Grid, Spec).
Grid = [[1,1,1,...], ...],
Spec = [version(1), level(q), mode(byte), mask(0)],
Text = "byakuren" ;
... .That encode/decode symmetry is the main difference from an ordinary
QR tool. qr/1..3 are the deterministic convenience
wrappers. qr_code/2,3,4 are the relational core.
The simplest practical use is still just “make me a QR and print it”.
Best QR for byakuren:
?- qr("byakuren").
Spec: [version(1), level(q), mode(byte), mask(7)]
█▀▀▀▀▀█ ▀▀█▄█ █▀▀▀▀▀█
█ ███ █ █▀▄ ▄ █ ███ █
█ ▀▀▀ █ ▄▄█▄▀ █ ▀▀▀ █
▀▀▀▀▀▀▀ █▄█ █ ▀▀▀▀▀▀▀
▀▄▀ ▀▀▀▀█▀█▀▀██▄██ █
▄▄▄█ ▄▀▄ █▄ █▄▀ █▀▄▀
▀ ▀▀▀ ▄ ▄██▄██▀▄ █
█▀▀▀▀▀█ █▄▄▄█ ███ ▀█
█ ███ █ ▄█ ▀ ██ ▄ ▄█▄
█ ▀▀▀ █ ▄█▄█▄▀▀▀█▀ ▀
▀▀▀▀▀▀▀ ▀ ▀ ▀▀ ▀
If you want a specific valid spec instead of the best wrapper, you
can ask for one directly. Here the mask is fixed to 3 even
though the best wrapper picked 7:
?- qr_code("byakuren",
Grid,
[version(1), level(q), mode(byte), mask(3)]),
display_qr(Grid).
█▀▀▀▀▀█ ▄▄ ▄█ █▀▀▀▀▀█
█ ███ █ ▀▀▄█▀ █ ███ █
█ ▀▀▀ █ ▀█ █ ▀▀▀ █
▀▀▀▀▀▀▀ ▀ ▀ █ ▀▀▀▀▀▀▀
▄██▀ █▀▄ █▀ ▄▄▄▄▄ ▀█▄
██ ▀▄█▀█▄▀ ▀▀▄ █▄▄ ██
▀ ▀▀ ▀▀ ██▀██▄ ▄▄
█▀▀▀▀▀█ ▄▄▄▀ █████▄
█ ███ █ ▄▀▄ ▀▄▀▄ ▀█▄
█ ▀▀▀ █ █ ▀█▄▀▄▄ ▀ ▄
▀▀▀▀▀▀▀ ▀ ▀▀ ▀▀▀▀
Grid = [[1,1,1,...], ...].And for the page URL itself:
?- qr("https://web.liminal.cafe/~byakuren/prolog/qr/", Spec).
Spec: [version(3), level(l), mode(byte), mask(5)]
█▀▀▀▀▀█ ▀▀█▄█ █▀ ▄ █▀▀▀▀▀█
█ ███ █ ▄▀██ ▄ ▀ ▄▀ ▄ █ ███ █
█ ▀▀▀ █ ▀▄█ ▀█▄ █ ▀▀█ █ ▀▀▀ █
▀▀▀▀▀▀▀ ▀▄▀ █ █▄█ ▀▄▀ ▀▀▀▀▀▀▀
▀▀▄ ▄▀▀█ ▀█ ▀██▄▄██▄ ▄█▀▄▄
▀ ▀█▄▀█▄▀▄█▀ ▀█▄▀ ▄▄ ▀▄ ▄
▄▀▀▄▀▄▀▀▄▀█ ▄▄ ▄█▄ ▄▀ █▀▄ ▄█
▄ ▄▀▀▀ ▄ ▄▀▀▀▀▀▄ ██ ▄▀█ ▄
▄▀█▀ ▀▀ ▀▀▀ ▀▀▀█▄▀▄█▄ ▄▄▄▀█▄▄
█▀▀ ▀▀▀▀ ▄▄▀ ▄█▀ █▀ ▄ ▄█▀ ▀
▀ ▀ ▀▀█ ▀██▄█▀█ █▀▀▀█▄▀▀▀
█▀▀▀▀▀█ █▀▄▄ ▀█▀█ ▀██ ▀ █▀▀▄
█ ███ █ █▄█ ▄█▄ ▄▀██▀▀▀█▄ ▄
█ ▀▀▀ █ ▄▀█▄█ ██ ▀▀▀▀▀█▀██▀▄
▀▀▀▀▀▀▀ ▀▀▀ ▀▀ ▀ ▀ ▀▀▀▀
?- qr("byakuren", Spec).
█▀▀▀▀▀█ ▀▀█▄█ █▀▀▀▀▀█
█ ███ █ █▀▄ ▄ █ ███ █
█ ▀▀▀ █ ▄▄█▄▀ █ ▀▀▀ █
▀▀▀▀▀▀▀ █▄█ █ ▀▀▀▀▀▀▀
▀▄▀ ▀▀▀▀█▀█▀▀██▄██ █
▄▄▄█ ▄▀▄ █▄ █▄▀ █▀▄▀
▀ ▀▀▀ ▄ ▄██▄██▀▄ █
█▀▀▀▀▀█ █▄▄▄█ ███ ▀█
█ ███ █ ▄█ ▀ ██ ▄ ▄█▄
█ ▀▀▀ █ ▄█▄█▄▀▀▀█▀ ▀
▀▀▀▀▀▀▀ ▀ ▀ ▀▀ ▀
Spec = [version(1), level(q), mode(byte), mask(7)].
?- qr_code("byakuren", Grid, Spec),
qr_penalty_breakdown(Grid, Breakdown).
Grid = [[1,1,1,...], ...],
Spec = [version(1), level(q), mode(byte), mask(0)],
Breakdown = [runs(230), blocks(207), finders(800), balance(0), total(1237)] ;
... .
?- qr_mask_penalties("byakuren",
[version(1), level(q), mode(byte), mask(_)],
Penalties).
Penalties =
[ mask_penalty(0,1237),
mask_penalty(1,1385),
mask_penalty(2,1308),
mask_penalty(3,1248),
mask_penalty(4,1410),
mask_penalty(5,1309),
mask_penalty(6,1428),
mask_penalty(7,1196)
].So qr/1..3 are not just picking a random valid code.
They can justify the chosen mask.
Most QR generators give you one answer and hide the rest.
qr.pl lets you treat QR codes as a search space:
For byakuren, there are many valid version-1 QR
codes:
?- findall(Spec,
( spec_version(Spec, 1),
qr_code("byakuren", _, Spec)
),
Specs),
length(Specs, Count).
Specs = [[version(1), level(q), mode(byte), mask(0)], ...],
Count = 48.That is the sort of query that normal generators usually do not expose at all.
The easiest way to see the relational side is to leave part of the spec open and let Prolog backtrack:
?- qr_code("byakuren",
_,
[version(1), level(q), mode(byte), mask(Mask)]).
Mask = 0 ;
Mask = 1 ;
Mask = 2 ;
Mask = 3 ;
Mask = 4 ;
Mask = 5 ;
Mask = 6 ;
Mask = 7 ;
false.Or leave the whole spec open:
?- qr_code("byakuren", _, Spec).
Spec = [version(1), level(q), mode(byte), mask(0)] ;
Spec = [version(1), level(q), mode(byte), mask(1)] ;
Spec = [version(1), level(q), mode(byte), mask(2)] ;
... .That is the key difference between qr_code/* and
qr/*: qr/* chooses one best symbol, while
qr_code/* lets Prolog enumerate the valid search space.
Specs are plain property lists:
[version(Version), level(Level), mode(Mode), mask(Mask)]That makes them pleasant to reason over with ordinary Prolog.
Constrain the search:
?- spec_version(Spec, 1),
spec_level(Spec, q),
spec_mode(Spec, byte),
spec_mask(Spec, 3),
qr_code("byakuren", _, Spec).
Spec = [version(1), level(q), mode(byte), mask(3)].Ask for one exact family of answers:
?- qr_code("byakuren",
_,
[version(1), level(q), mode(byte), mask(Mask)]).
Mask = 0 ;
Mask = 1 ;
Mask = 2 ;
... .Or use the bounded skeleton directly:
?- qr_spec(Spec),
member(version(1), Spec),
member(level(q), Spec),
member(mode(byte), Spec),
member(mask(0), Spec),
qr_code("byakuren", _, Spec).
Spec = [version(1), level(q), mode(byte), mask(0)].The spec_* helpers are useful because they keep the
search finite while still feeling like ordinary Prolog reasoning.
The qr/1..3 predicates are the deterministic
front-end.
They choose:
That means:
?- qr("https://web.liminal.cafe/~byakuren/prolog/qr/", Spec).
█▀▀▀▀▀█ ▀▀█▄█ █▀ ▄ █▀▀▀▀▀█
█ ███ █ ▄▀██ ▄ ▀ ▄▀ ▄ █ ███ █
█ ▀▀▀ █ ▀▄█ ▀█▄ █ ▀▀█ █ ▀▀▀ █
▀▀▀▀▀▀▀ ▀▄▀ █ █▄█ ▀▄▀ ▀▀▀▀▀▀▀
▀▀▄ ▄▀▀█ ▀█ ▀██▄▄██▄ ▄█▀▄▄
▀ ▀█▄▀█▄▀▄█▀ ▀█▄▀ ▄▄ ▀▄ ▄
▄▀▀▄▀▄▀▀▄▀█ ▄▄ ▄█▄ ▄▀ █▀▄ ▄█
▄ ▄▀▀▀ ▄ ▄▀▀▀▀▀▄ ██ ▄▀█ ▄
▄▀█▀ ▀▀ ▀▀▀ ▀▀▀█▄▀▄█▄ ▄▄▄▀█▄▄
█▀▀ ▀▀▀▀ ▄▄▀ ▄█▀ █▀ ▄ ▄█▀ ▀
▀ ▀ ▀▀█ ▀██▄█▀█ █▀▀▀█▄▀▀▀
█▀▀▀▀▀█ █▀▄▄ ▀█▀█ ▀██ ▀ █▀▀▄
█ ███ █ █▄█ ▄█▄ ▄▀██▀▀▀█▄ ▄
█ ▀▀▀ █ ▄▀█▄█ ██ ▀▀▀▀▀█▀██▀▄
▀▀▀▀▀▀▀ ▀▀▀ ▀▀ ▀ ▀ ▀▀▀▀
Spec = [version(3), level(l), mode(byte), mask(5)].That is the QR shown at the top of this page.
If you want every valid answer instead of the best one, use
qr_code/2,3,4.
qr.pl exposes a useful analysis layer:
qr_penalty/2qr_penalty_breakdown/2qr_mask_penalties/3qr_codewords/4qr_capacity/2qr_analysis/2For example:
?- qr("byakuren", Spec, Grid),
qr_capacity(Spec, Capacity).
█▀▀▀▀▀█ ▀▀█▄█ █▀▀▀▀▀█
█ ███ █ █▀▄ ▄ █ ███ █
█ ▀▀▀ █ ▄▄█▄▀ █ ▀▀▀ █
▀▀▀▀▀▀▀ █▄█ █ ▀▀▀▀▀▀▀
▀▄▀ ▀▀▀▀█▀█▀▀██▄██ █
▄▄▄█ ▄▀▄ █▄ █▄▀ █▀▄▀
▀ ▀▀▀ ▄ ▄██▄██▀▄ █
█▀▀▀▀▀█ █▄▄▄█ ███ ▀█
█ ███ █ ▄█ ▀ ██ ▄ ▄█▄
█ ▀▀▀ █ ▄█▄█▄▀▀▀█▀ ▀
▀▀▀▀▀▀▀ ▀ ▀ ▀▀ ▀
Spec = [version(1), level(q), mode(byte), mask(7)].
Grid = [[1,1,1,...], ...].
Capacity =
[ size(21),
total_codewords(26),
data_codewords(13),
data_bits(104),
ecc_codewords(13)
].And the bundled view:
?- qr_analysis("byakuren", Analysis).
Analysis =
[ data("byakuren"),
spec([version(1), level(q), mode(byte), mask(0)]),
grid([[1,1,1,...], ...]),
size(21),
capacity([size(21), total_codewords(26), data_codewords(13), ...]),
penalty(1237),
penalty_breakdown([runs(230), blocks(207), finders(800), balance(0), total(1237)]),
received_codewords([...]),
codewords([...]),
mask_penalties([mask_penalty(0,1237), ..., mask_penalty(7,1196)])
] ;
... .The same predicate also works directly from a grid:
?- qr_code("byakuren", Grid, [version(1), level(q), mode(byte), mask(7)]),
qr_analysis(Grid, Analysis).
Grid = [[1,1,1,...], ...],
Analysis =
[ data("byakuren"),
spec([version(1), level(q), mode(byte), mask(7)]),
grid([[1,1,1,...], ...]),
size(21),
capacity([size(21), total_codewords(26), data_codewords(13), ...]),
penalty(1196),
penalty_breakdown([runs(204), blocks(192), finders(800), balance(0), total(1196)]),
received_codewords([...]),
codewords([...]),
mask_penalties([mask_penalty(0,1237), ..., mask_penalty(7,1196)])
].returns a property list that includes the chosen spec, grid, capacity, penalty, breakdown, received codewords, corrected codewords, and the penalty table for every mask in that family.
That is very useful when you want to inspect why a particular QR was chosen.
qr.pl can also decode text-art QR codes copied from
terminals or websites.
It accepts:
qrencode -t ANSIUTF8███From standard input:
qrencode -t ANSIUTF8 "byakuren" | \
tpl -g "use_module(qr), qr_decode_stdin(Text, Spec)."
Text = "byakuren", Spec = [version(1),level(l),mode(byte),mask(5)].From a file:
tpl -g \
"use_module(qr), qr_decode_file('byakuren-qr.txt', Text, Spec)."
Text = "byakuren", Spec = [version(1),level(l),mode(byte),mask(5)].From Prolog directly:
?- qr_code("byakuren", Grid, [version(1), level(q), mode(byte), mask(7)]),
qr:qr_render_lines(Grid, TextArt),
qr_decode(TextArt, Data, Spec).
Grid = [[1,1,1,...], ...],
TextArt = [[32,32,32,...], ...],
Data = "byakuren",
Spec = [version(1), level(q), mode(byte), mask(7)].That makes it easy to copy/paste a QR from elsewhere and still bring it back into a relational Prolog workflow.
The module also exposes things that ordinary QR front-ends usually keep hidden.
You can inspect the extracted versus corrected codewords:
?- qr_code("byakuren", Grid, [version(1), level(q), mode(byte), mask(7)], _),
qr_codewords(Grid,
[version(1), level(q), mode(byte), mask(7)],
ReceivedCodewords,
CorrectedCodewords).
Grid = [[1,1,1,...], ...],
ReceivedCodewords =
[64,134,39,150,22,183,87,38,86,224,236,17,236,230,72,52,248,95,161,26,105,51,173,138,79,180],
CorrectedCodewords =
[64,134,39,150,22,183,87,38,86,224,236,17,236,230,72,52,248,95,161,26,105,51,173,138,79,180].When the grid contains recoverable damage, those two lists differ.
For example, if one data module is flipped:
?- qr_code("byakuren", Grid, [version(1), level(q), mode(byte), mask(7)], _),
qr:flip_first_data_module(Grid,
[version(1), level(q), mode(byte), mask(7)],
DamagedGrid),
qr_codewords(DamagedGrid,
[version(1), level(q), mode(byte), mask(7)],
ReceivedCodewords,
CorrectedCodewords).
Grid = [[1,1,1,...], ...],
DamagedGrid = [[1,1,1,...], ...],
ReceivedCodewords =
[192,134,39,150,22,183,87,38,86,224,236,17,236,230,72,52,248,95,161,26,105,51,173,138,79,180],
CorrectedCodewords =
[64,134,39,150,22,183,87,38,86,224,236,17,236,230,72,52,248,95,161,26,105,51,173,138,79,180].And decoding the damaged grid still recovers the payload:
?- qr_code("byakuren", Grid, [version(1), level(q), mode(byte), mask(7)], _),
qr:flip_first_data_module(Grid,
[version(1), level(q), mode(byte), mask(7)],
DamagedGrid),
qr_code(Text, DamagedGrid, Spec, Stages),
member(stage(rs_corrections, Corrections), Stages).
Grid = [[1,1,1,...], ...],
DamagedGrid = [[1,1,1,...], ...],
Text = "byakuren",
Spec = [version(1), level(q), mode(byte), mask(7)],
Stages = [stage(spec, [version(1), level(q), mode(byte), mask(7)]), ...],
Corrections = [rs_corrected([correction(0,128)])].Decode can work with unknown modules marked as u or left
as variables. When a partial grid is still within the Reed-Solomon
repair limit, the relation can recover the original text and fill the
unknown cells from the corrected symbol.
For multi-symbol payloads:
?- qr_sequence("byakuren", _, Specs).
Specs = [[version(1), level(q), mode(byte), mask(7)]].If the payload fits in one symbol, you get one symbol. If not,
qr.pl can split it into a structured-append sequence and
decode the sequence back again.
If you do not want automatic text handling, give the exact segment structure:
?- qr_code(qr_data([fnc1(first), byte("bya"), byte("kuren")]),
Grid,
Spec).
Grid = [[1,1,1,...], ...],
Spec = [version(1), level(q), mode(segments), mask(0)] ;
... .That is useful for learning how QR payload streams are really assembled.
What makes this module interesting is not just that it can generate QR codes in Prolog.
It is that Prolog stays visible at the boundary:
So qr.pl works both as a practical terminal QR generator
and as a small reference implementation for learning how QR codes
work.