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