Click here to Skip to main content
15,900,258 members
Articles / Desktop Programming / Windows Forms
Article

GenCode128 - A Code128 Barcode Generator

Rate me:
Please Sign up or sign in to vote.
4.95/5 (101 votes)
10 Jun 2006CPOL7 min read 843.9K   56.5K   189   156
Create Code128 barcodes for WinForms or ASP.NET.

Sample Image - GenCode128.png

Introduction

This is a simple library that lets you do one thing very easily: generate an Image for a Code128 barcode, with a single line of code. This image is suitable for print or display in a WinForms application, or even in ASP.NET.

Background

Support for other barcode symbologies can be easily found because they're easy to create. Basic Code39 barcodes, for example, can be produced using nothing but a simple font, and it seems like you can't hardly swing a cat without hitting a Code39 font or imageset for download.

However, Code39 has deficiencies. The naive font-derived output doesn't afford any error checking. In their standard configuration, most scanners can only recognize 43 different symbols (configuration changes may fix this, but you may not have that luxury with your users' choice of hardware configuration). And Code39 is fairly verbose, requiring a large amount of space for a given message.

Code128, on the other hand, has out-of-the-box support for all 128 low-order ASCII characters. It has built-in error detection at the character and message level, and is extremely terse. Unfortunately, producing a reasonable encoding in this symbology is an "active" process. You must analyze the message for the optimal encoding strategy, and you must calculate a checksum for the entire message.

In my case, it was an absolute necessity to encode control characters. My application's design demanded that the user be able to trigger certain menu shortcut keys by scanning a barcode on a page. But since I've got no control over the scanners that my users are employing, the Code39EXT symbology wasn't a good candidate.

A search yielded several Code128 controls, but these had two important deficiencies. First, they were controls. That would be fine if I just wanted to produce a barcode on the page, but I wanted to use them as images in a grid, so I needed a means of obtaining a raw GDI+ Image object. Second, they were fairly expensive -- enough that a license covering all of our developers would cost more than my time to roll my own.

Using the code

Basic usage

As promised, producing the barcode Image is as simple as a single line of code. Of course, you'll still need code lines necessary to put that Image where it needs to go.

Here's a chunk from the sample application. In it, I respond to a button click by generating a barcode based on some input text, and putting the result into a PictureBox control:

C#
private void cmdMakeBarcode_Click(object sender, System.EventArgs e)
{
   try
   {
      Image myimg = Code128Rendering.MakeBarcodeImage(txtInput.Text, 
                                         int.Parse(txtWeight.Text), true);
      pictBarcode.Image = myimg;
   }
   catch (Exception ex)
   {
      MessageBox.Show(this, ex.Message, this.Text);
   }
}

Obviously, the meat of this is the first line following the try. For the caller, there's just one interesting method in the whole library:

C#
GenCode128.Code128Rendering.MakeBarcodeImage( string InputData, 
                              int BarWeight, bool AddQuietZone )

(That's the GenCode128 namespace, in a static class called Code128Rendering). Since this is a static class, you don't even need to worry about instantiating an object.

There are three parameters:

  • string InputData

    The message to be encoded

  • int BarWeight

    The baseline width of the bars in the output. Usually, 1 or 2 is good.

  • bool AddQuietZone

    If false, omits the required white space at the start and end of the barcode. If your layout doesn't already provide good margins around the Image, you should use true.

You can get a feel for the effect of these values by playing with the sample application. While you're at it, try printing out some samples to verify that your scanners can read the barcodes you're planning to produce.

Printing

A barcode library is pretty much useless if you don't use it to print. You can't very well scan the screen. It's been quite a long time since I had printed anything from a Windows application, and it took a little while to remember how. If you need a quick reminder like I did, take a look at the event that the demo app's Print button calls.

What you should be aware of

First of all, I don't have any exception handling built into the library itself. For your own safety, you should put try/catch blocks around any calls to the library.

The solution comprises three projects. One is the library itself, one is the demo application, and then there is the unit test code. I used NUnit by way of TestDriven.net. If you don't have that, then Visual Studio is going to complain. Since it's just test code, you can safely drop it and still use the library successfully.

Another point is the required vertical height of the barcode. The spec requires that the image be either 1/4" or 15% of the overall width, whichever is larger. Since I don't have any control of the scaling you're using when outputting the image, I didn't bother implementing the 1/4" minimum. This means that for very short barcode, the height might be illegally small.

Code128's high information density derives partly from intelligently shifting between several alternate codesets. Obtaining the optimal encoding is, as far as I can tell, a "hard" problem (in the sense of discrete math's non-polynomial problems like the Traveling Salesman). The difference between the best possible solution and my pretty good one should be small, and doesn't seem worth the effort.

