Wednesday, September 20, 2006 4:36 PM bart

EAN-13 barcodes in C#

Introduction

A couple of days ago I posted about Code 39 barcode generation in C# (and here). Today another more famous barcode is the subject of my blogpost: EAN-13. EAN stands for European Article Number and is a way to number products. It's an extension of UPC (Universal Product Code). More information over here.

One particular interesting application of EAN-13 is the fact that ISBN (International Standard Book Number) codes are also represented as EAN-13 codes. I'll blog about this too.

Mission goal

As we did with Code 39, we want to be able to do something like this:

Ean13 barcode = new Ean13("9780201734843", null);
barcode.Paint().Save("c:\\temp\\test.png", ImageFormat.Png);

The second parameter to the Ean13 constructor is an optional title to display on top of the barcode (will be handy for ISBN numbers).

The skeleton

Let's start by defining the code skeleton of our Ean13 class:

class Ean13
{
   private Ean13Settings
settings;
   private string
code;
   private string
title;

  
public Ean13(string code, string title)
      :
this(code, title, new Ean13Settings
())
   {
   }

   public Ean13(string code, string title, Ean13Settings
settings)
   {
     
this
.settings = settings;
      this
.code = code;
      this
.title = title;

      if
(!CheckCode(code))
         throw new ArgumentException("Invalid EAN-13 code specified."
);
   }

   public Image Paint()
   {
      ...
   }
}

Again we have an additional class to specify settings, this time called Ean13Settings. It's nothing more than a container for a series of properties (<type> <property name> <default value>):

  • int BarCodeHeight = 120
  • int LeftMargin = 10
  • int RightMargin = 10
  • int TopMargin = 10
  • int BottomMargin = 10
  • int BarWidth = 2
  • Font Font = new Font(FontFamily.GenericSansSerif, 10)

A checksum checker

A helper method is required to check the code's checksum. This one is called from the constructor to ensure the code is valid. Here it is (CheckCode):

private bool CheckCode(string code)
{
   if (code == null
|| code.Length != 13)
      return false
;

   int
res;
   foreach (char c in
code)
      if (!int.TryParse(c.ToString(), out
res))
         return false
;

   char
check = (char)('0' + CalculateChecksum(code.Substring(0, 12)));
   return
code[12] == check;
}

Which is on its turn again relying on another helper method CalculateChecksum:

public static int CalculateChecksum(string code)
{
   if (code == null
|| code.Length != 12)
      throw new ArgumentException("Code length should be 12, i.e. excluding the checksum digit"
);

   int
sum = 0;
   for (int
i = 0; i < 12; i++)
   {
      int
v;
      if (!int.TryParse(code[i].ToString(), out
v))
         throw new ArgumentException("Invalid character encountered in specified code."
);
      sum += (i % 2 == 0 ? v : v * 3);
   }
   int
check = 10 - (sum % 10);
   return
check % 10;
}

The checksum is calculated as follows:

  1. Take the sum of all digits on even positions.
  2. Take the sum of all digits on odd positions and multiply by 3.
  3. Sum the results of step 1 and 2.
  4. Find the number (0-9) you have to add to the result from step 3 in order to make the remainder of the division by 10 equal to 0 (i.e. divisible by 10 :-)).

An example:

978020173484?
9+8+2+1+3+8     = 31
(7+0+0+7+4+4)*3 = 66
--------------------
                  97 + 3 = 100 (% 10 = 0)
9780201734843

CalculcateChecksum is defined as a public static method so that we can use it in order to calculate the EAN-13 code for a given ISBN later on (see later post).

EAN-13 coding explained

EAN-13 coding is not as easy as Code 39 coding. Therefore it's useful to have some explanation first. As you should have noticed by now, an EAN-13 code consists of 13 digits. These 13 digits are divided in 3 "groups":

9 780201 734843

The first digit is part of the number system, a code to represent the country of origin. In the code shown above, this number system is 978 or ISBN Book Land, the artifical country code for books. An example of a real country code is 54 for Belgium and Luxemburg (e.g. 5413356623321 on a CD of "De Kreuners"). The next group of 6 digits is the "left group" (remainder number system code + manufacturer code), the last group of 6 bytes is the "right group" (product code + checksum digit).

Encoding works as follows:

  1. The first digit (9 in the example above) determines the encoding scheme for the left group or the manufacturer code. This encoding is based on odd or even parity as explained below.
  2. The first group is encoded based on the encoding scheme found in step 1.
  3. The right group is encoded which is independent from the encoding scheme mentioned above.

Let's start by taking a look at the EAN parity encoding schemes:

oooooo
1
ooeoee
2
ooeeoe
3
ooeeeo
4
oeooee
5
oeeooe
6
oeeeoo
7
oeoeoe
8
oeoeeo
9
oeeoeo

In here, the first column represents the first digit of the code (e.g. 9). The second column indicated the encoding parity scheme for the next 6 digits (e.g. 780201), where e stands for even and o stands for odd.

