Codici QR decorati

Altre lingue

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.

[6]

circa: mi sa che serve una fotocamera a risoluzione altina, e un programma di riconoscimento abbastanza permissivo

DateCreato: 2016-11-10 17:06:20 Ultima modifica: 2023-02-10 12:45:24