Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Drawing POSTNET Barcodes with C#

0.00/5 (No votes)
8 Dec 2004 1  
This article demonstrates a method to draw POSTNET barcodes.
app400x200.gif

Introduction

This article investigates the POSTNET barcode system, and demonstrates some basic functionality to create POSTNET barcodes using C# 2005 and GDI+.

Background

Many employers these days are fairly frugal, so having them purchase something as simple as postal barcode fonts sometimes can prove to be extremely difficult. Being in this position a time or two, I decided to investigate the postal barcode system and create a simple library that could draw the POSTNET barcodes when given the zip code.

POSTNET Barcode Overview

POSTNET barcodes can be composed of several pieces of information: frame bars, zip code, +4, delivery point code, and correction character (see image below).

Description of POSTNET Barcode format.

Frame bars signify the beginning and the end of the barcode. The frame bars are mandatory, and are composed of a single Full Bar. The zip code is also mandatory, and is represented by 25 bars. The +4 is composed of 20 bars and is optional. The delivery point code is represented by 10 bars, is optional, and is normally the last two digits of the street address, post office box, rural route number, or highway contract route number. The correction character is mandatory, and is basically a check sum (I use the terms correction character and check sum interchangeably in this article).

POSTNET Barcode Numbers.

Each digit is represented by a distinct pattern composed of five bars (see image on left for patterns). Basically, there are two distinct types of bars: the Full Bar and the Half Bar. A Full Bar must be between a minimum of 0.115" and a maximum of 0.135" tall. A Half Bar must be between a minimum of 0.04" and a maximum of 0.06" tall. Both the Full Bar and the Half Bar must be between 0.015" and 0.025" wide.

In addition to the height and width specifications, there is spacing requirement between bars; it must be between 0.012" and 0.040".

Specifications for POSTNET Barcode
Specification Nominal Allowable Variance
Full Bar Height 0.125" +/-0.01"
Half Bar Height 0.05" +/-0.01"
Bar Width 0.02" +/-0.005"
Bar Spacing 0.026" +/-0.014
Center Line Spacing 0.0458" +/-0.0042"

(For a complete reference, please refer to the Postal Service POSTNET barcode specifications available here, or do a search on POSTNET barcode specifications.)

Correction Character

The correction character is a simple check sum which is calculated by adding each of the numbers in the zip code together, takes the modulus of the summed numbers by 10, and then subtracts this result from 10.

Example: Zip code + 4 is 12345-6789 with a delivery point of 01.

1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 0 + 1 = 46

46 % 10 = 6

10 - 6 = 4

4 % 10 = 4

So the correction character is 4; the last modulus is for the special case when the summed zip code is a multiple of 10.

Code

The Postnet class is used to render the barcode using a handle to a Graphics object or create a Bitmap class. The only required data is the zip code; the +4 digits and the delivery point are optional. The correction character will be calculated by the Postnet class.

Using the Code

The Postnet class will handle all aspects of rendering the barcode. Simply create an instance of the Postnet class, passing the zip code to the constructor as a parameter. Then, call the DrawPOSTNET function passing the Graphics object and the starting point as parameters. Once it returns from processing, handle any other drawing tasks, and then clean up by calling the Dispose function of the Graphics object.

    // Create an instance of the Postnet class and send it
    //  the zip code, +4, and delivery point.  The Postnet class will
    //  calculate the correction character.
    Postnet ps = new Postnet( "12345", "6789", "01" );
    
    // Create a Graphics object handle from a picture box.
    System.Drawing.Graphics g = this.pictureBox1.CreateGraphics( );
    
    // Send a handle to the Graphics object and the starting
    //  point to begin drawing the barcode.
    ps.DrawPOSTNET( g, new System.Drawing.Point( 0, 0 ) );

    // Handle any other drawing necessary.
    
    // Clean up.
    g.Dispose( );

