==================
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.
.. _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