My algorithm for obtaining a "pretty good" encoding involves a single-character look-ahead.

  • If the current character can be encoded in the current codeset, then it just goes ahead and does so.
  • Otherwise, if the next character would be legal in this codeset, it temporarily shifts into the alternate codeset for just this character.
  • Else, both this character and the next would need a shift, so instead it changes codesets to avoid the shifts.

A similar decision has to be made about which codeset to start the encoding in. To solve this, I check the first two characters of the string, letting them "vote" to see which codeset they prefer. If there's a preference for one codeset, I choose it; otherwise, I default to codeset B. This is because codeset A allows uppercase alpha-numerics plus control characters, while codeset B allows upper and lower alpha-numerics; I assume that you're more likely to want lowercase than control characters.

Finally, there is an optimization in the Code128 spec for numeric-only output that I didn't take advantage of. Long runs of digits can be encoded in a double density codeset. Accounting for this in my already-ugly look-ahead algorithm would have taken a lot more effort -- for a feature that I don't need. But if you have lots of digits and space is tight, you might look at enhancing this.

Points of interest

I suppose that anyone examining my source code will wonder why in the world my table of bar width has two extra columns. In any sane universe, there should be six columns rather than eight. This was a compromise to allow for the oddball STOP code, which has seven bars rather than six. I could have implemented a special case for just this code, but that was too distasteful.

Instead, I added extra zero-width columns to everything else, making the data equivalent in all cases. For every bar that comes up with a zero width, nothing is output, so nothing is harmed.

Of course, the choice between six or eight columns just begs the question: why not seven? This is to accommodate an optimization in the rendering code. By pre-initializing the entire image to white, I can avoid needing to draw the white bars. Thus, I grab bar widths in groups of two. The first one is the black one, and I draw that normally (unless its width is zero). The second one is white, but there's white already there, so I can just skip the area that would have occupied.

If anyone's keeping score, this is my second attempt at truly Test-Driven Development. On the whole, I think this worked out pretty well. Especially, at the lower levels of code, I'm pretty confident of the code. However, the highest level -- where the output is just an Image -- seemed impractical to be tested in this way.

One problem I've got with the TDD, though, is code visibility. Optimally, this library should have exactly one publicly-visible class, with one public method. However, my test code forces me to expose all of the lower-level stuff that the end caller should never know about. If TDD in C# has developed a good answer to that, I haven't yet stumbled upon it.

History

  • 1.0.0.0 - 2006-06-10

    Initial release.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Web Developer
United States United States
Chris has been developing software since the 80s, and is currently a project manager at SHI in New Jersey where he builds ecommerce applications. Technologically he concentrates primarily on OO and relational databases, and is currently working in .Net technologies. He's also co-author of "Inside ISAPI".

He thinks that programming is easy, the really challenging work is figuring out what an application needs to do in the first place.