The second functionality that the Postnet class provides is to create the barcode in the form a bitmap. To create a bitmap, create an instance of the Postnet class passing the zip code to the constructor. Then, call the CreateBitmap function passing the width and height for the bitmap that will be created.

    // Create an instance of the Postnet class and send it
    //  the zip code, +4, and delivery point.  The Postnet class will
    //  calculate the correction character.
    Postnet ps = new Postnet( "12345", "6789", "01" );
    
    // Call the CreateBitmap function passing in the width and height for the
    //  newly created bitmap.
    pictureBox1.Image = ps.CreateBitmap( this.pictureBox1.Width, 
                                         this.pictureBox1.Height );

How the code works

The Postnet class is relatively simple. It provides several constructors for various means of creation; several properties/attributes to set and get class data; the two public functions discussed above, and a few private functions which handle some of the grunt work.

When an instance of the class is created, it initializes the bar specifications by calling the InitBarSpecification function. This function sets several pieces of information, which controls the various aspects of the bars, and initializes the DRAWING_INFO structure (see code listed below).

        private void InitBarSpecifications( )
        {
            // Set the Bar specifications with the default values.
            di = new DRAWING_INFO( );
            _fBarWidth = .022f;
            _fTallBarHeight = 0.125f;
            _fShortBarHeight = 0.05f;
            _fBarSpacing = 0.02f;
        }

The DRAWING_INFO contains the instance of the brush used to draw the bars, the current x location, and the current y location.

        private struct DRAWING_INFO
        {
            public System.Drawing.SolidBrush brush;
            public float xStart;
            public float yStart;
        }

The DrawPOSTNET function uses the CalculateCorrectionCharacter function to calculate the check sum, renders the frame bars, and calls the DrawBars function to render the zip code, +4, delivery point and correction character. In addition to these features, the DrawPOSTNET function also saves the GraphicsState of the Graphics object, and restores it when it has finished drawing the barcode. One interesting item is the use of the g.PageUnit and the g.PageScale; if these are not set or get correctly, the barcode can be too large or too small. The PageUnit and PageScale are used to automatically adjust the rendering from pixels to inches. The PageUnit is a System.Drawing.GraphicsUnit enumeration type, and tells the GDI+ objects what type of units to convert the drawing to. The PageScale is used to scale the rendering; so if the PageScale is set to .5, then one inch on the drawing will be scaled to one half an inch on the actual device (monitor, printer, etc...).

        public void DrawPOSTNET( System.Drawing.Graphics g, 
                                 System.Drawing.Point startPt )
        {
            // Calculate the Check Sum.
            CalculateCorrectionCharacter( );

            // Save the user's graphics state, just in case they are doing
            //  something special.
            System.Drawing.Drawing2D.GraphicsState gs = g.Save( );

            // Failure to set the PageUnit and PageScale will result
            //    in odd results.
            g.PageUnit = System.Drawing.GraphicsUnit.Inch;
            g.PageScale = 1f;

            di.brush = new System.Drawing.SolidBrush( System.Drawing.Color.Black );
            di.xStart = startPt.X;
            di.yStart = startPt.Y;

            // Print Frame Bar
            g.FillRectangle( di.brush, di.xStart, di.yStart, 
                             _fBarWidth, _fTallBarHeight );
            di.xStart += ( _fBarWidth + _fBarSpacing );

            // Print Zip Code
            string sTempValue = ZipCode.ToString( );
            DrawBars( g, sTempValue, ref di );

            // Print Plus4
            if( Plus4.Length == 4 )
            {
                sTempValue = Plus4.ToString( );
                DrawBars( g, sTempValue, ref di );

                // Print Delivery Point
                if( this.DeliveryPoint.Length > 0 && this.DeliveryPoint.Length < 3 )
                {
                    sTempValue = DeliveryPoint.ToString( );
                    DrawBars( g, sTempValue, ref di );
                }
            }
            
            // Print Correction Character
            sTempValue = this.CorrectionCharacter.ToString( );
            DrawBars( g, sTempValue, ref di );

            // Print Frame Bar
            g.FillRectangle( di.brush, di.xStart, di.yStart, 
                             _fBarWidth, _fTallBarHeight );

            // Restore the saved user's graphics state, just incase they are doing
            //  something special.
            g.Restore( gs );
        }

