源码解析zxing条码边距及总宽度计算规则,附java使用zxing生成条形码,并去除条码两边空白

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 条码边距及总宽度-默认计算规则如下

  • sidesMargin: 默认为10,用户设置了则为hints中EncodeHintType.MARGIN的值
  • inputWidth: 条码根据编码内容自动生成编码数组长度(code.length)
  • codeWidth: 用户自定义的条码宽度
  • fullWidth: 编码数组长度 inputWidth + 边距 sidesMargin
  • outputWidth: codeWidth 与 fullWidth 的最大值
 //放大倍数(取整)
 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时,则条码无边框



以下为完整代码:

导入依赖

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

条形码生成与解析工具类

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框架,并去除条码两边空白/

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 我行我“速” 设计师:Amelia_0503 返回首页