Nano QR kód generáló

Fórumok

Mivel több HUP-os fórumtárs is mikrokontrollerezik, ezért gondoltam megosztom, hátha másnak is hasznos lesz.

Összedobtam egy minimalista QR kód generálót ANSI C-ben, kifejezetten URL-ek kódolására:
- Szabad és Nyílt Forráskódú, MIT licenszű
- irtó kicsi (kb. 150 SLoC), egyetlen egy függvény csak
- nincs semmi függősége (még libc se, de még csak libc fejlécek sem kellenek neki)
- nem foglal memóriát egyáltalán
- csak pár bájtot eszik a veremből
- pofon egyszerű használni, nem kell konfigurálni
- egy 53 x 53 pixeles szürkeárnyalatos képet köp ki (csak 0 (előtér) ill. 255 (háttér) színekkel)
- a bemenete egy legfeljebb 128 bájtos URL (vagy hát bármilyen UTF-8 sztring igazából)

Példa használatra (csak példa, semmilyen domain sem query paraméter nincs a függvénykönyvtárban, bármi lehet):

#define NANOQR_IMPLEMENTATION
#include "nanoqr.h"

uint8_t image[53][53];
nanoqr(image, "https://gitlab.com/bztsrc/nanoqr?errcode=1234&errpos=1234&when=1234");

Ennyi. Aztán a megadott URL-re lehet rakni egy JavaScriptet, ami megcsócsálja és ember számára érthető hibaüzenetekké formálja az URL paraméterében megadott kódokat.

Technikai részletek: QR version 9-et generál (mert a 128 bájtos tárhely elfogadhatónak tűnt, a 53 x 53 pixeles méret meg elég kicsi ahhoz, hogy elférjen LCD-kre, de akár LED-es kijezlőkre is). A hibajavító kód QUARTILE (eggyel rosszabb csak, mint a legjobb opció), a maszkszint fixen 1-es, az adathalmaza pedig bájt (mert az alfanumerikusból hiányzik a ?, &, =, szóval nemigazán kódolható vele URL). Ezeket bedrótoztam az egyszerűség kedvéért, hogy ne kelljen semmit állítgatni. Ha valakinek ez nem felelne meg, akkor nekik ott a Nayuki QR kódgeneráló, de az többet eszik és bonyibb használni is.

Hozzászólások

Szep :) Tenyleg teljesen warning-mentesen fordul 4 architekturan is, olyan 1.5k-2k koruli kod lesz minden esetben. 

Hm... az mennyire bonyolitana el a kodot hogyha 2809 bytenyi 0-255 kep helyett ~352 ... ~371 bytenyi bitmaszkot allitana elo? 

Szep :)

Kösz! :)

Hm... az mennyire bonyolitana el a kodot hogyha 2809 bytenyi 0-255 kep helyett ~352 ... ~371 bytenyi bitmaszkot allitana elo?

Nem annyira vészes. A képet két lépcsőben állítja elő:
1. először 255-el tölti fel (133. sor), majd bizonyos pixeleket 0-ára állít, hogy a jól ismert alap mintázat rákerüljön (134.-150. sor).
2. az adatpixelek berakásakor (153.-191. sor) szintén csak 0-ra állítás van, illetve itt előfordul még XOR.
Ezek triviálisan átírhatók bitenkénti "dst &= ~x" és "dst ^= x" utasításokra, ez nem okoz problémát.

A gond inkább azzal van, hogy az 53 nem osztható 8-cal, így nem egyértelmű, mennyire kéne venni a sorhosszt. Lehet 56-ra (azaz 7 bájtra), de az az alignment miatt gondot okoz sok helyen (pl. ha BMP-be akarod menteni), vagy még több paddinggal 64-re, aminél meg lehetne akár uint64_t-t is használni egy sorra (mondjuk mikrokontrollereknél ritkán van 64 bites szó, de nem kizárt). Viszont az is gond még ennél, hogy minden rendszer máshogy veszi, balra vagy jobbra legyen-e a legalacsonyabb bit, mármint az endianess problémán túl (a PNG és a BMP pl. eltérő sorrendet használ). Bájtoknál nincs ilyen probléma, annál mindenki balról jobbra olvassa a sort, és az endianess is tök mindegy.

Aztán meg az is van, hogy macerás egy bitmapből kirakni a képet. Ha fájlba akarod menteni, akkor grayscale-nek simán megy így bájtokkal egy-az-egyben, ha indexelt palettás a formátumod, akkor elég minden pixelre gondolkodás nélkül &1-et nyomni és meg is vagy (az indexméret úgyis 8 bit), és RGB-re konvertálni is triviális (csak megismétled 3szor a bájtot ha true-color, vagy kétszer, ha hi-color), stb. Bitmapet sokkal több kóddal és macerávan lehetne csak átalakítani a cél pixelformátumra, ráadásul mint említettem volt, annál folyamatos probléma, hogy mi is a bitsorrend és hogy mennyi legyen a padding, meg még endianess függő is, mert nemcsak 1 bájt egy sor.