The CalculateCorrectionCharacter function simply adds the digits in the zip code, +4, and delivery code; it then takes the modulus of the summed number by 10; subtracts that number from 10 and then takes the modulus of 10 again.

         private void CalculateCorrectionCharacter( )
        {
            string sZipCode;
            sZipCode = ZipCode.ToString( );

            int iCorrectionCharacter = 0;

            for( int i = 0; i < sZipCode.Length; i++ )
            {
                iCorrectionCharacter += 
                             Convert.ToInt32( sZipCode.Substring( i, 1 ) );
            }

            if( this.Plus4.Length == 4 )
            {
                string sPlus4 = Plus4.ToString( );
                for( int i = 0; i < sPlus4.Length; i++ )
                {
                    iCorrectionCharacter += 
                             Convert.ToInt32( sPlus4.Substring( i, 1 ) );
                }

                if( this.DeliveryPoint.Length > 0 
                                      && this.DeliveryPoint.Length < 3 )
                {
                    string sDeliveryPoint = this.DeliveryPoint.ToString( );
                    for( int i = 0; i < sDeliveryPoint.Length; i++ )
                    {
                        iCorrectionCharacter += 
                             Convert.ToInt32( sDeliveryPoint.Substring( i, 1 ) );
                    }
                }
            }
            int iLeftOver = iCorrectionCharacter % 10;
            this._iCorrectionCharacter = ( 10 - iLeftOver ) % 10;
        }

The DrawBars function uses the sNumber parameter and the _aBarcodeValues array to determine how the bar should be drawn. The _aBarcodeValues array contains the number formats; 0 is a short bar and 1 is a tall bar.

         string [] _aBarcodeValues = {    "11000",    // 0
                        "00011",    // 1
                        "00101",    // 2
                        "00110",    // 3
                        "01001",    // 4
                        "01010",    // 5
                        "01100",    // 6
                        "10001",    // 7
                        "10010",    // 8
                        "10100" };  // 9

        ...
     
        private void DrawBars( System.Drawing.Graphics g, 
                     string sNumber, ref DRAWING_INFO di )
        {
            string sTempValue;
            for( int i = 0; i < sNumber.Length; i++ )
            {
                sTempValue = 
                  _aBarcodeValues[Convert.ToInt32( sNumber.Substring( i, 1 ) )];
                for( int j = 0; j < sTempValue.Length; j++ )
                {
                    if( sTempValue.Substring( j, 1 ) == "0" )
                        g.FillRectangle( di.brush, di.xStart, di.yStart + 
                        ( _fTallBarHeight - _fShortBarHeight ), 
                                            _fBarWidth, _fShortBarHeight );
                    else
                        g.FillRectangle( di.brush, di.xStart, di.yStart, 
                                         _fBarWidth, _fTallBarHeight );

                    di.xStart += ( _fBarWidth + _fBarSpacing );
                }
            }
        }

The final function is the CreateBitmap function. This function takes the desired width and height to create a new bitmap. It creates a Graphics object from the Bitmap, draws the barcode, and returns the Bitmap object.

        public System.Drawing.Bitmap CreateBitmap( int width, int height )
        {
            System.Drawing.Bitmap bmp = new System.Drawing.Bitmap( width, height );

            System.Drawing.Graphics g = System.Drawing.Graphics.FromImage( bmp );
            this.DrawPOSTNET( g, new System.Drawing.Point( 0, 0 ) );
            g.Dispose( );
            return bmp;

        }

All done!

Well, that's it for the Postnet class, and I should probably get back to work. Although this is probably not a hot topic, I do hope this will help someone out, at least by sharing a little knowledge.

History

  • Version 1.0.0 - 12/1/2004 - Initial version.
  • Version 1.1.0 - 12/7/2004 - Corrected the correction character calculation - thanks je_gonzalez.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here