带你探索条形码世界的奥秘
序
二维码也就是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都懒得按一下的话 也太伤我自尊了吧