The 6 next digits ("left group") are then encoded corresponding to the parity scheme:

# Odd     Even
0
0001101 0100111
1
0011001 0110011
2
0010011 0011011
3
0111101 0100001
4
0100011 0011101
5
0110001 0111001
6
0101111 0000101
7
0111011 0010001
8
0110111 0001001
9 0001011 0010111

For example, our code starts with 9, therefore the partity scheme for the first group of six is oeeoeo. The next digits are 780201:

  • 7 - o - 0111011
  • 8 - e - 0001001
  • 0 - e - 0100111
  • 2 - o - 0010011
  • 0 - e - 0100111
  • 1 - o - 0011001

In these codes, 0 stands for a white bar and 1 for a black bar. All bars have the same width (differs from Code 39 where bars can be narrow or wide) and there are no intercharacter gaps. Therefore, the encoding of 9 780201 will be:

011101100010010100111001001101001110011001

Notice all digit encodings for the left group start by 0 (white bar) and end by 1 (black bar). All partity codes start by o, meaning that the second number in the code is always encoded with Odd parity.

For the right group (product code and checksum digit) things are a little easier as the encoding doesn't follow any "parity scheme":

# Encoding
0
1110010
1
1100110
2
1101100
3
1000010
4
1011100
5
1001110
6
1010000
7
1000100
8
1001000
9 1110100

Now every encoding starts by 1 (black bar) and ends by 0 (white bar). In our example, the right part is "734843", resulting in:

100010010000101011100100100010111001000010

So, now we have the encoding of the left group and the encoding of the right group. These two encodings are concatenated but separated by guard bars (which are slightly higher than the others, see picture below):

101 011101100010010100111001001101001110011001 01010 100010010000101011100100100010111001000010 101

Static code table initialization

Some static code tables are required for convenience. These include the code patterns (010...) and the parity codes (eoe...):

static Dictionary<int, Pattern> codes = new Dictionary<int, Pattern>();
static Dictionary<int, Parity> parity = new Dictionary<int, Parity>();

Initialization code looks like this:

static Ean13()
{
   //      #  LEFT ODD   LEFT EVEN  RIGHT
   AddCode(0,
"0001101", "0100111", "1110010"
);
   AddCode(1,
"0011001", "0110011", "1100110"
);
   AddCode(2,
"0010011", "0011011", "1101100"
);
   AddCode(3,
"0111101", "0100001", "1000010"
);
   AddCode(4,
"0100011", "0011101", "1011100"
);
   AddCode(5,
"0110001", "0111001", "1001110"
);
   AddCode(6,
"0101111", "0000101", "1010000"
);
   AddCode(7,
"0111011", "0010001", "1000100"
);
   AddCode(8,
"0110111", "0001001", "1001000"
);
   AddCode(9,
"0001011", "0010111", "1110100"
);

   AddParity(0,
"ooooo"
);
   AddParity(1,
"oeoee"
);
   AddParity(2,
"oeeoe"
);
   AddParity(3,
"oeeeo"
);
   AddParity(4,
"eooee"
);
   AddParity(5,
"eeooe"
);
   AddParity(6,
"eeeoo"
);
   AddParity(7,
"eoeoe"
);
   AddParity(8,
"eoeeo"
);
   AddParity(9,
"eeoeo"
);
}

static void AddCode(int digit, string lhOdd, string lhEven, string
rh)
{
   Pattern p = new Pattern
();
   p.LhOdd = lhOdd; p.LhEven = lhEven; p.Rh = rh;
   codes.Add(digit, p);
}

static void AddParity(int digit, string
par)
{
   parity.Add(digit,
new Parity
(par));
}

Notice that the first odd partity ("o") indicator is omitted in the AddPartity calls because it's always odd (see previous remark). I could have dropped the 0...1 for left group codes and the 1...0 for right group codes as well but didn't do it for clarity.

Two helper classes

I've created two helper classes to aid in working with parities (Parity) and with encoding patterns (Pattern):

class Pattern
{
   private string
lhOdd;

   public string
LhOdd
   {
      get { return
lhOdd; }
      set { lhOdd = value
; }
   }

   private string
lhEven;

   public string
LhEven
   {
      get { return
lhEven; }
      set { lhEven = value
; }
   }

   private string
rh;

   public
string Rh
   {
      get { return
rh; }
      set { rh = value
; }
   }
}

class
Parity
{
   private string
par;

   internal Parity(string
par)
   {
      this
.par = par;
   }

   internal bool IsOdd(int
i)
   {
      return par[i] == 'o'
;
   }

   internal bool IsEven(int
i)
   {
      return par[i] == 'e'
;
   }
}

Painting it

Time to take a look at the painting code. The core Paint method is here:

private int top;

