65.9K
CodeProject 正在变化。 阅读更多。
Home

用 C# 创建 EAN-13 条形码

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.88/5 (62投票s)

2005年4月19日

12分钟阅读

viewsIcon

689154

downloadIcon

50877

演示了如何使用 C# 创建 EAN-13 条形码。

引言

本文是应要求扩展UPC-A 条形码示例以包含 EAN-8 和 EAN-13 的响应。在本文中,我们将探讨 EAN-13 规范并检查一些可以生成 EAN-13 条形码的代码。

EAN-13 背景

EAN-13 条形码由 13 位数字组成,这些数字由以下部分构成:前 2 或 3 位数字是国家代码,接下来的 5 到 7 位数字是制造商代码,接下来的 3 到 5 位数字是产品代码,最后一位数字是校验和数字。

下图显示了一个典型的 EAN-13 条形码。

国家代码

国家代码是一个 2 位或 3 位数字,用于标识分配制造商代码的国家。所有以“0”开头的 EAN-13 条形码都是 UPC-A 条形码。下表包含国家代码

00-13: 美国和加拿大 20-29: 店内功能 30-37: 法国
40-44: 德国 45: 日本(也包括 49) 46: 俄罗斯联邦
471: 台湾 474: 爱沙尼亚 475: 拉脱维亚
477: 立陶宛 479: 斯里兰卡 480: 菲律宾
482: 乌克兰 484: 摩尔多瓦 485: 亚美尼亚
486: 格鲁吉亚 487: 哈萨克斯坦 489: 香港
49: 日本 (JAN-13) 50: 英国 520: 希腊
528: 黎巴嫩 529: 塞浦路斯 531: 马其顿
535: 马耳他 539: 爱尔兰 54: 比利时和卢森堡
560: 葡萄牙 569: 冰岛 57: 丹麦
590: 波兰 594: 罗马尼亚 599: 匈牙利
600 和 601: 南非 609: 毛里求斯 611: 摩洛哥
613: 阿尔及利亚 619: 突尼斯 622: 埃及
625: 约旦 626: 伊朗 64: 芬兰
690-692: 中国 70: 挪威 729: 以色列
73: 瑞典 740: 危地马拉 741: 萨尔瓦多
742: 洪都拉斯 743: 尼加拉瓜 744: 哥斯达黎加
746: 多米尼加共和国 750: 墨西哥 759: 委内瑞拉
76: 瑞士 770: 哥伦比亚 773: 乌拉圭
775: 秘鲁 777: 玻利维亚 779: 阿根廷
780: 智利 784: 巴拉圭 785: 秘鲁
786: 厄瓜多尔 789: 巴西 80 - 83: 意大利
84: 西班牙 850: 古巴 858: 斯洛伐克
859: 捷克共和国 860: 南斯拉夫 869: 土耳其
87: 荷兰 880: 韩国 885: 泰国
888: 新加坡 890: 印度 893: 越南
899: 印度尼西亚 90 和 91: 奥地利 93: 澳大利亚
94: 新西兰 955: 马来西亚 977: 国际标准连续出版物号(ISSN)
978: 国际标准书号(ISBN) 979: 国际标准音乐编号(ISMN) 980: 退款收据
981 和 982: 通用货币优惠券 99: 优惠券

制造商代码

EAN 制造商代码是可变长度的数字。通常,5 位数字代码分配给公司,但是,有些公司生产的产品不足以获得 5 位数字产品代码,在这种情况下,EAN 将分配长度大于 5 位数字的制造商代码。

产品代码

制造商可以自由分配自己的产品代码,但他们必须确保每个产品代码在其产品代码中是唯一的。根据国家代码和制造商代码的长度,产品代码最短可以是 3 位数字,最长可以达 5 位数字。

校验和数字

