From 207844ee0d269545c9c885f34f6cf2395449358b Mon Sep 17 00:00:00 2001 From: dakkar Date: Thu, 10 Nov 2016 19:14:40 +0000 Subject: fancy QR codes articla --- additions/SW/fancy-qr-codes/kawaii-beholder-qr.png | Bin 0 -> 270527 bytes additions/SW/fancy-qr-codes/kawaii-beholder.jpg | Bin 0 -> 433149 bytes additions/SW/fancy-qr-codes/kawaii-beholder.png | Bin 0 -> 406802 bytes additions/SW/fancy-qr-codes/kawaii-beholder.xcf | Bin 0 -> 2448572 bytes additions/SW/fancy-qr-codes/overlay-qr.pl | 25 +++ additions/SW/fancy-qr-codes/squirrel-girl-qr.png | Bin 0 -> 91914 bytes additions/SW/fancy-qr-codes/squirrel-girl.jpg | Bin 0 -> 50173 bytes additions/SW/fancy-qr-codes/squirrel-girl.png | Bin 0 -> 183926 bytes additions/SW/fancy-qr-codes/squirrel-girl.xcf | Bin 0 -> 306256 bytes src/SW/fancy-qr-codes/.gitignore | 2 + src/SW/fancy-qr-codes/document.en.rest.txt | 195 ++++++++++++++++++++ src/SW/fancy-qr-codes/document.it.rest.txt | 196 +++++++++++++++++++++ src/SW/fancy-qr-codes/du2html.xsl | 1 + 13 files changed, 419 insertions(+) create mode 100644 additions/SW/fancy-qr-codes/kawaii-beholder-qr.png create mode 100644 additions/SW/fancy-qr-codes/kawaii-beholder.jpg create mode 100644 additions/SW/fancy-qr-codes/kawaii-beholder.png create mode 100644 additions/SW/fancy-qr-codes/kawaii-beholder.xcf create mode 100644 additions/SW/fancy-qr-codes/overlay-qr.pl create mode 100644 additions/SW/fancy-qr-codes/squirrel-girl-qr.png create mode 100644 additions/SW/fancy-qr-codes/squirrel-girl.jpg create mode 100644 additions/SW/fancy-qr-codes/squirrel-girl.png create mode 100644 additions/SW/fancy-qr-codes/squirrel-girl.xcf create mode 100644 src/SW/fancy-qr-codes/.gitignore create mode 100644 src/SW/fancy-qr-codes/document.en.rest.txt create mode 100644 src/SW/fancy-qr-codes/document.it.rest.txt create mode 120000 src/SW/fancy-qr-codes/du2html.xsl diff --git a/additions/SW/fancy-qr-codes/kawaii-beholder-qr.png b/additions/SW/fancy-qr-codes/kawaii-beholder-qr.png new file mode 100644 index 0000000..8e27c11 Binary files /dev/null and b/additions/SW/fancy-qr-codes/kawaii-beholder-qr.png differ diff --git a/additions/SW/fancy-qr-codes/kawaii-beholder.jpg b/additions/SW/fancy-qr-codes/kawaii-beholder.jpg new file mode 100644 index 0000000..9722164 Binary files /dev/null and b/additions/SW/fancy-qr-codes/kawaii-beholder.jpg differ diff --git a/additions/SW/fancy-qr-codes/kawaii-beholder.png b/additions/SW/fancy-qr-codes/kawaii-beholder.png new file mode 100644 index 0000000..d8e1862 Binary files /dev/null and b/additions/SW/fancy-qr-codes/kawaii-beholder.png differ diff --git a/additions/SW/fancy-qr-codes/kawaii-beholder.xcf b/additions/SW/fancy-qr-codes/kawaii-beholder.xcf new file mode 100644 index 0000000..6c7e385 Binary files /dev/null and b/additions/SW/fancy-qr-codes/kawaii-beholder.xcf differ diff --git a/additions/SW/fancy-qr-codes/overlay-qr.pl b/additions/SW/fancy-qr-codes/overlay-qr.pl new file mode 100644 index 0000000..b2cf1c2 --- /dev/null +++ b/additions/SW/fancy-qr-codes/overlay-qr.pl @@ -0,0 +1,25 @@ +#!/usr/bin/env perl +use strict; +use warnings; +use Data::QRCode; +use Imager::QRCode::Fancy; +use Imager; + +my ($string,$input_file,$output_file) = @ARGV; + +my $qr_code = Data::QRCode->new( + input_data => $string, + error_correction_level => 'H', +); + +print "width: ",$qr_code->width,"\n"; + +my $src_img = Imager->new( + file => $input_file, +) or die Imager->errstr; + +my $img = Imager::QRCode::Fancy::make({ + qr_code => $qr_code, + image => $src_img, +}); +$img->write(file=>$output_file); diff --git a/additions/SW/fancy-qr-codes/squirrel-girl-qr.png b/additions/SW/fancy-qr-codes/squirrel-girl-qr.png new file mode 100644 index 0000000..361e273 Binary files /dev/null and b/additions/SW/fancy-qr-codes/squirrel-girl-qr.png differ diff --git a/additions/SW/fancy-qr-codes/squirrel-girl.jpg b/additions/SW/fancy-qr-codes/squirrel-girl.jpg new file mode 100644 index 0000000..f721f6e Binary files /dev/null and b/additions/SW/fancy-qr-codes/squirrel-girl.jpg differ diff --git a/additions/SW/fancy-qr-codes/squirrel-girl.png b/additions/SW/fancy-qr-codes/squirrel-girl.png new file mode 100644 index 0000000..277c2f3 Binary files /dev/null and b/additions/SW/fancy-qr-codes/squirrel-girl.png differ diff --git a/additions/SW/fancy-qr-codes/squirrel-girl.xcf b/additions/SW/fancy-qr-codes/squirrel-girl.xcf new file mode 100644 index 0000000..a1420f6 Binary files /dev/null and b/additions/SW/fancy-qr-codes/squirrel-girl.xcf differ diff --git a/src/SW/fancy-qr-codes/.gitignore b/src/SW/fancy-qr-codes/.gitignore new file mode 100644 index 0000000..08d33d4 --- /dev/null +++ b/src/SW/fancy-qr-codes/.gitignore @@ -0,0 +1,2 @@ +/document.en.du.xml +/document.it.du.xml diff --git a/src/SW/fancy-qr-codes/document.en.rest.txt b/src/SW/fancy-qr-codes/document.en.rest.txt new file mode 100644 index 0000000..152b8a2 --- /dev/null +++ b/src/SW/fancy-qr-codes/document.en.rest.txt @@ -0,0 +1,195 @@ +============== +Fancy QR Codes +============== +:CreationDate: 2016-11-10 17:06:20 +:Id: SW/fancy-qr-codes +:tags: - software + - perl + +You may have seen `sylnsfar's "Artistic QR Code" library +`_. It produces non-standard QR +codes that incorporate arbitrary pictures, but are nonetheless +machine-readable [#]_. + +Of course, I had to understand how it worked ☺ + +It doesn't take much to find that the magic happens in the ``combine`` +function 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))) + +That code is full of magic numbers, and the multiple nested loops +don't really help legibility much. What's going on in there? + +To understand where those numbers came from, I studied `a QR code +tutorial `_. The most useful +page for our purposes is `the one that explains "function patterns" +`_. + +Everything in that function assumes a 3-pixel QR module, so: + +* the first block collects the placements of the alignment + patterns + + * but only if the code is large enough to need them (``ver > 1``) + * and ignoring those that would collide with finder patterns + (``(a==b==0) or (a==len(aloc)-1 and b==0) or (a==0 and + b==len(aloc)-1)`` means "top-left, top-right, bottom-left corners") + +* the second block replaces pixels in the QR code (``qr.putpixel``) + with pixels from the fancy picture (``bg.getpixel``) if: + + * they're not in timing patterns (``(i in (18,19,20)) or (j in + (18,19,20))``) + * they're not in 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)``) + * they're not in alignment patterns (``(i,j) in aligs``) + * and two more conditions I'll explain later + +So all those magic numbers define the placements of finder / timing / +alignment patterns: those *really* have to be there, otherwise the +scanning / recognition code won't even realise it's looking at a QR +code. + +The last two conditions are:: + + (i%3==1 and j%3==1) or (bg0.getpixel((i,j))[3]==0) + +The second one skips transparent pixels in the picture (``bg0`` is a +ARGB-version of the input picture ``bg``, and the pixel value at +position 3 is the alpha value). + +The first one makes sure to leave the central pixel of each 3×3 QR +module untouched: we want the QR data to remain there! + +.. _`the procedure outlined before`: + +So, another way to get the same result would be: + +* paint a normal black & white QR code +* layer the fancy picture on top of it, honouring the alpha channel +* paint all non-data modules on top +* paint the data modules on top, but only their central pixels + +Of course, it's not trivial to know whether a module is part of the +data or of a function pattern, but we're in luck: the |libqrencode|_ C +library `provides us with that information +`_! +It generates the QR code as a matrix of bytes, and each byte is a +bitfield:: + + MSB 76543210 LSB + |||||||`- 1=black/0=white + ||||||`-- data and ecc code area + |||||`--- format information + ||||`---- version information + |||`----- timing pattern + ||`------ alignment pattern + |`------- finder pattern and separator + `-------- non-data modules (format, timing, etc.) + +The Perl modules |text-qrcode|_ and |imager-qrcode|_ wrap that +library, but do not expose the internal byte matrix. So of course I +wrapped it again myself ☺ + +|alien-qrencode|_ uses |alien-base|_ to install |libqrencode|_, then +|data-qrcode|_ binds it via |inline-module|_ and provides a Perlish +API (see `the test file `_ for an +example). + +Now, we only need to deal with the actual images: let's use |imager|_, +which seems simple and flexible enough. The `actual code +`_ is maybe +too flexible, but the core of the logic is:: + + 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; + +which is a direct translation of `the procedure outlined +before`_. + +Once we've installed |alien-qrencode|_, |data-qrcode|_, and +|qr-fancy|_, we can say: + +.. parsed_literal:: + + perl `overlay-qr.pl`_ 'http://pokketmowse.deviantart.com/art/In-the-Kawaii-of-the-Beholder-177076230' `kawaii-beholder.png`_ `kawaii-beholder-qr.png`_ + +and get this: + +.. image:: kawaii-beholder-qr.png + +Or maybe: + +.. parsed_literal:: + + perl `overlay-qr.pl`_ 'Squirrel Girl is the best' `squirrel-girl.png`_ `squirrel-girl-qr.png`_ + +and get this: + +.. image:: squirrel-girl-qr.png + +Notice how the size of the "dots" adapts to the resolution of the +picture (although it could be smarter), and how transparency is +maintained. + +.. [#] more or less: I think you need a high-enough-resolution camera + and some pretty forgiving recognition code + +.. _myqrpy: https://github.com/sylnsfar/qrcode/blob/master/MyQR/myqr.py#L51 +.. |myqrpy| replace:: ``myqr.py`` + +.. _libqrencode: http://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 diff --git a/src/SW/fancy-qr-codes/document.it.rest.txt b/src/SW/fancy-qr-codes/document.it.rest.txt new file mode 100644 index 0000000..ab8b6e7 --- /dev/null +++ b/src/SW/fancy-qr-codes/document.it.rest.txt @@ -0,0 +1,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 +`_. 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`_ 'http://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: http://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 diff --git a/src/SW/fancy-qr-codes/du2html.xsl b/src/SW/fancy-qr-codes/du2html.xsl new file mode 120000 index 0000000..e2487e0 --- /dev/null +++ b/src/SW/fancy-qr-codes/du2html.xsl @@ -0,0 +1 @@ +../../../templates/du2html.xsl \ No newline at end of file -- cgit v1.2.3