public
Image
Paint()
{
   top = settings.TopMargin;

   Graphics g = Graphics.FromImage(new Bitmap
(1, 1));

   int
width = (3 + 6 * 7 + 5 + 6 * 7 + 3) * settings.BarWidth + settings.LeftMargin + settings.RightMargin + (int)g.MeasureString(code[0].ToString(), settings.Font).Width;
   int
height = settings.BarCodeHeight;

   if (title != null
)
   {
      int h = (int
)g.MeasureString(title, settings.Font).Height;
      height += h;
      top += h;
   }

   Bitmap bmp = new Bitmap(width, height, PixelFormat
.Format32bppArgb);
   g =
Graphics
.FromImage(bmp);
   int
left = settings.LeftMargin;

   //LEFT GUARD
   left = DrawLeftGuard(settings, g, code[0].ToString(), left);

   //LEFT GROUP
   int first = int
.Parse(code[0].ToString());
   Parity
par = parity[first];
   string
digit = code[1].ToString();
   left = Draw(settings, g, left, digit, codes[
int.Parse(digit)].LhOdd);
//Odd
   for (int
i = 2; i <= 6; i++)
   {
      digit = code[i].ToString();
      Pattern p = codes[int
.Parse(digit)];
      left = Draw(settings, g, left, digit, (par.IsOdd(i - 2) ? p.LhOdd : p.LhEven));
   }

   //MIDDLE GUARD
   left = DrawCenterGuard(settings, g, left);

   //RIGHT GROUP
   for (int
i = 7; i <= 12; i++)
   {
      digit = code[i].ToString();
      Pattern p = codes[int
.Parse(digit)];
      left = Draw(settings, g, left, digit, p.Rh);
   }

   //RIGHT GUARD
   left = DrawRightGuard(settings, g, left);

   return
bmp;
}

Rather self-explaining code that should be. The used helper methods Draw and Draw*Guard are listed below:

private static Pen pen = new Pen(Color.Black);
private static Brush brush = Brushes
.Black;

private int Draw(Ean13Settings settings, Graphics g, int left, string digit, string
s)
{
   int h = (int
)(settings.BarCodeHeight * 0.8);
   g.DrawString(digit, settings.Font, brush, left, h + top);
   foreach (char c in
s)
   {
      if (c == '1'
)
         g.FillRectangle(brush, left, top, settings.BarWidth, h);
      left += settings.BarWidth;
   }
   return
left;
}

private int DrawLeftGuard(Ean13Settings settings, Graphics g, string digit, int
left)
{
   int h = (int
)(settings.BarCodeHeight * 0.8);
   g.DrawString(digit, settings.Font, brush, left, h + top);
   left += (
int
)g.MeasureString(digit, settings.Font).Width;
   //TITLE
   if (title != null
)
   g.DrawString(title, settings.Font, brush, left, settings.TopMargin);
   g.FillRectangle(brush, left, top, settings.BarWidth, settings.BarCodeHeight);
//1
   left += settings.BarWidth;
   left += settings.BarWidth;
//0
   g.FillRectangle(brush, left, top, settings.BarWidth, settings.BarCodeHeight);
//1
   left += settings.BarWidth;
   return
left;
}

private int DrawRightGuard(Ean13Settings settings, Graphics g, int
left)
{
   g.FillRectangle(brush, left, top, settings.BarWidth, settings.BarCodeHeight);
//1
   left += settings.BarWidth;
   left += settings.BarWidth;
//0
   g.FillRectangle(brush, left, top, settings.BarWidth, settings.BarCodeHeight);
//1
   left += settings.BarWidth;
   return
left;
}

private int DrawCenterGuard(Ean13Settings settings, Graphics g, int
left)
{
   left += settings.BarWidth;
//0
   g.FillRectangle(brush, left, top, settings.BarWidth, settings.BarCodeHeight);
//1
   left += settings.BarWidth;
   left += settings.BarWidth;
//0
   g.FillRectangle(brush, left, top, settings.BarWidth, settings.BarCodeHeight);
//1
   left += settings.BarWidth;
   left += settings.BarWidth;
//0
   return
left;
}

The result

Below is a sample EAN-13 bar code based on the ISBN (0-7356-1917-4) of the book "Microsoft Windows Internals Fourth Edition" by Mark E. Russinovich and David A. Solomon, a must-have for every Windows developer.

And the original code:

Oh yes, you can download the code here. Have fun!

Del.icio.us | Digg It | Technorati | Blinklist | Furl | reddit | DotNetKicks

Filed under:

Comments

# re: EAN-13 barcodes in C#

Wednesday, September 20, 2006 5:03 PM by Geert Roete

Great explanation Bart.

I've wrote a library application (with barcode support) which uses a font to create the barcodes. Just calculated with the checksum and then printed the text to screen, using the barcode Font. Also works for reporting Services. Works great!

# [Code Barre] - Cr?ation et impression de code barre avec C# | hilpers

Pingback from  [Code Barre] - Cr?ation et impression de code barre avec C# | hilpers