zxing使用byte数组生成二维码和解析二维码

二维码的生成方法网上一搜一大堆,但基本都是直接以字符串进行生成,存储访问路径或一些简单的信息。然而有时就是有这么特殊的需求,需要使用二维码传输数据,数据量还比较大。数据量太大二维码不好生成和扫描,就想到压缩数据,使用jdk的GZIP压缩,得到一个byte数组。byte数组无法生成二维码,直接new String()转回字符串,看下数据量比压缩前还大,base64编码一下,虽然比压缩前小,但是比编码前大多了。那么是否可以直接使用数组生成二维码呢?

改写的jar:重新编译的可使用byte[]生成二维码的zxing-core-3.4.0
使用方法:java压缩字符串并生成二维码

二维码生成

对zxing的core-3.4.0源码进行追踪、查看,new MultiFormatWriter().encode()选择生成QRCode。
zxing使用byte数组生成二维码和解析二维码_第1张图片
我们要生成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方法。
zxing使用byte数组生成二维码和解析二维码_第2张图片
这里主要是对参数的验证,只需要把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方法。
zxing使用byte数组生成二维码和解析二维码_第3张图片
去掉一些不需要的验证,把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方法代码如下:
zxing使用byte数组生成二维码和解析二维码_第4张图片
从这里可以看到,字符串实际上最终是转成了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数组。

为了不影响原有功能,所有的改写都是新建的方法。

你可能感兴趣的:(qrcode,java)