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源码了解原理,先看看顶层调用的代码:
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方法
@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源码
@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方法,继续找
@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)返回编码内容所对应的编码数组,具体编码数组生成规则就不贴出来了
int sidesMargin = getDefaultMargin();
if (hints != null) {
Integer sidesMarginInt = (Integer) hints.get(EncodeHintType.MARGIN);
if (sidesMarginInt != null) {
sidesMargin = sidesMarginInt;
}
}
从上面可以看出,ZXing先自动获取了一个Margin默认值,然后查看hints参数集合中是否存在参数EncodeHintType.MARGIN,存在则替换默认值
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值来设定边距,而且参考编码内容和用户设定的宽度共同计算的!
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 条码边距及总宽度-默认计算规则如下
//放大倍数(取整)
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时,则条码无边框
<dependency>
<groupId>com.google.zxinggroupId>
<artifactId>javaseartifactId>
<version>3.2.1version>
dependency>
<dependency>
<groupId>com.google.zxinggroupId>
<artifactId>coreartifactId>
<version>3.2.1version>
dependency>
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;
}
}
调用工具类生成条形码
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;
}
}
ZXing生成二维码,并自定义绘画文字,可参考我另一篇博客:ZXing二维码自定义绘画文字
欢迎访问本文的个人博客链接: https://br-bai.github.io/2019/04/12/Java生成条形码,使用ZXing框架,并去除条码两边空白/