Comments and Discussions

 
AnswerRe: print style Pin
Chris Wuestefeld21-Sep-07 8:25
Chris Wuestefeld21-Sep-07 8:25 
GeneralCode C implementation Pin
gspadari4-Sep-07 17:25
gspadari4-Sep-07 17:25 
GeneralRe: Code C implementation Pin
Govindv23-Jun-09 4:32
professionalGovindv23-Jun-09 4:32 
GeneralRe: Code C implementation Pin
Chris Wuestefeld23-Jun-09 4:47
Chris Wuestefeld23-Jun-09 4:47 
GeneralRe: Code C implementation Pin
Govindv28-Jun-09 19:43
professionalGovindv28-Jun-09 19:43 
GeneralRe: Code C implementation Pin
KelceyC8-Jul-09 8:22
KelceyC8-Jul-09 8:22 
GeneralRe: Code C implementation Pin
KelceyC23-Jul-09 6:59
KelceyC23-Jul-09 6:59 
GeneralRe: Code C implementation Pin
Member 33176594-Jun-10 15:49
Member 33176594-Jun-10 15:49 
I've made more modifications to the code. I've also included some "smart" code to know when not to switch to codeC. The noticeable case is a barcode like: "A44A" or "A444" You save a bar by not having to switch twice.


using System;
using System.Collections.Generic;
using System.Text;

