C# - Vonalkód rajzolása

forráskód letöltése
Cikkünkben annak járunk utána, hogy alkalmazásunk miként állíthatja elő az EAN-13-nak megfelelő vonalkód rajzát a megadott 13 számjegy alapján.
Először talán mélyedjünk el egy kicsit az EAN-13 vonalkód rejtelmeiben. Azt már rögtön az elején illik tisztázni, hogy egy vonalkódban nem a fekete, hanem a fehér terület a hasznos terület, hiszen a leolvasó ezt tudja csak érzékelni.
A vonalkódoknak többféle típusa létezik, de a legelterjedtebb az EAN-13 (EAN = Europaische Artikel Numerierung, Európai árucikkszámozás), mely 13 számjegyet kódol.
Ha jól megnézünk egy ilyen vonalkódot, akkor láthatjuk, hogy a baloldalon áll egy szám egymagában, utána pedig 6-6 szám. A szám csoportok között két-két elválasztó vonal van. Ezek a vonalak egy egységnyi vastagságúak. Minden számot két vonallal jelölnek, ezek vastagsága és helye változó. Mint majd látni fogjuk, a számokat összesen hét egységnyi területen kell ábrázolni. Az első számhoz nem tartozik vonal, ez speciálisan van kódolva.
Ha valaki megpróbálja a vonalakból kitalálni, hogy melyik számhoz milyen vonalak tartoznak, akkor hamar abba is hagyja, méghozzá valószínűleg csalódottan. Ez abból adódik, hogy az első 7 szám és az utolsó 6 másféle kódolással van megoldva. Ráadásul az első csoporton belül is lehetnek eltérések az azonos számjegyek kódolása között. Összesen háromféle kódolás van egy vonalkódon belül, ezeket jelöljük A-nak, B-nek és C-nek.
Szám A B C
0 0001101 0100111 1110010
1 0011001 0110011 1100110
2 0011001 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
Az első 6 számjegy (a különálló baloldali számot most nem számoljuk!) az A és B, míg az utolsó 6 számjegy a C oszlop szerint van kódolva. Az utolsó 6 számjegy kódolása nem ütközhet különösebb nehézségekbe, de mi van az első 6 számjeggyel? Honnan tudjuk eldönteni, hogy melyik számjegyet melyik oszlop szerint kell kódolni, valamint az első, különálló számjegy hogyan lesz kódolva? Ehhez megint szükségünk lesz egy táblázatra:
Első számjegy Kódolás módja
0 AAAAAA
1 AABABB
2 AABBAB
3 AABBBA
4 ABAABB
5 ABBAAB
6 ABBBAA
7 ABABAB
8 ABABBA
9 ABBABA
Már első látásra is nyilvánvaló, hogy ezeket a táblázatokat konstans tömbökben érdemes tárolni. A fenti táblázat adatait fel lehet úgy is fogni, mint egy 7 bites bináris szám, és így is fogjuk kezelni. Ahol a bit 1, oda húzunk vonalat, ahol 0, oda értelemszerűen nem.
Az első táblázat tehát így néz ki:
    private int[,] CodePage = {
       {0x0D, 0x27, 0x72},
       {0x19, 0x33, 0x66},
       {0x13, 0x1B, 0x6C},
       {0x3D, 0x21, 0x42},
       {0x23, 0x1D, 0x5C},
       {0x31, 0x39, 0x4E},
       {0x2F, 0x05, 0x50},
       {0x3B, 0x11, 0x44},
       {0x37, 0x09, 0x48},
       {0x0B, 0x17, 0x74}
     };