校验和数字是使用国家代码、制造商代码和产品代码计算得出的。从最右边的数字开始,奇数位数字乘以 3 并加到总和中,而偶数位数字则直接加到总和中。EAN-13 校验和计算顺序相反(从最右边的数字开始,并将其视为奇数而不是偶数)的原因是为了与 UPC-A 条形码兼容。然后对总和取模 10。然后用 10 减去此结果,再取模 10。

例如:EAN-13 001234567890
国家代码:00
制造商代码:12345
产品代码:67890

最右边的数字是“0”,被视为奇数,所以乘以 3,倒数第二个数字“9”是偶数,所以直接相加,依此类推……

(0 * 3) + 9 + (8 * 3) + 7 + (6 * 3) + 5 + (4 * 3) + 3 + (2 * 3) + 1 + (0 * 3) + 0 = 85

85 % 10 = 5

( ( 10 - 5 ) % 10 ) = 5

符号尺寸

EAN-13 条形码的规格规定了标称尺寸为 37.29mm 宽和 25.93mm 高。基于此标称尺寸,EAN-13 条形码可以按 0.8 到 2.0 的放大系数进行缩放。缩放条形码将产生介于最小允许尺寸 29.83mm 宽 x 20.74mm 高和最大允许尺寸 74.58mm 宽 x 51.86mm 高之间的条形码。

数字模式

EAN-13 条形码中的每个数字由一系列两个空格和两个条纹组成。每个数字绘制在一个 7 个模块宽的空间内。除了构成 EAN-13 条形码的 13 位数字外,条形码符号还包含两个静区、一个起始块、一个分隔符和一个结束块。每个静区宽 9 个模块,起始块和结束块是条纹和空格的序列,格式为条纹、空格、条纹。分隔符由序列空格/条纹/空格/条纹/空格表示。

特殊符号 模式
静区 000000000
起始/结束 101
分隔符 01010

其中“0”代表空格,“1”代表条纹。

除了上面列出的特殊符号模式外,EAN-13 条形码符号还使用了三种不同的数字模式:左数字奇校验模式、左数字偶校验模式和右数字模式。左数字模式以空格开头,而右数字模式以条纹开头(参见下表)。

数字 左侧数字 右侧数字
奇校验 偶校验
0 0001101 0100111 1110010
1 0011001 0110011 1100110
2 0010011 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

其中“0”表示空格,“1”表示条纹。

国家代码的第一位数字用于确定制造商代码中每个数字的校验和(参见“确定数字校验和”部分)。右数字模式通常用于绘制产品代码和校验和数字。但是,如果国家代码大于 2,或者制造商代码大于 5,它将用于渲染制造商代码的一部分。

确定数字校验和

EAN-13 条形码中的国家代码第一位数字不进行编码,而是用于确定制造商代码中数字的校验和。国家代码的第二位数字始终是奇数,制造商代码将具有三个使用偶校验的左侧数字和两个使用奇校验的数字,但与 UPC-A 兼容的条形码则使用所有奇校验。下表概述了制造商代码中数字的校验和。

第一个国家代码数字 校验和
第二个国家代码数字 制造商代码数字
1 2 3 4 5
0 (UPC-A) 奇数 奇数 奇数 奇数 奇数 奇数
1 奇数 奇数 偶数 奇数 偶数 偶数
2 奇数 奇数 偶数 偶数 奇数 偶数
3 奇数 奇数 偶数 偶数 偶数 奇数
4 奇数 偶数 奇数 奇数 偶数 偶数
5 奇数 偶数 偶数 奇数 奇数 偶数
6 奇数 偶数 偶数 偶数 奇数 奇数
7 奇数 偶数 奇数 偶数 奇数 偶数
8 奇数 偶数 奇数 偶数 偶数 奇数
9 奇数 偶数 偶数 奇数 偶数 奇数

