Potreste aver visto la libreria "Artistic QR Code" di sylnsfar. Produce codici QR non-standard che incorporano immagini arbitrarie, ma sono comunque leggibili a macchina [6]
Ovviamente, dovevo capire come funziona ☺
- Non ci vuole molto a scoprire che la magia avviene nella funzione
-
combine in myqr.py:
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. Per i nostri scopi, la pagina più utile è quella che spiega i "function patterns".
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!
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! 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 mostra un esempio d'uso).
Ora, dobbiamo solo preoccuparci delle immagini: usiamo Imager, che sembra abbastanza semplice e potente. Il codice vero e proprio è 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 Imager::QRCode::Fancy, possiamo dire:
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:

O magari:
perl overlay-qr.pl 'Squirrel Girl is the best' squirrel-girl.png squirrel-girl-qr.png
e ottenere questo:

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