namespace GenCode128
{
public enum CodeSet
{
CodeA, CodeB, CodeC
}

/// <summary>
/// Represent the set of code values to be output into barcode form
/// </summary>
public class Code128Content
{
private readonly int[] mCodeList;

/// <summary>
/// Create content based on a string of ASCII data
/// </summary>
/// <param name="AsciiData">the string that should be represented</param>
public Code128Content(string AsciiData)
{
mCodeList = StringToCode128(AsciiData);
}

/// <summary>
/// Provides the Code128 code values representing the object's string
/// </summary>
public int[] Codes
{
get { return mCodeList; }
}

/// <summary>
/// Transform the string into integers representing the Code128 codes
/// necessary to represent it
/// </summary>
/// <param name="AsciiData">String to be encoded</param>
/// <returns>Code128 representation</returns>
private static int[] StringToCode128(string AsciiData)
{
// turn the string into ascii byte data
byte[] asciiBytes = Encoding.ASCII.GetBytes(AsciiData);

// decide which codeset to start with
CodeSet currcs = Code128Code.GetBestCodeSet(asciiBytes, 0);

// set up the beginning of the barcode
List<int> codes = new List<int>();
codes.Add(Code128Code.StartCodeForCodeSet(currcs));

// add the codes for each character in the string
for (int i = 0; i < asciiBytes.Length; i++)
{
codes.AddRange(Code128Code.CodesForChar(asciiBytes, i, ref currcs));
//Jump 2 bytes if Code C is active
if (currcs == CodeSet.CodeC)
i++;
}

// calculate the check digit
int checksum = codes[0];
for (int i = 1; i < codes.Count; i++)
{
checksum += i * codes[i];
}
codes.Add(checksum % 103);

codes.Add(Code128Code.StopCode());

int[] result = codes.ToArray();
return result;
}
}

/// <summary>
/// Static tools for determining codes for individual characters in the content
/// </summary>
public static class Code128Code
{
#region Constants

private const int cSHIFT = 98;
private const int cCODEA = 101;
private const int cCODEB = 100;
private const int cCODEC = 99;

private const int cSTARTA = 103;
private const int cSTARTB = 104;
private const int cSTARTC = 105;
private const int cSTOP = 106;

#endregion

/// <summary>
/// Determines the best starting code set based on the the first two
/// characters of the string to be encoded
/// </summary>
/// <param name="csa1">First character of input string</param>
/// <param name="csa2">Second character of input string</param>
/// <returns>The codeset determined to be best to start with</returns>
private static CodeSet GetBestStartSet(Code128Code.CodeSetAllowed csa1, Code128Code.CodeSetAllowed csa2)
{
int vote = 0;

vote += (csa1 == Code128Code.CodeSetAllowed.CodeA) ? 1 : 0;
vote += (csa1 == Code128Code.CodeSetAllowed.CodeB) ? -1 : 0;
vote += (csa2 == Code128Code.CodeSetAllowed.CodeA) ? 1 : 0;
vote += (csa2 == Code128Code.CodeSetAllowed.CodeB) ? -1 : 0;

return (vote > 0) ? CodeSet.CodeA : CodeSet.CodeB; // ties go to codeB due to my own prejudices
}

/// <summary>
/// Determines the best starting code set based on the the char1 and char2
/// </summary>
/// <returns>The codeset determined to be best to start with</returns>
public static CodeSet GetBestCodeSet(byte[] asciiBytes, int position)
{
int char1 = asciiBytes.Length > position ? asciiBytes[position] : -1;
int char2 = asciiBytes.Length > position+1 ? asciiBytes[position + 1] : -1;
// check for Code C: If first 2 bytes are numeric, then we use Code C
if (asciiBytes.Length > position + 1)
{
if (Code128Code.IsCodeCAllowedForChar(asciiBytes[position], asciiBytes[position+1]))
return CodeSet.CodeC;
}
Code128Code.CodeSetAllowed csa1 = char1 != -1
? Code128Code.CodesetAllowedForChar(char1)
: Code128Code.CodeSetAllowed.CodeAorB;
Code128Code.CodeSetAllowed csa2 = char2 != -1
? Code128Code.CodesetAllowedForChar(char2)
: Code128Code.CodeSetAllowed.CodeAorB;
return GetBestStartSet(csa1, csa2);
}


/// <summary>
/// Get the Code128 code value(s) to represent an ASCII character, with
/// optional look-ahead for length optimization
/// </summary>
/// <param name="position"></param>
/// <param name="asciiBytes"></param>
/// <param name="CurrCodeSet">The current codeset, that the returned codes need to follow;
/// if the returned codes change that, then this value will be changed to reflect it</param>
/// <returns>An array of integers representing the codes that need to be output to produce the
/// given character</returns>
public static int[] CodesForChar(byte[] asciiBytes, int position, ref CodeSet CurrCodeSet)
{
int CharAscii = asciiBytes[position];
int LookAheadAscii = asciiBytes.Length > (position + 1) ? asciiBytes[position + 1] : -1;
int LookAheadAscii2 = asciiBytes.Length > (position + 2) ? asciiBytes[position + 2] : -1;
int LookAheadAscii3 = asciiBytes.Length > (position + 3) ? asciiBytes[position + 3] : -1;
int[] result;
int shifter = -1;

// We don't need to switch CodeSet if we are using Code C and the next 2 chars are compatible with it
if (CurrCodeSet == CodeSet.CodeC && IsCodeCAllowedForChar(CharAscii, LookAheadAscii))
{
result = new int[1];
result[0] = CodeCValueForChars(CharAscii, LookAheadAscii);
return result;
}

//If our next 2 chars are compatible with Code C, switch to it.
//Actually... Only make the switch if the next 4 characters are compatable. Here's the cases.
// 00 the cost is 2 characters, once to switch to code C, one for the numbers. 2 vs 2
// 000 It's more expensive, because switch to code c, 00, switch to code a, last number. 4 vs 3
// 00a The cost is more expensive, once to switch to code c, one for the numbers, one to go to code a, then the letter. 4 Vs 3
// 0000 switch to code c, first number, second number. 3 vs 4
// 0000a switch to code c, first number, second number, switch to code a, last letter, 4 vs 5
if (IsCodeCAllowedForChar(CharAscii, LookAheadAscii) && IsCodeCAllowedForChar(LookAheadAscii2, LookAheadAscii3))
{
CurrCodeSet = CodeSet.CodeC;
result = new int[2];
result[0] = cCODEC;
result[1] = CodeCValueForChars(CharAscii, LookAheadAscii);
return result;
}

if (!CharCompatibleWithCodeset(CharAscii, LookAheadAscii, CurrCodeSet))
{
// If we are working with Code C or if we have a lookahead character AND if the next character is ALSO not compatible
if (CurrCodeSet == CodeSet.CodeC
|| ((LookAheadAscii != -1) && !CharCompatibleWithCodeset(LookAheadAscii, -1, CurrCodeSet)))
{
// we need to switch code sets
switch (CurrCodeSet)
{
case CodeSet.CodeA:
shifter = cCODEB;
CurrCodeSet = CodeSet.CodeB;
break;
case CodeSet.CodeB:
shifter = cCODEA;
CurrCodeSet = CodeSet.CodeA;
break;
case CodeSet.CodeC:
CodeSet cs = GetBestCodeSet(asciiBytes, position);
switch (cs)
{
case CodeSet.CodeA:
shifter = cCODEA;
CurrCodeSet = CodeSet.CodeA;
break;
case CodeSet.CodeB:
shifter = cCODEB;
CurrCodeSet = CodeSet.CodeB;
break;
case CodeSet.CodeC:
throw new Exception("We don't want to shift to CodeC if it not allowed.");
}
break;
}
}
else
{
// no need to switch code sets, a temporary SHIFT will suffice
shifter = cSHIFT;
}
}

if (shifter != -1)
{
result = new int[2];
result[0] = shifter;
CodeSet shiftedCodeSet;
switch (shifter)
{
case cCODEA:
shiftedCodeSet = CodeSet.CodeA;
break;
case cCODEB:
shiftedCodeSet = CodeSet.CodeB;
break;
case cSHIFT:
switch (CurrCodeSet)
{
case CodeSet.CodeA:
shiftedCodeSet = CodeSet.CodeB;
break;
case CodeSet.CodeB:
shiftedCodeSet = CodeSet.CodeA;
break;
default:
throw new ArgumentOutOfRangeException();
}
break;
default:
throw new ArgumentOutOfRangeException();
}
result[1] = CodeValueForChar(CharAscii, shiftedCodeSet);
}
else
{
result = new int[1];
result[0] = CodeValueForChar(CharAscii, CurrCodeSet);
}

return result;
}

/// <summary>
/// Tells us which codesets a given character value is allowed in
/// </summary>
/// <param name="CharAscii">ASCII value of character to look at</param>
/// <returns>Which codeset(s) can be used to represent this character</returns>
public static CodeSetAllowed CodesetAllowedForChar(int CharAscii)
{
if (CharAscii >= 32 && CharAscii <= 95)
{
return CodeSetAllowed.CodeAorB;
}

return (CharAscii < 32) ? CodeSetAllowed.CodeA : CodeSetAllowed.CodeB;
}

/// <summary>
/// Tells us if Code C is compatible with the 2 parameter bytes
/// </summary>
/// <param name="CharAscii">First char</param>
/// <param name="NextCharAscii">Second char</param>
/// <returns>Which codeset(s) can be used to represent this character</returns>
public static bool IsCodeCAllowedForChar(int CharAscii, int NextCharAscii)
{
if (CharAscii == -1 && NextCharAscii == -1)
return false;
return CharAscii >= 48 && CharAscii <= 57 && NextCharAscii >= 48 && NextCharAscii <= 57;
}

/// <summary>
/// Determine if a character can be represented in a given codeset
/// </summary>
/// <param name="CharAscii">character to check for</param>
/// <param name="NextCharAscii"></param>
/// <param name="currcs">codeset context to test</param>
/// <returns>true if the codeset contains a representation for the ASCII character</returns>
public static bool CharCompatibleWithCodeset(int CharAscii, int NextCharAscii, CodeSet currcs)
{
if (currcs == CodeSet.CodeC)
{
return IsCodeCAllowedForChar(CharAscii, NextCharAscii);
}
CodeSetAllowed csa = CodesetAllowedForChar(CharAscii);
return csa == CodeSetAllowed.CodeAorB
|| (csa == CodeSetAllowed.CodeA && currcs == CodeSet.CodeA)
|| (csa == CodeSetAllowed.CodeB && currcs == CodeSet.CodeB);
}

/// <summary>
/// Gets the integer code128 code value for a character (assuming the appropriate code set)
/// </summary>
/// <param name="CharAscii">character to convert</param>
/// <param name="currcs">The current Codeset</param>
/// <returns>code128 symbol value for the character</returns>
public static int CodeValueForChar(int CharAscii, CodeSet currcs)
{
if (CharCompatibleWithCodeset(CharAscii, -1, currcs) == false)
throw new Exception("Encoding error. For some reason, we're trying to encode ASCII char " + CharAscii + " in codeset " + currcs);
return (CharAscii >= 32) ? CharAscii - 32 : CharAscii + 64;
}

/// <summary>
/// Gets the integer code128 code value for pair character (assuming Code C)
/// </summary>
/// <param name="CharAscii">First character to convert</param>
/// <param name="NextCharAscii">Second character to convert</param>
/// <returns>code128 symbol value for the character</returns>
public static int CodeCValueForChars(int CharAscii, int NextCharAscii)
{
return (CharAscii - 48) * 10 + NextCharAscii - 48;
}

/// <summary>
/// Return the appropriate START code depending on the codeset we want to be in
/// </summary>
/// <param name="cs">The codeset you want to start in</param>
/// <returns>The code128 code to start a barcode in that codeset</returns>
public static int StartCodeForCodeSet(CodeSet cs)
{
switch (cs)
{
case CodeSet.CodeA:
return cSTARTA;
case CodeSet.CodeB:
return cSTARTB;
case CodeSet.CodeC:
return cSTARTC;
}

throw new Exception("Must be a CodeSet specified");
}

/// <summary>
/// Return the Code128 stop code
/// </summary>
/// <returns>the stop code</returns>
public static int StopCode()
{
return cSTOP;
}

/// <summary>
/// Indicates which code sets can represent a character -- CodeA, CodeB, or either
/// </summary>
public enum CodeSetAllowed
{
CodeA, CodeB, CodeAorB
}
}
}
GeneralRe: Code C implementation Tests Pin
Member 33176594-Jun-10 15:50
Member 33176594-Jun-10 15:50 
GeneralSystem.IndexOutOfRangeException Pin
gspadari4-Sep-07 14:42
gspadari4-Sep-07 14:42 
AnswerRe: System.IndexOutOfRangeException Pin
Chris Wuestefeld4-Sep-07 15:31
Chris Wuestefeld4-Sep-07 15:31 
QuestionCode128 lenght? Pin
PACO7712-Jul-07 1:24
PACO7712-Jul-07 1:24 
AnswerRe: Code128 lenght? Pin
Chris Wuestefeld12-Jul-07 2:20
Chris Wuestefeld12-Jul-07 2:20 
QuestionPrinting Pin
niemkhuccuoi07019-Jun-07 7:03
niemkhuccuoi07019-Jun-07 7:03 
AnswerRe: Printing Pin
Chris Wuestefeld9-Jun-07 8:11
Chris Wuestefeld9-Jun-07 8:11 
GeneralRe: Printing Pin
blak3r17-Aug-07 13:29
blak3r17-Aug-07 13:29 
GeneralGreat work Pin
San Maguil24-Jan-07 8:39
San Maguil24-Jan-07 8:39 
Question"test" Errore when Build. Pin
Khosrow ZF12-Jan-07 4:23
Khosrow ZF12-Jan-07 4:23 
AnswerRe: "test" Errore when Build. Pin
Chris Wuestefeld12-Jan-07 4:30
Chris Wuestefeld12-Jan-07 4:30 
QuestionTitle [modified] Pin
Lu5ck12-Nov-06 15:27
Lu5ck12-Nov-06 15:27 
AnswerRe: Title Pin
Lu5ck12-Nov-06 21:41
Lu5ck12-Nov-06 21:41 
GeneralRe: Title Pin
Chris Wuestefeld13-Nov-06 7:39
Chris Wuestefeld13-Nov-06 7:39 
GeneralRe: Title Pin
Lu5ck16-Nov-06 20:10
Lu5ck16-Nov-06 20:10 
GeneralError when downloading the source code Pin
Pozz15-Aug-06 0:41
Pozz15-Aug-06 0:41 
GeneralASP.Net rendering Pin
Glosse3-Aug-06 5:13
Glosse3-Aug-06 5:13 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.