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:
- Take the sum of all digits on even positions.
- Take the sum of all digits on odd positions and multiply by 3.
- Sum the results of step 1 and 2.
- 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:
- 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.
- The first group is encoded based on the encoding scheme found in step 1.
- 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:
0
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: C# 2.0