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;
}
}
歡迎訪問本文的個人博客鏈接: https://br-bai.github.io/2019/04/12/Java生成條形碼,使用ZXing框架,並去除條碼兩邊空白/