ZXing(“zebra crossing”)是一个用Java实现的开源,多格式1D / 2D条形码图像处理库,具有其他语言的端口。
如果不想看源码分析,想直接看代码请跳到最后!!!
常用的Code 128 码与 Code 39 码比较:
Code 128 码与 Code 39 码都广泛运用在企业内部管理、生产流程、物流控制系统方面。不同的在于 Code 128 比 Code 39 能表现更多的字符,单位长度里的编码密度更高。 当单位长度里不能容下 Code 39 编码或编码字符超出了 Code 39 的限制时,就可选择 Code 128 来编码。所以 Code 128 比 Code 39 更具灵性。 CODE128码是1981年引入的一种高密度条码,CODE128 码可表示从 ASCII 0 到ASCII 127 共128个字符,故称128码。其中包含了数字、字母和符号字符。
以下内容以code128编码为例,分析ZXing源码了解原理,先看看顶层调用的代码:
1 2 3 4 5 6 7 8 try { BitMatrix bitMatrix = new MultiFormatWriter ().encode(contents, BarcodeFormat.CODE_128, codeWidth, height, hints); return MatrixToImageWriter.toBufferedImage(bitMatrix); } catch (Exception e) { e.printStackTrace(); }
encode方法有5个参数,hints为ZXing的参数Map集合,查看源码了解具体参数,先查看MultiFormatWriter的code方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 @Override public BitMatrix encode (String contents, BarcodeFormat format, int width, int height, Map<EncodeHintType,?> hints) throws WriterException { Writer writer; switch (format) { case EAN_8: writer = new EAN8Writer (); break ; case EAN_13: writer = new EAN13Writer (); break ; case UPC_A: writer = new UPCAWriter (); break ; case QR_CODE: writer = new QRCodeWriter (); break ; case CODE_39: writer = new Code39Writer (); break ; case CODE_128: writer = new Code128Writer (); break ; case ITF: writer = new ITFWriter (); break ; case PDF_417: writer = new PDF417Writer (); break ; case CODABAR: writer = new CodaBarWriter (); break ; case DATA_MATRIX: writer = new DataMatrixWriter (); break ; case AZTEC: writer = new AztecWriter (); break ; default : throw new IllegalArgumentException ("No encoder available for format " + format); } return writer.encode(contents, format, width, height, hints); }
由上面的源码可以知道,MultiFormatWriter的encode方法调用了实现Writer接口的Code128Writer实例中的encode方法,继续查看Code128Writer的encode源码
1 2 3 4 5 6 7 8 9 10 11 @Override public BitMatrix encode (String contents, BarcodeFormat format, int width, int height, Map<EncodeHintType,?> hints) throws WriterException { if (format != BarcodeFormat.CODE_128) { throw new IllegalArgumentException ("Can only encode CODE_128, but got " + format); } return super .encode(contents, format, width, height, hints); }
发现Code128Writer的encode调用了父类OneDimensionalCodeWriter的encode方法,继续找
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 @Override public BitMatrix encode (String contents, BarcodeFormat format, int width, int height, Map<EncodeHintType,?> hints) throws WriterException { if (contents.isEmpty()) { throw new IllegalArgumentException ("Found empty contents" ); } if (width < 0 || height < 0 ) { throw new IllegalArgumentException ("Negative size is not allowed. Input: " + width + 'x' + height); } int sidesMargin = getDefaultMargin(); if (hints != null ) { Integer sidesMarginInt = (Integer) hints.get(EncodeHintType.MARGIN); if (sidesMarginInt != null ) { sidesMargin = sidesMarginInt; } } boolean [] code = encode(contents); return renderResult(code, width, height, sidesMargin); }
注意此处有调用Code128Writer的重载方法boolean[] encode(String contents)返回编码内容所对应的编码数组,具体编码数组生成规则就不贴出来了
1 2 3 4 5 6 7 int sidesMargin = getDefaultMargin();if (hints != null ) { Integer sidesMarginInt = (Integer) hints.get(EncodeHintType.MARGIN); if (sidesMarginInt != null ) { sidesMargin = sidesMarginInt; } }
从上面可以看出,ZXing先自动获取了一个Margin默认值,然后查看hints参数集合中是否存在参数EncodeHintType.MARGIN,存在则替换默认值
1 2 3 4 5 public int getDefaultMargin () { return 10 ; }
通过查看getDefaultMargin()发现默认边距Margin为10,但是ZXing并不是只看Margin值来设定边距,而且参考编码内容和用户设定的宽度共同计算的!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 private static BitMatrix renderResult (boolean [] code, int width, int height, int sidesMargin) { int inputWidth = code.length; int fullWidth = inputWidth + sidesMargin; int outputWidth = Math.max(width, fullWidth); int outputHeight = Math.max(1 , height); int multiple = outputWidth / fullWidth; int leftPadding = (outputWidth - (inputWidth * multiple)) / 2 ; BitMatrix output = new BitMatrix (outputWidth, outputHeight); for (int inputX = 0 , outputX = leftPadding; inputX < inputWidth; inputX++, outputX += multiple) { if (code[inputX]) { output.setRegion(outputX, 0 , multiple, outputHeight); } } return output; }
分析上面源码可知:
ZXing 条码边距及总宽度-默认计算规则如下
sidesMargin: 默认为10,用户设置了则为hints中EncodeHintType.MARGIN的值
inputWidth: 条码根据编码内容自动生成编码数组长度(code.length)
codeWidth: 用户自定义的条码宽度
fullWidth: 编码数组长度 inputWidth + 边距 sidesMargin
outputWidth: codeWidth 与 fullWidth 的最大值
1 2 3 4 5 6 int multiple = outputWidth / fullWidth;int leftPadding = (outputWidth - (inputWidth * multiple)) / 2 ;生成条码长度为: outputWidth + 2 * leftPadding
想生成的条形码无边距的话,即leftPadding=0,必须设置EncodeHintType.MARGIN为0的同时保证用户给定的宽度为编码数组长度的倍数。
编码数组长度可通过如下计算:
int width = new Code128Writer().encode(contents).length;
即当传入宽度为 width * n,且EncodeHintType.MARGIN=0时,则条码无边框
以下为完整代码: 导入依赖 1 2 3 4 5 6 7 8 9 10 11 12 <dependency > <groupId > com.google.zxing</groupId > <artifactId > javase</artifactId > <version > 3.2.1</version > </dependency > <dependency > <groupId > com.google.zxing</groupId > <artifactId > core</artifactId > <version > 3.2.1</version > </dependency >
条形码生成与解析工具类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 package com.framework.utils.pay;import com.google.zxing.EncodeHintType;import com.google.zxing.oned.Code128Writer;import java.awt.image.BufferedImage;import java.io.File;import java.util.HashMap;import java.util.Map;import javax.imageio.ImageIO;import com.google.zxing.BarcodeFormat;import com.google.zxing.BinaryBitmap;import com.google.zxing.LuminanceSource;import com.google.zxing.MultiFormatReader;import com.google.zxing.MultiFormatWriter;import com.google.zxing.Result;import com.google.zxing.client.j2se.BufferedImageLuminanceSource;import com.google.zxing.client.j2se.MatrixToImageWriter;import com.google.zxing.common.BitMatrix;import com.google.zxing.common.HybridBinarizer;public class BarCodeUtil { public static BufferedImage encode (String contents) { Map<EncodeHintType,Object> hints = new HashMap <>(); hints.put(EncodeHintType.MARGIN, 0 ); int width = new Code128Writer ().encode(contents).length; int height = 70 ; int codeMultiples = 1 ; int codeWidth = width * codeMultiples; try { BitMatrix bitMatrix = new MultiFormatWriter ().encode(contents, BarcodeFormat.CODE_128, codeWidth, height, hints); return MatrixToImageWriter.toBufferedImage(bitMatrix); } catch (Exception e) { e.printStackTrace(); } return null ; } public static String decode (String imgPath) { BufferedImage image = null ; Result result = null ; try { image = ImageIO.read(new File (imgPath)); if (image == null ) { throw new RuntimeException ("the decode image may be not exists." ); } LuminanceSource source = new BufferedImageLuminanceSource (image); BinaryBitmap bitmap = new BinaryBitmap (new HybridBinarizer (source)); result = new MultiFormatReader ().decode(bitmap, null ); return result.getText(); } catch (Exception e) { e.printStackTrace(); } return null ; } }
调用工具类生成条形码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 package com.controller.pay;import java.awt.image.BufferedImage;import java.io.IOException;import javax.imageio.ImageIO;import javax.servlet.ServletOutputStream;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestParam;import com.dtt.sett.framework.utils.pay.BarCodeUtil;@Controller public class BarCodeController extends PayBaseController { @RequestMapping("/getBarCodeImage") public String getBarCodeImage (HttpServletRequest req, HttpServletResponse resp, @RequestParam("paymentCode") String imgcode) { try { BufferedImage buffImg = BarCodeUtil.encode(imgcode); resp.setHeader("Pragma" , "no-cache" ); resp.setHeader("Cache-Control" , "no-cache" ); resp.setDateHeader("Expires" , 0 ); resp.setContentType("image/jpeg" ); ServletOutputStream sos = resp.getOutputStream(); ImageIO.write(buffImg, "jpeg" , sos); sos.close(); } catch (IOException e) { e.printStackTrace(); } return null ; } }