例如,如果国家代码是 75,那么根据第一个国家代码数字和上表,第二个国家代码数字 5 将是奇数。制造商代码的第一个数字将使用偶数模式,第二个数字将使用奇数模式,第三个数字将使用偶数模式,第四个数字将是奇数,第五个数字将使用偶数模式。

使用代码

首先,我们将检查如何使用 Ean13 类,然后我们将检查 Ean13 类的工作原理。

使用 Ean13 类

下面的代码段使用 Ean13 类在 picture box 控件中绘制 EAN-13 条形码

private void DrawEan13( )
{        
    System.Drawing.Graphics g = this.picBarcode.CreateGraphics( ); 

    g.FillRectangle(
         new System.Drawing.SolidBrush(System.Drawing.SystemColors.Control), 
         new Rectangle(0, 0, picBarcode.Width, picBarcode.Height)); 
        
    // Create an instance of the Ean13 Class.        
    upc = new Ean13( ); 
    
    upc.CountryCode = "12";
    upc.ManufacturerCode = "34567"; 
    upc.ProductCode = "89012"; 
    upc.Scale = 
        (float)Convert.ToDecimal(cboScale.Items [cboScale.SelectedIndex]); 
    
    upc.DrawEan13Barcode( g, new System.Drawing.Point( 0, 0 ) ); 
    
    g.Dispose( );
}

DrawEan13 函数的第一步是创建 Ean13 类的实例,然后设置国家代码、制造商代码、产品代码和比例因子属性(校验和将由 Ean13 类计算)。设置完这些属性后,将调用 DrawEan13Barcode 函数,传递一个 Graphics 对象和一个 Point,它指示绘制的起始位置,这将导致条形码从点 (0, 0) 开始绘制在 picture box 中。

Ean13 类

最重要的变量列在下面

// This is the nomimal size recommended by the EAN.
private float _fWidth = 37.29f;
private float _fHeight = 25.93f;
private float _fFontSize = 8.0f;
private float _fScale = 1.0f;

// Left Hand Digits.
private string [] _aOddLeft = { "0001101", "0011001", "0010011", "0111101", 
                                "0100011", "0110001", "0101111", "0111011", 
                                "0110111", "0001011" };

private string [] _aEvenLeft = { "0100111", "0110011", "0011011", "0100001", 
                                 "0011101", "0111001", "0000101", "0010001", 
                                 "0001001", "0010111" };

// Right Hand Digits.
private string [] _aRight = { "1110010", "1100110", "1101100", "1000010", 
                              "1011100", "1001110", "1010000", "1000100", 
                              "1001000", "1110100" };

private string _sQuiteZone = "000000000";

private string _sLeadTail = "101";

private string _sSeparator = "01010";

_fWidth_fHeight_fScale 变量使用标称尺寸进行初始化。当条形码渲染时,其实际尺寸将由标称尺寸和比例因子决定,正如本文“符号尺寸”部分所述。变量 _aOddLeft_aEvenLeft_aRight_sQuiteZone_sLeadTail_sSeparator 都是条纹/空格图形的字符串表示,它们代表 EAN-13 条形码的各个部分。本质上,“1”代表条纹,“0”代表空格,因此 _sSeparator 将渲染空格-条纹-空格-条纹-空格。使用字符串的替代方法是使用二进制表示,其中 0 位代表空格,1 位代表条纹。

有四个主要函数为 Ean13 类提供了大部分功能。这些函数中的主力是 DrawEan13Barcode,它使用几个函数作为辅助函数。辅助函数包括:CalculateChecksumDigitConvertToDigitPatternsConvertLeftPattern(将首先讨论)。还有一个第五个函数 CreateBitmap,它提供了一种创建位图图像的便捷方法。

第一个辅助函数 DrawEan13Barcode 调用 CalculateChecksumDigit 函数,该函数使用国家代码、制造商代码和产品代码来计算条形码的校验和。