Szóval mindezeket megfontolva jutottam arra, hogy jobb, ha bájt alapú a kép, tisztább, szárazabb érzés.

Meg egy gyors szakmai kerdes :) Ez a kod mennyire altalanosithato, akar #define-okkal, akar mint fuggvenyparameterrel hogy ne csak Version 9-et tudjon hanem (akar) kisebbet is? Igen, az biztos ront a hatekonysagan hogyha fuggvenyparameterrel adod at a negyzet meretet, de a forditaskor ismert meret mint megoldas is erdekes/hasznos lehet. Akkor masok a GF(2^8) parameterek? 

Igen, az biztos ront a hatekonysagan hogyha fuggvenyparameterrel adod at a negyzet meretet

A QR kód nem egészen így működik. Vannak belőle "verziók", amik igazából nem is verziók, hanem pixelméretek és fixen a hozzájuk tartozó kapacitások.

Ez a kod mennyire altalanosithato, akar #define-okkal, akar mint fuggvenyparameterrel hogy ne csak Version 9-et tudjon hanem (akar) kisebbet is?

Hát lássuk...

1. A legtöbb statikus tömb marad, ahogy van. A "message" és az "interleved_output" tömb mérete változhat, mert ezek a bemenet méretétől függenek. A "message_parameters"-ből és a "version_info"-ból kétdimenziós tömböt kell csinálni, mert ezek az értékek QR verzió függőek.

2. Az üzenet formázása rész elé kell egy ciklus, ami kikeresi, hogy a bemenet méretéhez melyik verzió passzol. Vagy hát ez jöhet paraméterből, #define-ból akár, a lényeg, hogy a kapacitás függvényében kell egy QR verziószám (és indirekt így meghatározza a pixelméretet is, az ugyanis "(verzió-1)*4+21" fixen).

3. Ha csak kissebb QR kódokat akarsz, akkor az üzenet formázás rész marad ugyanúgy, ahogy van. Ha nagyobbakat is, akkor kell egy if a 97. sor köré, mert a magasabb verzióknál 3 bájtos az üzenet fejléce, nem 2, egyébként az üzenet formázás algoritmusa azoknál is marad ugyanaz (persze más, verziófüggő "message_parameters" értékekkel fog dolgozni, de a kódon nem kell módosítani).

4. A különböző dobozok kirajzolása rész megint marad ugyanaz, csak 53 helyett változóból kell jöjjön a méret, csak a ciklusok iterációs száma lesz más (illetve az is csak kettőnél, a többinél csak a pozíció változik).

5. Az adatpixelek berakása rész szintén marad ugyanaz, mint most, megint csak az 53 helyett kell változó, egyébként mást nem kell módosítani.

Ami a legnagyobb módosítás lenne szvsz, hogyha dinamikusan kezeled a verziót, akkor oda kell figyelni a maszkokra. Ez most elegánsan fixre lett véve az egyszerűség nevében, de egy rendes implementációban ki kéne generálni az összes lehetséges maszkkal a QR kódot, mindhez hibaértéket számolni, és a legalacsonyabb pontszámú változatot kéne visszaadni. Ha dinamikus a méret, akkor (mivel a deketálódobozok mérete fix) bizonyos méretváltozatoknál drasztikusan megnő az esélye annak, hogy a dobozokhoz hasonló minta alakulhat ki az adatpixelek részben, azért fontos ilyenkor a korrekt maszk kezelés. Bár a tesztjeim során úgy vettem észre, a legtöbb olvasót nem zavarja, simán megeszik, ha az adatpixelek részben felbukkan itt-ott egy detektálódoboz minta, nem sikerült még olyan QR kódot előállítanom, ahol ez gondot okozott volna (de biztos létezik ilyen, gondolom nem ok nélkül rakták bele a QR spec-be a maszkokat).

Gratulálok, szép munka!

 

A detektálhatóság miatt nem kétszínűnek kellene lennie az eredményképnek? Jó kontrasztos, fekete-fehér?

WAT?

Egyetlen fuggveny, ami a parameterul kapott 2D tombbe beteszi a parameterul kapott stringbol generalt QR kodot. Aztan hogy a 0-at es 255-ot hogy jelenited meg, rad van bizva. Azt is leirta, miert nem 1 bit egy pixel 1 byte helyett.

Ha 0 es 255 van benne, mi nem ketszinu? Az meg, hogy hopapiros nyomtato vagy kisnyuszik csinalnak belole a vegen beolvashato kepet, nem ezen a szinten dol el. Ha ott nem eleg kontrasztos, az baj ugyan, de nem a C kod hibaja.

A strange game. The only winning move is not to play. How about a nice game of chess?