EAN-13 barcode rajzolás Qt alapokon

Már lassan 2 éve, hogy egyetlen sor kódot sem írtam kedvenc keretrendszeremben. Nagyon lelombozó érzés rájönni, hogy mennyit fejlődött azóta és a gyakorlat hiányával milyen gyorsan felejt az ember... Ezt ellensúlyozandó az utóbbi hetekben, ha volt időm, próbáltam felfrissíteni azt a keveset, amit anno megtanultam. Mivel van egy régóta dédelgetett tervem, a héten pedig mahosilver-nek hála megjön a vonalkódolvasó is, gondoltam utána járok, mi fán is terem ez a jelölés. Ma pedig összegányoltam egy QGraphicsView alapú EAN-13 barcode rajzolót, csak úgy PoC módon :)

Persze korántsem tökéletes, nem szép és nem biztos, hogy kellően gyors, de még úgyis alakítok rajta.

Lássuk!

Az EAN-13 ... ah, Wikipedia leírja szépen... minket az alábbi táblázatok érdekelnek elsősorban:


EAN-13 struktúra
====================================
| 1. jegy | 2-6. jegy | 7-13. jegy |
====================================
|    0    | LLLLLL    | RRRRRR     |
------------------------------------
|    1    | LLGLGG    | RRRRRR     |
------------------------------------
|    2    | LLGGLG    | RRRRRR     |
------------------------------------
|    3    | LLGGGL    | RRRRRR     |
------------------------------------
|    4    | LGLLGG    | RRRRRR     |
------------------------------------
|    5    | LGGLLG    | RRRRRR     |
------------------------------------
|    6    | LGGGLL    | RRRRRR     |
------------------------------------
|    7    | LGLGLG    | RRRRRR     |
------------------------------------
|    8    | LGLGGL    | RRRRRR     |
------------------------------------
|    9    | LGGLGL    | RRRRRR     |
====================================

Számjegyek kódolása:
========================================
| Szám   | L-kód   | G-kód   | R-kód   |
----------------------------------------
|   0    | 0001101 | 0100111 | 1110010 |
----------------------------------------
|   1    | 0011001 | 0110011 | 1100110 |
----------------------------------------
|   2    | 0010011 | 0011011 | 1101100 |
----------------------------------------
|   3    | 0111101 | 0100001 | 1000010 |
----------------------------------------
|   4    | 0100011 | 0011101 | 1011100 |
----------------------------------------
|   5    | 0110001 | 0111001 | 1001110 |
----------------------------------------
|   6    | 0101111 | 0000101 | 1010000 |
----------------------------------------
|   7    | 0111011 | 0010001 | 1000100 |
----------------------------------------
|   8    | 0110111 | 0001001 | 1001000 |
----------------------------------------
|   9    | 0001011 | 0010111 | 1110100 |
========================================

Innen már egész egyszerű dolgunk van:
Megnézzük az első számjegyet, ami megmutatja a többi 12 kódolását. A 2-tól a 13. jegyig gyalogolva a 2. táblázatból kikeressük a megfelelő számjegy-kódoláshoz tartozó bitmintát, majd ahol 1-es van, oda húzunk egy vonalat. Hogy a vonalkódolvasót segítsük a 2. jegy elé (az elsőt nem ábrázoljuk vonalakkal) és a 13. jegy után határolóvonalat, a 6. és 7. jegy közé pedig egy elválasztót firkálunk.

A kód:

Qt alatti rajzoláshoz a legújabb ajánlások a QGraphicsView widgetet javasolják, így erre fogok építkezni. Származtatok egy saját BarcodeDrawView osztályt a következőképp:


#ifndef BARCODEDRAWVIEW_H
#define BARCODEDRAWVIEW_H

#include <QGraphicsView>

class BarcodeDrawView : public QGraphicsView
{
public:
    BarcodeDrawView(QWidget *parent = 0);
    ~BarcodeDrawView();

    void drawBarcode();
    void setNumber(const QString& num);

private:
    QGraphicsScene *_scene;
    QString *_barcodeNum;
    const char *_encodingMask;

    void initEncoder();
    QBitArray *getBitmaskForDigit(const QChar &num, const char &codeType);
    void drawBitPattern(const QBitArray &, int, int);
};

#endif // BARCODEDRAWVIEW_H

Mint látható az osztály 2 darab publikus eljárást tartalmaz. A

setNumber(const QString& num)

eljárás állítja be a

_barcodeNum

változó értékét, majd inicializálja a "kódolót" az

initEncoder()

eljárás meghívásával.