public void CalculateChecksumDigit( )
{
    string sTemp = 
      this.CountryCode + this.ManufacturerCode + this.ProductCode;
    int iSum = 0;
    int iDigit = 0;

    // Calculate the checksum digit here.
    for( int i = sTemp.Length; i >= 1; i-- )
    {
        iDigit = Convert.ToInt32( sTemp.Substring( i - 1, 1 ) );
        // This appears to be backwards but the 
        // EAN-13 checksum must be calculated
        // this way to be compatible with UPC-A.
        if( i % 2 == 0 )
        { // odd  
            iSum += iDigit * 3;
        }  
        else 
        { // even
            iSum += iDigit * 1; 
        }
    }
    int iCheckSum = ( 10 - ( iSum % 10 ) )  % 10; 
    this.ChecksumDigit = iCheckSum.ToString( );
}

CalculateChecksumDigit 函数使用上面列出的“校验和数字”部分中讨论的方法计算校验和。

第二个使用的辅助函数是 ConvertToDigitPatterns 函数。此函数接收制造商编号和产品编号的各个数字,并将它们转换为条形码图形的字符串表示。

private string ConvertToDigitPatterns(string inputNumber, string [] patterns)
{
    System.Text.StringBuilder sbTemp = new StringBuilder( );
    int iIndex = 0;
    for( int i = 0; i < inputNumber.Length; i++ )
    {
        iIndex = Convert.ToInt32( inputNumber.Substring( i, 1 ) );
        sbTemp.Append( patterns[iIndex] );
    }
    return sbTemp.ToString( );
}

ConvertToDigitPatterns 函数需要两个参数

  • inputNumber
  • 模式

inputNumber 将是制造商编号或产品编号,而 patterns 将是 _aOddLeft_aEvenLeft_aRight 数组,具体取决于 inputNumber 是制造商编号还是产品编号。

ConvertLeftPatterns 用于创建“确定数字校验和”部分中讨论的左侧模式。ConvertLeftPatterns 确定国家代码,并调用相应的国家代码转换器。

private string ConvertLeftPattern( string sLeft )
{
    switch( sLeft.Substring( 0, 1 ) )
    {
        case "0":
            return CountryCode0( sLeft.Substring( 1 ) );
            
         case "1":
            return CountryCode1( sLeft.Substring( 1 ) );

         case "2":
            return CountryCode2( sLeft.Substring( 1 ) );

         case "3":
            return CountryCode3( sLeft.Substring( 1 ) );

         case "4":
            return CountryCode4( sLeft.Substring( 1 ) );

         case "5":
            return CountryCode5( sLeft.Substring( 1 ) );

         case "6":
            return CountryCode6( sLeft.Substring( 1 ) );

         case "7":
            return CountryCode7( sLeft.Substring( 1 ) );

         case "8":
            return CountryCode8( sLeft.Substring( 1 ) );

         case "9":
            return CountryCode9( sLeft.Substring( 1 ) );

         default:
            return "";
    }
}

每个国家代码都有其自己的单独转换器函数。CountryCode0 是 UPC-A 条形码,因此它将仅使用左侧奇校验模式。CountryCode1CountryCode9 各自使用“确定数字校验和”部分中讨论的适当校验模式。例如,请参阅下面的 CountryCode1 代码,了解国家代码转换器函数的示例

private string CountryCode1( string sLeft )
{
    // 1 Odd Odd  Even Odd  Even Even 
    System.Text.StringBuilder sReturn = new StringBuilder( );
    sReturn.Append( ConvertToDigitPatterns( sLeft.Substring( 0, 1 ), 
                                                     this._aOddLeft ) );
    sReturn.Append( ConvertToDigitPatterns( sLeft.Substring( 1, 1 ), 
                                                     this._aOddLeft ) );
    sReturn.Append( ConvertToDigitPatterns( sLeft.Substring( 2, 1 ), 
                                                     this._aEvenLeft ) );
    sReturn.Append( ConvertToDigitPatterns( sLeft.Substring( 3, 1 ), 
                                                     this._aOddLeft ) );
    sReturn.Append( ConvertToDigitPatterns( sLeft.Substring( 4, 1 ), 
                                                     this._aEvenLeft ) );
    sReturn.Append( ConvertToDigitPatterns( sLeft.Substring( 5, 1 ), 
                                                     this._aEvenLeft ) );
    return sReturn.ToString( );
}

