• 带你探索条形码世界的奥秘


    二维码也就是QR码受所谓的移动互联网吵得也比较火,但是同学我奉劝你还是把一维的先搞懂吧。首先要说的就是印在商品上的条形码 就仅仅是一串竖条而已没什么玄机,对印刷面也无特定要求 黑与白只要能达到一定的光学分辨程度即可。说白了就是一个数字ID 和它下面标注的数字对应,弄条码 只是方便“快速录入数据” 仅此而已。再说一下 它仅仅是一个数字ID  既不包含价格信息 也不包含产地 等其鸟信息。但为什么扫一下就能知道他是哪种产品呢 还有价格呢。因为厂商产品的条形码都会添加到ZF的数据库字典里去 就跟那个鸟icp备案一样,这个就是中国物品编码中心。扫描完再到数据字典里一检索自然就出来了。

    说到这里你就释然了。不管怎样 反正那个鬼激光器在条码上面晃一下瞬间就读出了那串数字ID 就是这么神奇 高效 还不会出错。远不是OCR文本识别技术对环境 对速度所能达到的。硬件方面咱也不懂 也不去研究那杆枪到底啥结构 里面又包括激光器 又包括信号放大电路啥的 反正就是能读条码就OK了。软件方面咱还是可以搞一搞的。

    说到这可能瞬间就有想法了:

    kao 这不是二进制么 0101 的。对 如果16个竖条来表示数字 按照我们简单的想法 他能表示的极限是 0~65535。如果表示字母的话呢 2字节? 这也太少了吧 16个竖条啊 。如果弄成横竖二维的方式呢,好了打住  关于这些下次再讨论。
    首先就算激光器再精确读取也是有误差的 。我们平常在商品上看到的条码 绝对不是这种编码方式 这是非常不靠谱的。如果你真的发明了这种码 那你就自己搞个东西去读取它吧 整不死你。

    看最上面的商品条码图就知道他的“细条” “粗条” “空白” 都有明显的区别 也就是辨识度。这是不是有点像摩斯电码?两长一短 、两短一长 、滴 滴滴 、嘿嘿。事实上超市商品 条码 所遵循的标准叫 EAN-13 、当然还有其他种类型的码 有码的  无码的 不是我们看的爱情动作片里的那种码哈。

    先来看下ean-13的简单介绍:

    • 大体上看编码的基本依然是黑色代表1 空白代表0。
    • 可以看到前面 中间 最后 有3各部分的线段较长 称之为护线。分别为起始符 101    中间分隔符01010    终止符101。
    • 左边有6位数字x7=42个单位。
    • 右边也有6位数字x7=42个单位。

    ean-13 难道这个13是因为它有13位数字?实际上最左边的数字称之为导入值 是不使用“竖条”进行编码的 ,最后一位称之为检查码 是根据它前面12位进行运算而来的
    所以他的有效数据只有11位 每位为0~9。

    随便找了点资料  来看下ean-13的编码规则:

    数字符 左侧数据 右侧数据
      A B C
    0 0001101 0100111 1110010 
    1 0011001 0110011 1100110
    2 0010011 0011011 1101100
    3 011101 0100001 1000010
    4 0100011 0011101 1011100
    5 0110001 0111001 1001110
    6 0101111 000101 1010000
    7 0111011 0010001 1000100
    8 0110111 0001001 1001000
    9 0001011 0010111 1110100
    前置字符左侧数据对应规则:
    0 A A A A A A
    1 A A B A B B
    2  A A B B A B
    3 A A B B B A
    4 A B A A B B
    5 A B B A A B
    6 A B B B A A
    7 A B A B A B
    8 A B A B B A
    9 A B B A B A 
     

    怎么样 看了半天 也没看出规律吗?肯定还有很多疑问?ABC各代表啥意思?

    • ABC分别代表:A类编码 B类编码 C类编码 对应的数字0~9表现形式
    • AB都为左侧数据 规律是都以0开始1结尾。
    • C为右侧数据 规律是都以1开始0结尾。

    所以右边不用考虑了
    左边就要看第二个表了,看到 最开始说的那图没有

    条形码下面一串数字最左边的 看到没有6 这是称之为导入值 其实是国家代码。中国的都为6。
    第二个表的第6行:6A B B B A A 。总共13个数字 左右护线各6个数字 A B B B A A 是不是正好6个,所以左边就在A类编码与B类编码之间按照A B B B A A的规则来回变换来对对应的数字进行编码。而右边始终按照C类编码方式处理。更细心的同学可能发现了 A类跟C类是互补的  B类跟C类是对称的 不知亲爱的你发现没有。

    是不是忘了什么东西?忘了最前面说的了么 ean-13 有效位只有11位   分隔符右边6位数有效位只有5位 最后一位是检查码 。也就是计算机里常说的奇偶效验啥的 防止标签污损 或者有人篡改而设置的。
    检查码之计算步骤如下:
    C1 = N1+ N3+N5+N7+N9+N11 C2 = (N2+N4+N6+N8+N10+N12)× 3
    CC = (C1+C2) 取个位数 C (检查码) = 10 - CC  (若值为10,则取0)


    EAN标准码的尺寸:
    条码部分:宽31.35mm 高23.18mm
    全部 :宽37.29mm 高26.26mm
    可放大倍数:0.8 ----- 2

    我需要怎么做?考验你动手能力的时候到了

    好了 能量蓄得差不多了 哥要发大招了

    先建个Bar类
    第一步 做什么事情都先在脑子里思考下 你需要干什么 你得先确定下几个固定的变量吧,在生成条码之前你不可能不验证下数据的正确性吧?:

     1 int[] code ;//原始数据
     2 Bitmap result;//条形码图案
     3 int x;//第n个竖线单位 竖线单位数=6x7+6x7+3+3+5=95
     4 Brush[] cors = { Brushes.White, Brushes.Black };//背景色和前景色
     5 int zoom = 2;//分辨率 默认为1单位像素
     6 
     7 public Bitmap generate(string _code)
     8 {
     9     //初始化数据
    10     code=new int[12];
    11     result= new Bitmap(95*zoom+7*zoom, 55*zoom);//条码宽度单位
    12     x=0;
    13     //验证  必须为12位数字
    14     if (RegexValidate("^[0-9]{12}$", _code) == false)
    15     {
    16         throw new Exception("数据格式错误,必须为12位的数字组合");
    17         return result;
    18     }
    19     else
    20     {
    21         for (int i = 0; i < code.Length; i++)
    22             code[i] = int.Parse(_code[i].ToString());
    23     }
    24     Graphics g = Graphics.FromImage(result);
    25     g.FillRectangle(cors[0], 0, 0, result.Width, result.Height);
    26     //条码生成算法部分
    27     for (int i = 0; i <= 11; i++)
    28     {
    29         step(i);
    30     }
    31     //清空数据
    32     code = new int[12];
    33     //result = new Bitmap(95, 44);
    34     x = 0;
    35     return result;
    36 }
    37 
    38 public static bool RegexValidate(string regexString, string validateString)
    39 {
    40     Regex regex = new Regex(regexString);
    41     return regex.IsMatch(validateString.Trim());
    42 }

    第二步 然后你得把上面那两张表格抄下来 至于抄的方式嘛 各有各的C++的 java的 C#的:

     1 enum codeType
     2 {
     3     A, B, C
     4 }
     5 codeType getCp(int left)//left 0~5
     6 {
     7     codeType[][] cps = new codeType[10][];
     8     cps[0] = new codeType[] { codeType.A, codeType.A, codeType.A, codeType.A, codeType.A, codeType.A };
     9     cps[1] = new codeType[] { codeType.A, codeType.A, codeType.B, codeType.A, codeType.B, codeType.B };
    10     cps[2] = new codeType[] { codeType.A, codeType.A, codeType.B, codeType.B, codeType.A, codeType.B };
    11     cps[3] = new codeType[] { codeType.A, codeType.A, codeType.B, codeType.B, codeType.B, codeType.A };
    12     cps[4] = new codeType[] { codeType.A, codeType.B, codeType.A, codeType.A, codeType.B, codeType.B };
    13 
    14     cps[5] = new codeType[] { codeType.A, codeType.B, codeType.B, codeType.A, codeType.A, codeType.B };
    15     cps[6] = new codeType[] { codeType.A, codeType.B, codeType.B, codeType.B, codeType.A, codeType.A };
    16     cps[7] = new codeType[] { codeType.A, codeType.B, codeType.A, codeType.B, codeType.A, codeType.B };
    17     cps[8] = new codeType[] { codeType.A, codeType.B, codeType.A, codeType.B, codeType.B, codeType.A };
    18     cps[9] = new codeType[] { codeType.A, codeType.B, codeType.B, codeType.A, codeType.B, codeType.A };
    19 
    20     int first = int.Parse(code[0].ToString());
    21     return cps[first][left];
    22 }
    23 
    24 byte getCodeVal(int val, codeType cp)
    25 {
    26     byte[,] values ={
    27         {Convert.ToByte("0001101", 2),Convert.ToByte("0100111", 2),Convert.ToByte("1110010", 2)},
    28         {Convert.ToByte("0011001", 2),Convert.ToByte("0110011", 2),Convert.ToByte("1100110", 2)},
    29         {Convert.ToByte("0010011", 2),Convert.ToByte("0011011", 2),Convert.ToByte("1101100", 2)},
    30         {Convert.ToByte("0111101", 2),Convert.ToByte("0100001", 2),Convert.ToByte("1000010", 2)},
    31         {Convert.ToByte("0100011", 2),Convert.ToByte("0011101", 2),Convert.ToByte("1011100", 2)},
    32 
    33         {Convert.ToByte("0110001", 2),Convert.ToByte("0111001", 2),Convert.ToByte("1001110", 2)},
    34         {Convert.ToByte("0101111", 2),Convert.ToByte("0000101", 2),Convert.ToByte("1010000", 2)},
    35         {Convert.ToByte("0111011", 2),Convert.ToByte("0010001", 2),Convert.ToByte("1000100", 2)},
    36         {Convert.ToByte("0110111", 2),Convert.ToByte("0001001", 2),Convert.ToByte("1001000", 2)},
    37         {Convert.ToByte("0001011", 2),Convert.ToByte("0010111", 2),Convert.ToByte("1110100", 2)}
    38     };
    39     switch (cp)
    40     {
    41         case codeType.A:
    42             return values[val, 0];
    43         case codeType.B:
    44             return values[val, 1];
    45         case codeType.C:
    46             return values[val, 2];
    47         default:
    48             return 0;
    49     }
    50 }               

    第三步 即将大功告成了 看到上面你也了解了 只要调用getCodeVal函数 就可以逐一获取12个编码的竖条的原始数据 ,虽然每个编码只占7跟竖线 但是为了方便我们依然用一个字节来表示。你要问怎么确定12个编码每个的竖线显示与不显示 这个是位运算 别说俺没告诉你哦。0x40二进制是1000000,和原始字节码数据进行“与”运算即可得到结果 其他的以此类推。
    大招祭出:

     1 //按步骤写入单个码,全局变量code 索引 0~11
     2         //索引为0时 是模式匹配,所以只会写入起始符
     3         //索引为6时 左数据结束 会写入中间的分隔符
     4         //索引为11时为最后一个 会写入效验码和结束符
     5         public void step(int codeIndx)
     6         {
     7             Graphics g = Graphics.FromImage(result);
     8             g.TranslateTransform(7 * zoom, 0);
     9             if (codeIndx == 0)
    10             {
    11                 //导入值字符
    12                 g.DrawString(code[codeIndx].ToString(), new Font(FontFamily.GenericSerif, 7f * zoom), cors[1], new PointF(x - 7 * zoom, result.Height * 0.76f));
    13                 //起始符
    14                 g.FillRectangle(cors[1], x, 0, zoom, result.Height); x += zoom;
    15                 g.FillRectangle(cors[0], x, 0, zoom, result.Height); x += zoom;
    16                 g.FillRectangle(cors[1], x, 0, zoom, result.Height); x += zoom;
    17             }
    18             else
    19             {
    20                 //底端字符
    21                 g.DrawString(code[codeIndx].ToString(), new Font(FontFamily.GenericSerif, 7f * zoom), cors[1], new PointF(x, result.Height * 0.76f));
    22 
    23                 //提取码
    24                 byte csh_1 = 0x40, csh_2 = 0x20, csh_3 = 0x10, csh_4 = 0x8, csh_5 = 0x4, csh_6 = 0x2, csh_7 = 0x1;
    25 
    26                 //数据位
    27                 byte coded = getCodeVal(code[codeIndx], codeIndx < 7 ? getCp(codeIndx - 1) : codeType.C);
    28                 g.FillRectangle((coded & csh_1) == 0 ? cors[0] : cors[1], x, 0, zoom, result.Height * 0.76f); x += zoom;
    29                 g.FillRectangle((coded & csh_2) == 0 ? cors[0] : cors[1], x, 0, zoom, result.Height * 0.76f); x += zoom;
    30                 g.FillRectangle((coded & csh_3) == 0 ? cors[0] : cors[1], x, 0, zoom, result.Height * 0.76f); x += zoom;
    31                 g.FillRectangle((coded & csh_4) == 0 ? cors[0] : cors[1], x, 0, zoom, result.Height * 0.76f); x += zoom;
    32                 g.FillRectangle((coded & csh_5) == 0 ? cors[0] : cors[1], x, 0, zoom, result.Height * 0.76f); x += zoom;
    33                 g.FillRectangle((coded & csh_6) == 0 ? cors[0] : cors[1], x, 0, zoom, result.Height * 0.76f); x += zoom;
    34                 g.FillRectangle((coded & csh_7) == 0 ? cors[0] : cors[1], x, 0, zoom, result.Height * 0.76f); x += zoom;
    35 
    36                 if (codeIndx == 11)
    37                 {
    38                     //效验码
    39                     int C1 = code[0] + code[2] + code[4] + code[6] + code[8] + code[10];
    40                     int C2 = (code[1] + code[3] + code[5] + code[7] + code[9] + code[11]) * 3;
    41                     int CC = C1 + C2;
    42                     int C = CC % 10;
    43                     C = 10 - C;
    44                     if (C == 10)
    45                         C = 0;
    46                     coded = getCodeVal(C, codeType.C);
    47                     //效验字符
    48                     g.DrawString(C.ToString(), new Font(FontFamily.GenericSerif, 7f * zoom), cors[1], new PointF(x, result.Height * 0.76f));
    49 
    50                     g.FillRectangle((coded & csh_1) == 0 ? cors[0] : cors[1], x, 0, zoom, result.Height * 0.76f); x += zoom;
    51                     g.FillRectangle((coded & csh_2) == 0 ? cors[0] : cors[1], x, 0, zoom, result.Height * 0.76f); x += zoom;
    52                     g.FillRectangle((coded & csh_3) == 0 ? cors[0] : cors[1], x, 0, zoom, result.Height * 0.76f); x += zoom;
    53                     g.FillRectangle((coded & csh_4) == 0 ? cors[0] : cors[1], x, 0, zoom, result.Height * 0.76f); x += zoom;
    54                     g.FillRectangle((coded & csh_5) == 0 ? cors[0] : cors[1], x, 0, zoom, result.Height * 0.76f); x += zoom;
    55                     g.FillRectangle((coded & csh_6) == 0 ? cors[0] : cors[1], x, 0, zoom, result.Height * 0.76f); x += zoom;
    56                     g.FillRectangle((coded & csh_7) == 0 ? cors[0] : cors[1], x, 0, zoom, result.Height * 0.76f); x += zoom;
    57                 }
    58             }
    59 
    60             if (codeIndx == 6)
    61             {
    62                 //分隔符
    63                 g.FillRectangle(cors[0], x, 0, zoom, result.Height); x += zoom;
    64                 g.FillRectangle(cors[1], x, 0, zoom, result.Height); x += zoom;
    65                 g.FillRectangle(cors[0], x, 0, zoom, result.Height); x += zoom;
    66                 g.FillRectangle(cors[1], x, 0, zoom, result.Height); x += zoom;
    67                 g.FillRectangle(cors[0], x, 0, zoom, result.Height); x += zoom;
    68             }
    69             else if (codeIndx == 11)
    70             {
    71                 //结束符
    72                 g.FillRectangle(cors[1], x, 0, zoom, result.Height); x += zoom;
    73                 g.FillRectangle(cors[0], x, 0, zoom, result.Height); x += zoom;
    74                 g.FillRectangle(cors[1], x, 0, zoom, result.Height); x += zoom;
    75             }
    76 
    77         }
    1 static void Main(string[] args)
    2 {
    3     Bar b = new Bar();
    4     //694492600001
    5     //692645690038
    6     //692173496230
    7     //669859324875
    8     b.generate("692173496230").Save("a.bmp", System.Drawing.Imaging.ImageFormat.Bmp);
    9 }

    找个代表性的 :


    kao 又是这 - -! 就不能换点别的。
    运行代码把它打印出来 打印机无要求 办公室普通的激光打印机即可,只要不是太破 。像那种碳粉质量又差 感光鼓又快不行了 打印出来的图片一片片的白道道 你还是趁早换掉吧 用着都伤心。见证奇鸡的时刻到了 ,是不是很鸡冻 。哥们儿掏出你的手机 来“扫一扫”吧。俺也忍不住来试一下,牛皮不是吹的 火车不是推的 看下哥这码能读不?
    拍照的手机像素有点低哈 见谅,上图:

    话说本帖上面的条码是啥东东 ?你知道了么?

    后记

    ean-13虽然是一种商品条形码 你也完全可以把它当成文档编号 或者应用到其他领域,如果是其他领域建议还是不要用ean-13 因为有专门用于 档案ID 或者物流ID 领域的codabar码 异曲同工 我就不多讲了。可以看到条形码 是一种完全开放的编码方式。也完全不是一种防伪码 更不带加密功能  因为数据容量少所以功能也有限 所以必须得借助数据库。想要那些的话切搞带芯片存储和算法加密的CPU卡吧。传统的银行卡称之为磁卡,说白了就是以“磁”的方式记录数字ID,并没有任何保护措施。所以要复制银行卡也不是什么太高科技的。你要想取出来钱儿 还是得要密码的 嘿嘿。
    虽然功能有限 但是 但是丝毫不影响他。条码是一种最方便 最廉价的解决 方案 ,尤其在物流 医疗等各种信息化系统领域 的应用无处不在,随便看下你身边你就会发现条形码。我身边就有一个: 网站信息备案申请单 左上角就有条形码。作为文档的电子ID印在左上角 方便调阅 管理。
    这就是科技的力量 ,世界上每天都会进行条码扫描达10亿次,他为我们生活提供了诸多方便 。

    闲扯点别的
    虽然啥都没学过从来没写半句代码 ,对我来说要弄android平台的程序也不是啥难事 只是懒得去整。
    现如今这个所谓的“移动平台”铺天盖地 大有势不可挡,当然也不乏催生了很多优秀的应用 优秀的开发者。
    当然也有好多乱七八糟的广告 垃圾。有时候呢也没有必要太跟风 你说呢。顺其自然吧。
    这次就不给源码文件了 哈 都贴在页面上了,如果你真的需要 ctr+c ctr+v都懒得按一下的话 也太伤我自尊了吧

  • 相关阅读:
    RTP 协议
    RTSP 协议分析 (一)
    RTSP协议分析(二)
    CreateRemoteThread简单应用
    函数开始处的MOV EDI, EDI的作用
    eax,ebx,ecx,edx,esi,edi,ebp,esp寄存器的作用
    C++函数调用原理理解
    WinDBG常用断点命令
    利用bu命令下延迟断点
    Windbg 查看SSDT表
  • 原文地址:https://www.cnblogs.com/assassinx/p/barcode.html
Copyright © 2020-2023  润新知