Mint látható, ez egy kétdimenziós tömb, és ez így tökéletesen használható is lesz számunkra. A fenti 0-kat és 1-eket átszámítottuk 16-os számrendszerbe! A második táblázat úgy néz ki, hogy az egyes számjegyekhez és pozíciókhoz tartozó kódolást egy-egy számmal jelöljük. Az A kódolás 0, a B=1 és a C=2.
    private int[,] CodeEAN13 = {
      {0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2},
      {0, 0, 1, 0, 1, 1, 2, 2, 2, 2, 2, 2},
      {0, 0, 1, 1, 0, 1, 2, 2, 2, 2, 2, 2},
      {0, 0, 1, 1, 1, 0, 2, 2, 2, 2, 2, 2},
      {0, 1, 0, 0, 1, 1, 2, 2, 2, 2, 2, 2},
      {0, 1, 1, 0, 0, 1, 2, 2, 2, 2, 2, 2},
      {0, 1, 1, 1, 0, 0, 2, 2, 2, 2, 2, 2},
      {0, 1, 0, 1, 0, 1, 2, 2, 2, 2, 2, 2},
      {0, 1, 0, 1, 1, 0, 2, 2, 2, 2, 2, 2},
      {0, 1, 1, 0, 1, 0, 2, 2, 2, 2, 2, 2}
    };
Létrehozunk egy belső változót LineWidth névvel, amiben a vonalak vastagságát adhatjuk meg pixelben. Ennek értéke a vonalkód képének nagyságát nagyban befolyásolja. Például ha a vonalvastagság 2, akkor az elkészített kép pontosan kétszerese lesz az 1-szeres vonalvastagsággal elkészített képnek.
A kirajzolás pozícióját a BaseX és BaseY változók határozzák meg.
A vonalkódok elkészítését a Form Paint eseményénél végezzük el. Van még egy függvényünk, a DrawLine, ami a megadott pozícióba rajzol egy vonalat a megfelelő vonalvastagsággal.
A három elválasztó vonalat megrajzoljuk a megfelelő pozíciókba. A DrawLine eljárás második paramétere egy logikai érték, ami azt határozza meg, hogy hosszú vonalat, vagy rövidet kell húzni. A hosszabb vonalak az elválasztó vonalak, míg a rövidebbek a számokat jelképező vonalak.
        DrawLine(BaseX+7, true);
        DrawLine(BaseX+9, true);
        DrawLine(BaseX+53, true);
        DrawLine(BaseX+55, true);
        DrawLine(BaseX+99, true);
        DrawLine(BaseX+101, true);
Az első számjegyet eltároljuk a cmode változóba. Az xpos változóba az aktuális vonalpozíciót tároljuk. A ty változó a számok y koordinátája, a cw pedig 7 vonal (vagy egy számjegy) kódolásához szükséges terület szélessége pixelben. Az első számot mindjárt ki is írhatjuk.
        int cmode = Convert.ToInt32(s[0])-48;
        int xpos = BaseX+10;
        int ty = BaseY+60;
        int cw = LineWidth * 7;
        g.DrawString(Convert.ToString(s[0]), font, brush, BaseX, ty);
Végigmegyünk a 2. számtól a 12-ig.
        for (int i = 1; i <= 12; i++)
        {
A num változóban eltároljuk az i. számot. A bitcode változóba kiolvassuk a CodePage táblázatból a számot jelképező vonalak bitkódját, majd a következő ciklusban (bit) azokba a pozíciókba, ahol 1-es bitet találunk, húzunk egy vonalat.
          num = Convert.ToInt32(s[i])-48;
          bitcode = CodePage[num, CodeEAN13[cmode, i-1]];
          cbit = 64;
          for (int bit = 1; bit <= 7; bit++)
          {
            if ((bitcode & cbit)>0)
            {
              DrawLine(xpos, false);
            }
            cbit /= 2;
            xpos++;            
          }
Ha megrajzoltuk a számot jelképező vonalakat, akkor megjelenítjük a számot is.
          tx=(xpos * LineWidth - cw) + (cw / 2) - 5;
          g.DrawString(Convert.ToString(s[i]), font, brush, tx, ty);
A második 6-os csoportot 5 pozícióval jobbra kell kezdeni, hiszen középen is van egy dupla választóvonal, amely 5 pozíciót foglal el.
          if (i==6)
          {
            xpos += 5;
          }