CountryCode1 函数使用“确定数字校验和”部分中讨论的校验模式,将每个数字发送到 ConvertToDigitPatterns 函数,并使用正确的左侧模式。sLeft 参数应包含国家代码的第二位数字和制造商代码。

最后是主力;DrawEan13Barcode 函数处理条形码图形的渲染,需要两个参数

  • g
  • pt

此函数首先通过按比例因子缩放标称宽度和高度来确定条形码的宽度和高度。lineWidth 基于渲染 EAN-13 条形码所需的总模块数。总模块数 113 是通过以下方式确定的:例如

EAN-13 代码 - 1234567890128

条形码部分 数值 图形表示 模块数
静区 N/A 000000000 9 个模块
潜在客户 N/A 101 3 个模块
国家代码的第一位数字 1 位数字 - "1" 用于确定校验和。
国家代码的第二位数字 1 位数字 - "2" 0010011 7 个模块
制造商号码 5 位数字 = "34567" 01111010011101011000100001010010001 5 位数字 * 7 个模块 = 35 个模块
分隔符 N/A 01010 5 个模块
产品号码 5 位数字 = "89012" 10010001110100111001011001101101100 5 位数字 * 7 个模块 = 35 个模块
校验和 1 位数字 = "8" 1001000 7 个模块
结束符 N/A 101 3 个模块
静区 N/A 000000000 9 个模块

要确定总模块宽度,只需将各个部分相加:9 + 3 + 7 + 35 + 5 + 35 + 7 + 3 + 9 = 113。国家代码的第二位数字 2 将具有奇校验,制造商代码的第一个数字 3 将使用奇数模式,第二个数字 4 将使用偶数模式,第三个数字 5 将使用奇数模式,第四个和第五个数字 6 和 7 将使用偶数模式。

