二维码的生成方法网上一搜一大堆,但基本都是直接以字符串进行生成,存储访问路径或一些简单的信息。然而有时就是有这么特殊的需求,需要使用二维码传输数据,数据量还比较大。数据量太大二维码不好生成和扫描,就想到压缩数据,使用jdk的GZIP压缩,得到一个byte数组。byte数组无法生成二维码,直接new String()转回字符串,看下数据量比压缩前还大,base64编码一下,虽然比压缩前小,但是比编码前大多了。那么是否可以直接使用数组生成二维码呢?
改写的jar:重新编译的可使用byte[]生成二维码的zxing-core-3.4.0
使用方法:java压缩字符串并生成二维码
对zxing的core-3.4.0源码进行追踪、查看,new MultiFormatWriter().encode()选择生成QRCode。
我们要生成QRCode,其它的生成方式就不需要,直接改写为:
public BitMatrix encodeQRCode(byte[] contents, int width, int height, Map<EncodeHintType,?> hints) throws WriterException {
QRCodeWriter writer = new QRCodeWriter();
return writer.encodeQRCode(contents, width, height, hints);
}
追踪进入QRCodeWriter的encode方法。
这里主要是对参数的验证,只需要把encode方法改为我们自定义的encodeQRCode方法:
public BitMatrix encodeQRCode(byte[] contents, int width, int height, Map<EncodeHintType,?> hints) throws WriterException {
if (contents == null || contents.length == 0) {
throw new IllegalArgumentException("Found empty contents");
}
if (width < 0 || height < 0) {
throw new IllegalArgumentException("Requested dimensions are too small: " + width + 'x' +
height);
}
ErrorCorrectionLevel errorCorrectionLevel = ErrorCorrectionLevel.L;
int quietZone = QUIET_ZONE_SIZE;
if (hints != null) {
if (hints.containsKey(EncodeHintType.ERROR_CORRECTION)) {
errorCorrectionLevel = ErrorCorrectionLevel.valueOf(hints.get(EncodeHintType.ERROR_CORRECTION).toString());
}
if (hints.containsKey(EncodeHintType.MARGIN)) {
quietZone = Integer.parseInt(hints.get(EncodeHintType.MARGIN).toString());
}
}
QRCode code = Encoder.encodeQRCode(contents, errorCorrectionLevel, hints);
return renderResult(code, width, height, quietZone);
}
继续追踪进入Encoder的encode方法。
去掉一些不需要的验证,把appendBytes方法改写:
public static QRCode encodeQRCode(byte[] content, ErrorCorrectionLevel ecLevel,
Map<EncodeHintType,?> hints) throws WriterException {
// Pick an encoding mode appropriate for the content. Note that this will not attempt to use
// multiple modes / segments even if that were more efficient. Twould be nice.
Mode mode = Mode.BYTE;
// This will store the header information, like mode and
// length, as well as "header" segments like an ECI segment.
BitArray headerBits = new BitArray();
// Append the FNC1 mode header for GS1 formatted data if applicable
boolean hasGS1FormatHint = hints != null && hints.containsKey(EncodeHintType.GS1_FORMAT);
if (hasGS1FormatHint && Boolean.valueOf(hints.get(EncodeHintType.GS1_FORMAT).toString())) {
// GS1 formatted codes are prefixed with a FNC1 in first position mode header
appendModeInfo(Mode.FNC1_FIRST_POSITION, headerBits);
}
// (With ECI in place,) Write the mode marker
appendModeInfo(mode, headerBits);
// Collect data within the main segment, separately, to count its size if needed. Don't add it to
// main payload yet.
BitArray dataBits = new BitArray();
for (byte b : content) {
dataBits.appendBits(b, 8);
}
//二维码的版本从21×21至177×177总共有40个版本,不同的版本可编码的总数据量不同,不指定则自行根据输入的数据量选择最适合的版本。
Version version;
if (hints != null && hints.containsKey(EncodeHintType.QR_VERSION)) {
int versionNumber = Integer.parseInt(hints.get(EncodeHintType.QR_VERSION).toString());
version = Version.getVersionForNumber(versionNumber);
int bitsNeeded = calculateBitsNeeded(mode, headerBits, dataBits, version);
if (!willFit(bitsNeeded, version, ecLevel)) {
throw new WriterException("Data too big for requested version");
}
} else {
version = recommendVersion(ecLevel, mode, headerBits, dataBits);
}
BitArray headerAndDataBits = new BitArray();
headerAndDataBits.appendBitArray(headerBits);
// Find "length" of main segment and write it
int numLetters = dataBits.getSizeInBytes();
appendLengthInfo(numLetters, version, mode, headerAndDataBits);
// Put data together into the overall payload
headerAndDataBits.appendBitArray(dataBits);
Version.ECBlocks ecBlocks = version.getECBlocksForLevel(ecLevel);
int numDataBytes = version.getTotalCodewords() - ecBlocks.getTotalECCodewords();
// Terminate the bits properly.
terminateBits(numDataBytes, headerAndDataBits);
// Interleave data bits with error correction code.
BitArray finalBits = interleaveWithECBytes(headerAndDataBits,
version.getTotalCodewords(),
numDataBytes,
ecBlocks.getNumBlocks());
QRCode qrCode = new QRCode();
qrCode.setECLevel(ecLevel);
qrCode.setMode(mode);
qrCode.setVersion(version);
// Choose the mask pattern and set to "qrCode".
int dimension = version.getDimensionForVersion();
ByteMatrix matrix = new ByteMatrix(dimension, dimension);
int maskPattern = chooseMaskPattern(finalBits, ecLevel, version, matrix);
qrCode.setMaskPattern(maskPattern);
// Build the matrix and set it to "qrCode".
MatrixUtil.buildMatrix(finalBits, ecLevel, version, maskPattern, matrix);
qrCode.setMatrix(matrix);
return qrCode;
}
appendBytes方法实际上是调用了append8BitBytes方法(纯英文、数字、日语汉字不是),上面重写的方法中直接把append8BitBytes方法内的代码复制了进去,appendBytes的具体代码不贴了,append8BitBytes方法代码如下:
从这里可以看到,字符串实际上最终是转成了byte数组,所以我们直接传byte数组是可以的。
二维码生成完了接下来就是解码了,new MultiFormatReader().decode()返回Result对象,不要使用Result对象的getText方法获取数据,会把byte数组直接转成String,使用rawBytes方法获取byte数组,
byte数组含有zxing在生成二维码时附加的一些信息,需要进行解析获取原始的byte数组。对zxing源码进行分析,实际是可以直接获取到原始的byte数组的,zxing的代码不贴了,MultiFormatReader中把decode改写:
public Result decodeQRCode(BinaryBitmap image, Map<DecodeHintType,?> hints) throws NotFoundException {
QRCodeReader reader = new QRCodeReader();
this.readers = new Reader[] {reader};
try {
return reader.decodeQRCode(image, hints);
} catch (Exception e) {
e.printStackTrace();
}
throw NotFoundException.getNotFoundInstance();
}
进入QRCodeReader把decode改写:
public final Result decodeQRCode(BinaryBitmap image, Map<DecodeHintType,?> hints) throws NotFoundException, ChecksumException, FormatException {
DetectorResult detectorResult = new Detector(image.getBlackMatrix()).detect(hints);
DecoderResult decoderResult = decoder.decode(detectorResult.getBits(), hints);
return new Result(decoderResult.getText(), decoderResult.getByteSegments().get(0), detectorResult.getPoints(), BarcodeFormat.QR_CODE);
}
改写完就可以直接使用Result.getRawBytes()获取原始byte数组了,这里实际上就是Result的rawBytes
本应接收decoderResult.getRawBytes(),替换为了decoderResult.getByteSegments().get(0),decoderResult.getByteSegments().get(0)就是原始的byte数组。
为了不影响原有功能,所有的改写都是新建的方法。