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() {
// CodaBar spec requires a side margin to be more than ten times wider than narrow space.
// This seems like a decent idea for a default for all formats.
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;
// Add quiet zone on both sides.
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
//放大倍数(取整)
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
<!-- Zxing -->
<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>
<!-- Zxing -->

条形码生成与解析工具类

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
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;

/**
* 条形码工具,内有生成条形码,与解析办法
* @author bhy
*
*/
public class BarCodeUtil {
/**
* 条形码编码
*
* @param contents
* @return
*/
public static BufferedImage encode(String contents) {

//配置条码参数
Map<EncodeHintType,Object> hints = new HashMap<>();
//设置条码两边空白边距为0,默认为10,如果宽度不是条码自动生成宽度的倍数则MARGIN无效
hints.put(EncodeHintType.MARGIN, 0);

//为了无边距,需设置宽度为条码自动生成规则的宽度
int width = new Code128Writer().encode(contents).length;
//前端可控制高度,不影响识别
int height = 70;
//条码放大倍数
int codeMultiples = 1;
//获取条码内容的宽,不含两边距,当EncodeHintType.MARGIN为0时即为条码宽度
int codeWidth = width * codeMultiples;

/* ZXing 条码边距及总宽度-默认计算规则
codeWidth: 自定义的条码宽度
fullWidth: 条码根据编码内容自动生成编码数组长度(new Code128Writer().encode(contents).length)+边距MARGIN
outputWidth: codeWidth 与 fullWidth 的最大值
//放大倍数(取整)
int multiple = outputWidth / fullWidth;
//边距
int leftPadding = (outputWidth - (inputWidth * multiple)) / 2;
生成条码长度为: outputWidth + 2 * leftPadding
*/

try {

// 图像数据转换,使用了矩阵转换 参数顺序分别为:编码内容,编码类型,生成图片宽度,生成图片高度,设置参数
BitMatrix bitMatrix = new MultiFormatWriter().encode(contents,
BarcodeFormat.CODE_128, codeWidth, height, hints);
// MatrixToImageWriter.writeToStream(bitMatrix, "png", new FileOutputStream("d:/code39.png"));
return MatrixToImageWriter.toBufferedImage(bitMatrix);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}

/**
* 解析条形码
*
* @param imgPath
* @return
*/
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;
/**
* 生成条形码
* @author bhy
*
*/
@Controller
public class BarCodeController extends PayBaseController{

/**
* 生成条形码
* @return
*/
@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");

// 将图像输出到Servlet输出流中。
ServletOutputStream sos = resp.getOutputStream();
ImageIO.write(buffImg, "jpeg", sos);
sos.close();
} catch (IOException e) {
e.printStackTrace();
}

return null;
}
}