资讯专栏INFORMATION COLUMN

聊聊zxing的qrcode

cnTomato / 3052人阅读

摘要:序本文主要研究下的的一些代码。可以看到这里重新计算了输出的宽度和高度,是取了的宽高两边的与输入参数的目标宽高取最大值。也就是说如果的宽高大于目标的宽高,则以的宽高为准,这种情况下的基本跟输出的一致。

本文主要研究下zxing的qrcode的一些代码。

maven
        
            com.google.zxing
            core
            3.3.1
        
        
            com.google.zxing
            javase
            3.3.1
        
QRCodeWriter

core-3.3.1-sources.jar!/com/google/zxing/qrcode/QRCodeWriter.java
QRCodeWriter的encode方法进行编码,转换为BitMatrix

  @Override
  public BitMatrix encode(String contents,
                          BarcodeFormat format,
                          int width,
                          int height,
                          Map hints) throws WriterException {

    if (contents.isEmpty()) {
      throw new IllegalArgumentException("Found empty contents");
    }

    if (format != BarcodeFormat.QR_CODE) {
      throw new IllegalArgumentException("Can only encode QR_CODE, but got " + format);
    }

    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.encode(contents, errorCorrectionLevel, hints);
    return renderResult(code, width, height, quietZone);
  }
QRCode

core-3.3.1-sources.jar!/com/google/zxing/qrcode/encoder/QRCode.java

public final class QRCode {

  public static final int NUM_MASK_PATTERNS = 8;

  private Mode mode;
  private ErrorCorrectionLevel ecLevel;
  private Version version;
  private int maskPattern;
  private ByteMatrix matrix;

  public QRCode() {
    maskPattern = -1;
  }

  public Mode getMode() {
    return mode;
  }

  public ErrorCorrectionLevel getECLevel() {
    return ecLevel;
  }

  public Version getVersion() {
    return version;
  }

  public int getMaskPattern() {
    return maskPattern;
  }

  public ByteMatrix getMatrix() {
    return matrix;
  }