void BarcodeDrawView::setNumber(const QString &barcodeNum)
{
    if(barcodeNum.isNull() || barcodeNum.isEmpty())
       return;

    this->_barcodeNum = new QString(barcodeNum);
    initEncoder();
}

Az

initEncoder()

eljárás megvizsgálja a beállított vonalkód-számsor első jegyét majd az

_encodingMask

változóba helyezi a megfelelő kódolási mintát.


void BarcodeDrawView::initEncoder()
{
    // The first digit determines the encoding of the other 12 digits,
    // so we set up the encoding masks here
    QList<QString> encodingMasks;
    encodingMasks
          << "LLLLLLRRRRRR"   // 0
          << "LLGLGGRRRRRR"   // 1
          << "LLGGLGRRRRRR"   // 2
          << "LLGGGLRRRRRR"   // 3
          << "LGLLGGRRRRRR"   // 4
          << "LGGLLGRRRRRR"   // 5
          << "LGGGLLRRRRRR"   // 6
          << "LGLGLGRRRRRR"   // 7
          << "LGLGGLRRRRRR"   // 8
          << "LGGLGLRRRRRR";  // 9

    int firstDigit = (this->_barcodeNum->at(0)).digitValue();

    if(firstDigit < 0 || firstDigit > 9)
       return;

    QString encodeStr = encodingMasks.at(firstDigit);
    QByteArray tmpStr = encodeStr.toAscii();
    this->_encodingMask = tmpStr.data();
}

A

drawBarcode()

eljárás meghívásakor az a kódolási mintának megfelelően minden számjegyre előállítja a hozzátartozó bitmintát majd a

_scene

változó által kijelölt QGraphicsScene példánnyal kirajzoltatja azt.


void BarcodeDrawView::drawBarcode()
{
    int x = 0;
    int height = 100;

    QBitArray *bitmaskForDigit; // NOTE!!! QBitArray is zero indexed, e.g. the first bit is at index 0, NOT 1!!!
    QBitArray borderBits(3);
    QBitArray separatorBits(5);

    // The border bars are always represented as '101'
    borderBits.toggleBit(0);
    borderBits.toggleBit(2);

    // The middle separator is always represented as '01010'
    separatorBits.toggleBit(1);
    separatorBits.toggleBit(3);

    int codeLength(12);

    drawBitPattern(borderBits, x, height+10);
    x += 3;

    for( int i=0; i<codeLength; i++ ) {

       bitmaskForDigit = getBitmaskForDigit(this->_barcodeNum->at(i+1), this->_encodingMask[i]);

       if( i==6 ) {
          drawBitPattern(separatorBits, x, height+5);
          x += 5;
       }

       drawBitPattern(*bitmaskForDigit, x, height);
       delete bitmaskForDigit;
       x += 7;
    }

    drawBitPattern(borderBits, x, height+10);
    update();
}

Mint látható a

drawBarcode

eljárás 2 segédet használ:

void drawBitPattern(const QBitArray &, int, int)

és

QBitArray *getBitmaskForDigit(const QChar &num, const char &codeType)

Utóbbi függvény visszaadja az első paraméterben átadott 1 darab karakternek a második paraméterben átadott kódolás ('L', 'G' vagy 'R') szerinti bitmintáját:


QBitArray *BarcodeDrawView::getBitmaskForDigit(const QChar& digitAsChar, const char &codeType)
{
    QBitArray *bitmaskForDigit = new QBitArray(7, false); // Resulting bit mask will be stored here
    QBitArray *tmpBitmask = new QBitArray(7, false); // Temporary bit array used by the L code transformations
    int currentDigit = digitAsChar.digitValue();

    // Always produce the L code first, since both R and G can be derived from it
    bitmaskForDigit->setBit(6, true);  // The last bit of the L code is always 1

    switch(currentDigit) {
    case 0:
       bitmaskForDigit->toggleBit(3);
       bitmaskForDigit->toggleBit(4);
       break;
    case 1:
       bitmaskForDigit->toggleBit(2);
       bitmaskForDigit->toggleBit(3);
       break;
    case 2:
       bitmaskForDigit->toggleBit(2);
       bitmaskForDigit->toggleBit(5);
       break;
    case 3:
       bitmaskForDigit->toggleBit(1);
       bitmaskForDigit->toggleBit(2);
       bitmaskForDigit->toggleBit(3);
       bitmaskForDigit->toggleBit(4);
       break;
    case 4:
       bitmaskForDigit->toggleBit(1);
       bitmaskForDigit->toggleBit(5);
       break;
    case 5:
       bitmaskForDigit->toggleBit(1);
       bitmaskForDigit->toggleBit(2);
       break;
    case 6:
       bitmaskForDigit->toggleBit(1);
       bitmaskForDigit->toggleBit(3);
       bitmaskForDigit->toggleBit(4);
       bitmaskForDigit->toggleBit(5);
       break;
    case 7:
       bitmaskForDigit->toggleBit(1);
       bitmaskForDigit->toggleBit(2);
       bitmaskForDigit->toggleBit(3);
       bitmaskForDigit->toggleBit(5);
       break;
    case 8:
       bitmaskForDigit->toggleBit(1);
       bitmaskForDigit->toggleBit(2);
       bitmaskForDigit->toggleBit(4);
       bitmaskForDigit->toggleBit(5);
       break;
    case 9:
       bitmaskForDigit->toggleBit(3);
       bitmaskForDigit->toggleBit(5);
       break;
    default:
       break;
    }

    //The R code is the complement of the L code so
    if(codeType == 'R') {
       for(int i=0; i<bitmaskForDigit->size(); i++) {
          bitmaskForDigit->toggleBit(i);
       }
    }
    else if(codeType == 'G') {
       // Produce the R code
       for(int i=0; i<bitmaskForDigit->size(); i++) {
          bitmaskForDigit->toggleBit(i);
       }

       //Reverse it to get the G code
       for(int i=bitmaskForDigit->size()-1; i>=0; i--) {
          tmpBitmask->setBit(i, bitmaskForDigit->at(bitmaskForDigit->size()-i-1));
       }

       bitmaskForDigit = tmpBitmask;
       delete tmpBitmask;
    }

    return bitmaskForDigit;
}

Aki idáig még nem fordult le a székről kínjában, az ne csüggedjen, már nincs sok hátra! :)
Az EAN-13-ban alkalmazott háromféle kódolásnak van egy nagyszerű tulajdonsága, amit rögtön ki is használunk: az 'L' kódból egyszerű műveletekkel megkapható az 'R' és a 'G' kódolás is, mépedig az alábbiak szerint:

R = ~L

, ahol '~' a komplemensképzés jele

G = inverz(~L) = inverz(R)

.

Tehát, teljesen mindegy, hogy adott számjegynek milyen kódolása kell, először mindig az 'L' szerintit állítjuk elő, majd ebből transzformációkkal előállítjuk a nekünk szükségest. /Itt jegyezném meg, hogy legyen áldott a neve annak, aki a QBitArray-t kitalálta! :D/

A visszaadott

bitmaskForDigit

változó tartalmát átadva a

drawBitPattern(const QBitArray &, int, int)

eljárásnak, az szépen megjeleníti a 2. paraméterében lévő X koordinátán a 3. paraméterében kapott magassággal a megfelelő vonalkód-részletet:


void BarcodeDrawView::drawBitPattern(const QBitArray& bitPattern, int x, int height)
{
    QPen pen;
    int patternSize = bitPattern.size();

    for(int i=0; i<patternSize; i++) {

       if(bitPattern.at(i))
       {
          pen.setColor(Qt::black);
       }
       else
       {
          pen.setColor(Qt::white);
       }

       (this->_scene)->addLine(x, 0, x, height, pen);
       x++;
    }
}

Ennyi az egész :)
Az osztályt !szépre faragva! kapunk egy újrafelhasználható komponenst, amit szinte bármilyen vonalkód előállítást alkalmazó projektben felhasználhatunk!

MEGJEGYZÉS: Kérem vegyétek figyelembe, hogy közel 2 éve nem fejlesztettem sem C++-ban, sem Qt használatával, így előfordulhatnak ordas nagy baromságok. Az építő jellegű kritikát szívesen fogadom, ha igény van rá, természetesen ennek megfelelően átalakítom eme demonstrációs alkalmazást.

Update:

Megtaláltam a hibát, ami miatt rossz vonalkódot generált a kód. Itt van két kép, az egyik 1px vastag vonallal, a másik 2px vastag vonallal és hozzátartozó x irányú eltolással készült. Nem tudom, hogy az olvasók mennyire érzékenyek a vonalvastagságra...

Hozzászólások

Oooo, ha az elso szamot nem abrazolod, honnet tudja az olvaso, h milyen kodolast olvasott, vagyis mi volt az elso szam?
--


()=() Ki oda vagyik,
('Y') hol szall a galamb
C . C elszalasztja a
()_() kincset itt alant.

sima barcode csomag segíthet tesztelni, ps-t ír.


echo 566555016040 | barcode -e "ean13" | ps2pdf - kimenet.pdf