summaryrefslogtreecommitdiff
path: root/src/SW/fancy-qr-codes/document.en.rest.txt
blob: 7164f134ba3e5c00022e5c52aa96be270832af2d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
==============
Fancy QR Codes
==============
:CreationDate: 2016-11-10 17:06:20
:Id: SW/fancy-qr-codes
:tags: - software
       - perl
 
You may have seen `sylnsfar's "Artistic QR Code" library
<https://github.com/sylnsfar/qrcode>`_. It produces non-standard QR
codes that incorporate arbitrary pictures, but are nonetheless
machine-readable [#]_.
 
Of course, I had to understand how it worked ☺
 
It doesn't take much to find that the magic happens in the ``combine``
function in |myqrpy|_::
 
  if ver > 1:
    aloc = alig_location[ver-2]
    for a in range(len(aloc)):
      for b in range(len(aloc)):
        if not ((a==b==0) or (a==len(aloc)-1 and b==0) or (a==0 and b==len(aloc)-1)):
          for i in range(3*(aloc[a]-2), 3*(aloc[a]+3)):
            for j in range(3*(aloc[b]-2), 3*(aloc[b]+3)):
              aligs.append((i,j))
 
  for i in range(qr.size[0]-24):
    for j in range(qr.size[1]-24):
      if not ((i in (18,19,20)) or (j in (18,19,20)) or (i<24 and j<24) or (i<24 and j>qr.size[1]-49) or (i>qr.size[0]-49 and j<24) or ((i,j) in aligs) or (i%3==1 and j%3==1) or (bg0.getpixel((i,j))[3]==0)):
        qr.putpixel((i+12,j+12), bg.getpixel((i,j)))
 
That code is full of magic numbers, and the multiple nested loops
don't really help legibility much. What's going on in there?
 
To understand where those numbers came from, I studied `a QR code
tutorial <https://www.thonky.com/qr-code-tutorial/>`_. The most useful
page for our purposes is `the one that explains "function patterns"
<https://www.thonky.com/qr-code-tutorial/module-placement-matrix>`_.
 
Everything in that function assumes a 3-pixel QR module, so:
 
* the first block collects the placements of the alignment
  patterns
  
  * but only if the code is large enough to need them (``ver > 1``)
  * and ignoring those that would collide with finder patterns
    (``(a==b==0) or (a==len(aloc)-1 and b==0) or (a==0 and
    b==len(aloc)-1)`` means "top-left, top-right, bottom-left corners")
 
* the second block replaces pixels in the QR code (``qr.putpixel``)
  with pixels from the fancy picture (``bg.getpixel``) if:
 
  * they're not in timing patterns (``(i in (18,19,20)) or (j in
    (18,19,20))``)
  * they're not in finder patterns (``(i<24 and j<24) or (i<24 and
    j>qr.size[1]-49) or (i>qr.size[0]-49 and j<24)``)
  * they're not in alignment patterns (``(i,j) in aligs``)
  * and two more conditions I'll explain later
 
So all those magic numbers define the placements of finder / timing /
alignment patterns: those *really* have to be there, otherwise the
scanning / recognition code won't even realise it's looking at a QR
code.
 
The last two conditions are::
 
  (i%3==1 and j%3==1) or (bg0.getpixel((i,j))[3]==0)
 
The second one skips transparent pixels in the picture (``bg0`` is a
ARGB-version of the input picture ``bg``, and the pixel value at
position 3 is the alpha value).
 
The first one makes sure to leave the central pixel of each 3×3 QR
module untouched: we want the QR data to remain there!
 
.. _`the procedure outlined before`:
 
So, another way to get the same result would be:
 
* paint a normal black & white QR code
* layer the fancy picture on top of it, honouring the alpha channel
* paint all non-data modules on top
* paint the data modules on top, but only their central pixels
 
Of course, it's not trivial to know whether a module is part of the
data or of a function pattern, but we're in luck: the |libqrencode|_ C
library `provides us with that information
<https://fukuchi.org/works/qrencode/manual/structQRcode.html#details>`_!
It generates the QR code as a matrix of bytes, and each byte is a
bitfield::
 
 MSB 76543210 LSB
     |||||||`- 1=black/0=white
     ||||||`-- data and ecc code area
     |||||`--- format information
     ||||`---- version information
     |||`----- timing pattern
     ||`------ alignment pattern
     |`------- finder pattern and separator
     `-------- non-data modules (format, timing, etc.)
 
