summaryrefslogtreecommitdiff
path: root/src/SW/fancy-qr-codes/document.it.rest.txt
blob: 43539b993cbc968823243df8c4d3a545f0a8dae9 (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
196
==================
Codici QR decorati
==================
:CreationDate: 2016-11-10 17:06:20
:Id: SW/fancy-qr-codes
:tags: - software
       - perl
 
Potreste aver visto la `libreria "Artistic QR Code" di sylnsfar
<https://github.com/sylnsfar/qrcode>`_. Produce codici QR non-standard
che incorporano immagini arbitrarie, ma sono comunque leggibili a
macchina [#]_
 
Ovviamente, dovevo capire come funziona ☺
 
Non ci vuole molto a scoprire che la magia avviene nella funzione
 ``combine`` 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)))
 
Quel codice è pieno di numeri magici, e i vari cicli annidati non
aiutano la leggibilità. Cosa fa tutta quella roba?
 
Per capire da dove venissero quei numeri, mi sono studiato `un
tutorial sui codici QR
<https://www.thonky.com/qr-code-tutorial/>`_. Per i nostri scopi, la
pagina più utile è `quella che spiega i "function patterns"
<https://www.thonky.com/qr-code-tutorial/module-placement-matrix>`_.
 
In quella funzione, tutto suppone che i moduli siano 3×3 pixel, per
cui:
 
* il primo blocco costruisce le coordinate degli "alignment
  patterns"
  
  * ma solo se il codice è grande abbastanza da usarli (``ver > 1``)
  * e saltando quelli che collidono coi "finder patterns: (``(a==b==0)
    or (a==len(aloc)-1 and b==0) or (a==0 and b==len(aloc)-1)`` vuol
    dire "angoli alto-sinistra, alto-destra, basso-sinistra corners")
 
* il secondo blocco rimpiazza i pixel del codice QR (``qr.putpixel``)
  con i pixel dall'immagine (``bg.getpixel``) se:
 
  * non fanno parte dei "timing patterns" (``(i in (18,19,20)) or (j
    in (18,19,20))``)
  * non fanno parte dei "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)``)
  * non fanno parte degli "alignment patterns" (``(i,j) in aligs``)
  * e altre due condizioni che spiego dopo
 
Quindi tutti quei numeri magici definiscono le coordinate dei vari
pattern: quelli *devono* esserci, altrimenti il programma di lettura /
riconoscimento non si accorgerebbe neppure che sta guardando un codice
QR.
 
Le ultime due condizioni sono::
 
  (i%3==1 and j%3==1) or (bg0.getpixel((i,j))[3]==0)
 
La seconda salta i pixel trasparenti dell'immagine (``bg0`` è una vesione
ARGB dell'immagine ``bg``, e il valore in posizione 3 del pixel è il
valore alpha).
 
La prima condizione fa in modo che il pixel centrale di ciascun modulo
3×3 resti inalterato: vogliamo che le informazioni del codice QR ci
siano, da qualche parte!
 
.. _`procedura descritta sopra`:
 
In altre parole, per ottenere lo stesso risultato potremmo:
 
* disegnare un normale codice QR in bianco e nero
* sovrapporre la nostra immagine, tenendo conto del canale alpha
* disegnarci sopra i moduli non-dati
* disegnarci sopra i moduli dati, ma solo i pixel centrali di ciascuno
 
Ovviamente, non è proprio banale sapere se un modulo è parte dei dati
o di un pattern, ma siamo fortunati: la libreria C |libqrencode|_ `ci
fornisce proprio questa informazione
<https://fukuchi.org/works/qrencode/manual/structQRcode.html#details>`_!
Genera il codice QR come matrice di byte, e ogni byte è in bit-field::
 
 MSB 76543210 LSB
     |||||||`- 1=nero/0=bianco
     ||||||`-- dati e ECC
     |||||`--- informazione di formato
     ||||`---- informazione di versione
     |||`----- timing pattern
     ||`------ alignment pattern
     |`------- finder pattern e separatori
     `-------- moduli non-dati (format, timing, &c)
 
I moduli Perl |text-qrcode|_ e |imager-qrcode|_ usano quella libreria,
ma non espongono i dettagli interni. Per cui ho ri-scritto un po' di
pezzi ☺
 
|alien-qrencode|_ usa |alien-base|_ per installare |libqrencode|_; poi
|data-qrcode|_ usa |inline-module|_ per accederla da Perl, e fornisce
un'interfaccia Perlosa (`il file di test
</cgit/Data-QRCode/tree/t/qrcode.t>`_ mostra un esempio d'uso).
 
Ora, dobbiamo solo preoccuparci delle immagini: usiamo |imager|_,
che sembra abbastanza semplice e potente. Il `codice vero e proprio
</cgit/Imager-QRCode-Fancy/tree/lib/Imager/QRCode/Fancy.pm>`_ è forse
troppo flessibile, ma il grosso della logica è::
 
   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;
 
che è una traduzione diretta della `procedura descritta sopra`_.
 
Una volta installati |alien-qrencode|_|data-qrcode|_, e
|qr-fancy|_, possiamo dire:
 
.. 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`_
 
e ottenere questo:
 
.. image:: kawaii-beholder-qr.png
 
O magari:
 
.. parsed_literal::
 
   perl `overlay-qr.pl`_ 'Squirrel Girl is the best' `squirrel-girl.png`_ `squirrel-girl-qr.png`_
 
e ottenere questo:
 
.. image:: squirrel-girl-qr.png
 
Notare come la dimensione dei "puntini" ci adatti alla risoluzione
dell'immagine (anche se potrebbe essere più furba), e che la
trasparenza è rispettata.
 
.. [#] circa: mi sa che serve una fotocamera a risoluzione altina, e
       un programma di riconoscimento abbastanza permissivo
 
.. _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