public void DrawEan13Barcode(System.Drawing.Graphics g, System.Drawing.Point pt)
{ 
    float width = this.Width * this.Scale;
    float height = this.Height * this.Scale;

    //    EAN13 Barcode should be a total of 113 modules wide.
    float lineWidth = width / 113f;

    // Save the GraphicsState.
    System.Drawing.Drawing2D.GraphicsState gs = g.Save( );

    // Set the PageUnit to Inch because all of 
    // our measurements are in inches.
    g.PageUnit = System.Drawing.GraphicsUnit.Millimeter;

    // Set the PageScale to 1, so a millimeter 
    // will represent a true millimeter.
    g.PageScale = 1;

    System.Drawing.SolidBrush brush = 
        new System.Drawing.SolidBrush(System.Drawing.Color.Black);

    float xPosition = 0;

    System.Text.StringBuilder strbEAN13 = new System.Text.StringBuilder( );
    System.Text.StringBuilder sbTemp = new System.Text.StringBuilder( );

    float xStart = pt.X;
    float yStart = pt.Y;
    float xEnd = 0;

    System.Drawing.Font font = 
         new System.Drawing.Font("Arial", this._fFontSize * this.Scale);

    // Calculate the Check Digit.
    this.CalculateChecksumDigit( );

    sbTemp.AppendFormat( "{0}{1}{2}{3}", 
                        this.CountryCode,
                        this.ManufacturerCode,
                        this.ProductCode, 
                        this.ChecksumDigit );


    string sTemp = sbTemp.ToString( );

    string sLeftPattern = "";

    // Convert the left hand numbers.
    sLeftPattern = ConvertLeftPattern(sTemp.Substring( 0, 7 ));

    // Build the UPC Code.
    strbEAN13.AppendFormat( "{0}{1}{2}{3}{4}{1}{0}",
               this._sQuiteZone, this._sLeadTail,
               sLeftPattern, this._sSeparator,
               ConvertToDigitPatterns(sTemp.Substring( 7 ), this._aRight));

    string sTempUPC = strbEAN13.ToString( );

    float fTextHeight = g.MeasureString( sTempUPC, font ).Height;

    // Draw the barcode lines.
    for( int i = 0; i < strbEAN13.Length; i++ )
    {
        if( sTempUPC.Substring( i, 1 ) == "1" )
        {
            if( xStart == pt.X )
                xStart = xPosition;

            // Save room for the UPC number below the bar code.
            if( ( i > 12 && i < 55 ) || ( i > 57 && i < 101 ) )
                // Draw space for the number
                g.FillRectangle( brush, xPosition, yStart, 
                                       lineWidth, height - fTextHeight );
            else
                // Draw a full line.
                g.FillRectangle( brush, xPosition, yStart, lineWidth, height );
        }

        xPosition += lineWidth;
        xEnd = xPosition;
    }

    // Draw the upc numbers below the line.
    xPosition = 
      xStart - g.MeasureString(this.CountryCode.Substring( 0, 1 ), font).Width;
    float yPosition = yStart + ( height - fTextHeight );

    // Draw 1st digit of the country code.
    g.DrawString( sTemp.Substring( 0, 1 ), font, brush, 
         new System.Drawing.PointF( xPosition, yPosition ) );

    xPosition += 
      (g.MeasureString(sTemp.Substring( 0, 1 ), font).Width + 43 * lineWidth) -
                 (g.MeasureString( sTemp.Substring( 1, 6 ), font ).Width);

    // Draw MFG Number.
    g.DrawString( sTemp.Substring( 1, 6 ), font, brush, 
         new System.Drawing.PointF( xPosition, yPosition ) );

    xPosition += 
      g.MeasureString(sTemp.Substring( 1, 6 ), font).Width + (11 * lineWidth);

    // Draw Product ID.
    g.DrawString( sTemp.Substring( 7 ), font, brush, 
         new System.Drawing.PointF( xPosition, yPosition ) );

    // Restore the GraphicsState.
    g.Restore( gs );
}

该函数使用 CalculateChecksumDigit 函数计算正确的校验和数字,然后调用 ConvertLeftPatterns,接着使用 ConvertToDigitPatterns 函数将 EAN-13 条形码号码的产品代码和校验和数字转换为字符串表示。一旦数字被转换为字符串表示,代码就会使用字符串表示来渲染条形码,1 会导致绘制一个矩形,0 会导致代码跳过绘制矩形。如果代码绘制矩形,它还会考虑是否需要缩短矩形以留出制造商号码和产品号码的空间。一旦条形码完全渲染完毕,代码就会确定位置,并绘制国家代码、制造商号码、产品号码和校验和数字。

CreateBitmap 函数仅创建一个 Bitmap 对象,并使用 DrawEan13Barcode 函数将条形码渲染到 Bitmap 对象,然后返回 Bitmap

public System.Drawing.Bitmap CreateBitmap( )
{
    float tempWidth = ( this.Width * this.Scale ) * 100 ;
    float tempHeight = ( this.Height * this.Scale ) * 100;

    System.Drawing.Bitmap bmp = 
        new System.Drawing.Bitmap( (int)tempWidth, (int)tempHeight );

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

特别感谢

我要感谢 m@u 指出了我最初的文章和源代码中的缺陷。我还要感谢 MArmbruckner 测试了 2.0 版本并确保条形码能够正确扫描。

历史

  • 版本 1.0 - 初始应用程序。
  • 版本 2.0 - 修改了应用程序,使其根据国家代码的第一位数字使用正确的条形码模式。
© . All rights reserved.