  @Override
  public String toString() {
    StringBuilder result = new StringBuilder(200);
    result.append("<<
");
    result.append(" mode: ");
    result.append(mode);
    result.append("
 ecLevel: ");
    result.append(ecLevel);
    result.append("
 version: ");
    result.append(version);
    result.append("
 maskPattern: ");
    result.append(maskPattern);
    if (matrix == null) {
      result.append("
 matrix: null
");
    } else {
      result.append("
 matrix:
");
      result.append(matrix);
    }
    result.append(">>
");
    return result.toString();
  }

  public void setMode(Mode value) {
    mode = value;
  }

  public void setECLevel(ErrorCorrectionLevel value) {
    ecLevel = value;
  }

  public void setVersion(Version version) {
    this.version = version;
  }

  public void setMaskPattern(int value) {
    maskPattern = value;
  }

  public void setMatrix(ByteMatrix value) {
    matrix = value;
  }

  // Check if "mask_pattern" is valid.
  public static boolean isValidMaskPattern(int maskPattern) {
    return maskPattern >= 0 && maskPattern < NUM_MASK_PATTERNS;
  }

}

是由Encoder类encode而来

Encoder

core/3.3.1/core-3.3.1-sources.jar!/com/google/zxing/qrcode/encoder/Encoder.java

 public static QRCode encode(String content,
                              ErrorCorrectionLevel ecLevel,
                              Map hints) throws WriterException {

    // Determine what character encoding has been specified by the caller, if any
    String encoding = DEFAULT_BYTE_MODE_ENCODING;
    boolean hasEncodingHint = hints != null && hints.containsKey(EncodeHintType.CHARACTER_SET);
    if (hasEncodingHint) {
      encoding = hints.get(EncodeHintType.CHARACTER_SET).toString();
    }

    // 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 = chooseMode(content, encoding);

    // This will store the header information, like mode and
    // length, as well as "header" segments like an ECI segment.
    BitArray headerBits = new BitArray();

    // Append ECI segment if applicable
    if (mode == Mode.BYTE && (hasEncodingHint || !DEFAULT_BYTE_MODE_ENCODING.equals(encoding))) {
      CharacterSetECI eci = CharacterSetECI.getCharacterSetECIByName(encoding);
      if (eci != null) {
        appendECI(eci, 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();
    appendBytes(content, mode, dataBits, encoding);

    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 = mode == Mode.BYTE ? dataBits.getSizeInBytes() : content.length();
    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;
  }

这里重点看Version的这段

    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);
    }

这里计算version,同时判断content的大小,是否超出qrcode的容量,超出的话,抛出WriterException("Data too big for requested version")

willFit计算方法

/**
   * @return true if the number of input bits will fit in a code with the specified version and
   * error correction level.
   */
  private static boolean willFit(int numInputBits, Version version, ErrorCorrectionLevel ecLevel) {
      // In the following comments, we use numbers of Version 7-H.
      // numBytes = 196
      int numBytes = version.getTotalCodewords();
      // getNumECBytes = 130
      Version.ECBlocks ecBlocks = version.getECBlocksForLevel(ecLevel);
      int numEcBytes = ecBlocks.getTotalECCodewords();
      // getNumDataBytes = 196 - 130 = 66
      int numDataBytes = numBytes - numEcBytes;
      int totalInputBytes = (numInputBits + 7) / 8;
      return numDataBytes >= totalInputBytes;
  }
QRCodeWriter.renderResult
// Note that the input matrix uses 0 == white, 1 == black, while the output matrix uses
  // 0 == black, 255 == white (i.e. an 8 bit greyscale bitmap).
  private static BitMatrix renderResult(QRCode code, int width, int height, int quietZone) {
    ByteMatrix input = code.getMatrix();
    if (input == null) {
      throw new IllegalStateException();
    }
    int inputWidth = input.getWidth();
    int inputHeight = input.getHeight();
    int qrWidth = inputWidth + (quietZone * 2);
    int qrHeight = inputHeight + (quietZone * 2);
    int outputWidth = Math.max(width, qrWidth);
    int outputHeight = Math.max(height, qrHeight);

    int multiple = Math.min(outputWidth / qrWidth, outputHeight / qrHeight);
    // Padding includes both the quiet zone and the extra white pixels to accommodate the requested
    // dimensions. For example, if input is 25x25 the QR will be 33x33 including the quiet zone.
    // If the requested size is 200x160, the multiple will be 4, for a QR of 132x132. These will
    // handle all the padding from 100x100 (the actual QR) up to 200x160.
    int leftPadding = (outputWidth - (inputWidth * multiple)) / 2;
    int topPadding = (outputHeight - (inputHeight * multiple)) / 2;

    BitMatrix output = new BitMatrix(outputWidth, outputHeight);

    for (int inputY = 0, outputY = topPadding; inputY < inputHeight; inputY++, outputY += multiple) {
      // Write the contents of this row of the barcode
      for (int inputX = 0, outputX = leftPadding; inputX < inputWidth; inputX++, outputX += multiple) {
        if (input.get(inputX, inputY) == 1) {
          output.setRegion(outputX, outputY, multiple, multiple);
        }
      }
    }

    return output;
  }

这个renderResult根据QRCode信息来构造BitMatrix。可以看到这里重新计算了输出的宽度和高度,是取了qrcode的宽高+两边的quietZone与输入参数的目标宽高取最大值。也就是说如果qrcode的宽高大于目标的宽高,则以qrcode的宽高为准,这种情况下的quietZone基本跟输出的一致。

一般而言qrcode的宽高小于目标宽高的话,这种情况下quietZone就跟输出的不一致,需要经过重新放大,得到的才是最后输出的padding。

doc

二维码最大能包含多少信息量?

使用zxing生成和识别二维码

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/67897.html

相关文章

  • 聊聊qrcodedetect position

    摘要:序本文主要介绍下的,也就是定位图案,用于定位一张图片中二维码所处的位置。分类分为三类是和的原型,从到共种尺寸。是的改良版本,添加了对齐标记,从到共种尺寸。这里默认有个的概念,不同的对应不同的尺寸大小。最高,,所以最高是的矩阵。 序 本文主要介绍下qrcode的detect position,也就是定位图案,用于定位一张图片中二维码所处的位置。 qrcode分类 QRCode 分为 Mo...

    legendaryedu 评论0 收藏0
  • 慕课网_《Java生成二维码》学习总结

    摘要:时间年月日星期五说明本文部分内容均来自慕课网。线性堆叠式二维码示意图矩阵式二维码在一个矩形空间通过黑白像素在矩阵中的不同分布进行编码。 时间:2017年06月23日星期五说明:本文部分内容均来自慕课网。@慕课网:http://www.imooc.com教学示例源码:无个人学习源码:https://github.com/zccodere/s... 第一章:二维码的概念 1-1 二维码概述...

    QLQ 评论0 收藏0
  • 前端vue、jquery/后台java实现生成二维码

    摘要:最近项目中需要开发生成二维码的功能,便于宣传和使用产品,于是便去研究一番,以下是研究的成果使用生成二维码二维码测试内容渲染方式有方式兼容和方式计算模式背景颜色二维码颜色二维码纠错级别默认查看源码可以看出生成的二维码使用生成二维 最近项目中需要开发生成二维码的功能,便于宣传和使用产品,于是便去研究一番,以下是研究的成果 1.使用jquery生成二维码 ...

    happyhuangjinjin 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<