The Perl modules |text-qrcode|_ and |imager-qrcode|_ wrap that
library, but do not expose the internal byte matrix. So of course I
wrapped it again myself ☺
 
|alien-qrencode|_ uses |alien-base|_ to install |libqrencode|_, then
|data-qrcode|_ binds it via |inline-module|_ and provides a Perlish
API (see `the test file </cgit/Data-QRCode/tree/t/qrcode.t>`_ for an
example).
 
Now, we only need to deal with the actual images: let's use |imager|_,
which seems simple and flexible enough. The `actual code
</cgit/Imager-QRCode-Fancy/tree/lib/Imager/QRCode/Fancy.pm>`_ is maybe
too flexible, but the core of the logic is::
 
   my $back_image = qr_image($qr_code);
 
   return $back_image unless $fancy_picture;
 
   my $front_image = qr_dots_image($qr_code);
 
   my $target = Imager->new(xsize=>$size,ysize=>$size);
   $target->paste(src => $back_image);
   $target->compose(src => $fancy_picture);
   $target->compose(src => $front_image);
 
   return $target;
 
which is a direct translation of `the procedure outlined
before`_.
 
Once we've installed |alien-qrencode|_|data-qrcode|_, and
|qr-fancy|_, we can say:
 
.. parsed_literal::
 
   perl `overlay-qr.pl`_ 'https://pokketmowse.deviantart.com/art/In-the-Kawaii-of-the-Beholder-177076230`kawaii-beholder.png`_ `kawaii-beholder-qr.png`_
 
and get this:
 
.. image:: kawaii-beholder-qr.png
 
Or maybe:
 
.. parsed_literal::
 
   perl `overlay-qr.pl`_ 'Squirrel Girl is the best' `squirrel-girl.png`_ `squirrel-girl-qr.png`_
 
and get this:
 
.. image:: squirrel-girl-qr.png
 
Notice how the size of the "dots" adapts to the resolution of the
picture (although it could be smarter), and how transparency is
maintained.
 
.. [#] more or less: I think you need a high-enough-resolution camera
       and some pretty forgiving recognition code
 
.. _myqrpy: https://github.com/sylnsfar/qrcode/blob/master/MyQR/myqr.py#L51
.. |myqrpy| replace:: ``myqr.py``
 
.. _libqrencode: https://fukuchi.org/works/qrencode/index.html.en
.. |libqrencode| replace:: ``libqrencode``
 
.. _`text-qrcode`: https://metacpan.org/pod/Text::QRCode
.. |text-qrcode| replace:: ``Text::QRCode``
 
.. _`imager-qrcode`: https://metacpan.org/pod/Imager::QRCode
.. |imager-qrcode| replace:: ``Imager::QRCode``
 
.. _`alien-qrencode`: /cgit/Alien-QREncode/
.. |alien-qrencode| replace:: ``Alien::QREncode``
 
.. _`data-qrcode`: /cgit/Data-QRCode/
.. |data-qrcode| replace:: ``Data::QRCode``
 
.. _`qr-fancy`: /cgit/Imager-QRCode-Fancy/
.. |qr-fancy| replace:: ``Imager::QRCode::Fancy``
 
.. _`inline-module`: https://metacpan.org/pod/Inline::Module
.. |inline-module| replace:: ``Inline::Module``
 
.. _`alien-base`: https://metacpan.org/pod/Alien::Base
.. |alien-base| replace:: ``Alien::Base``
 
.. _imager: https://metacpan.org/pod/Imager
.. |imager| replace:: ``Imager``
 
.. _`overlay-qr.pl`: overlay-qr.pl
.. _`kawaii-beholder.png`: kawaii-beholder.png
.. _`kawaii-beholder-qr.png`: kawaii-beholder-qr.png
.. _`squirrel-girl.png`: squirrel-girl.png
.. _`squirrel-girl-qr.png`: squirrel-girl-qr.png