QRCode 二维码的生成

2021-07-12  本文已影响0人  feifei_fly

一、什么是二维码

二维码又称QR Code,QR全称Quick Response,是一个近几年来移动设备上超流行的一种编码方式,它比传统的Bar Code条形码能存更多的信息,也能表示更多的数据类型,比如:字符,数字,日文,中文等等。

二、基础知识

2.1、version

version 代表二维码的尺寸。二维码一共有40个尺寸,官方叫版本Version。
Version 1是21 x 21的矩阵,Version 2是 25 x 25的矩阵,Version 3是29的尺寸。
每增加一个version,就会增加4的尺寸

二维码宽度与Version的关系。

QRwidth = (Version-1)*4 + 21

最高Version 40,(40-1)*4+21 = 177,所以最高是177 x 177 的正方形。

二维码结构图

2.2、定位图案

2.3、功能性数据

2.4、数据码和纠错码

除了上述的那些地方,剩下的地方存放 Data Code 数据码 和 Error Correction Code 纠错码。

数据编码

我们先来说说数据编码。QR码支持如下的编码:

Numeric mode 数字编码,从0到9。

如果需要编码的数字的个数不是3的倍数,那么,最后剩下的1或2位数会被转成4或7bits。则其它的每3位数字会被编成 10,12,14bits,编成多长还要看二维码的尺寸

Alphanumeric mode 字符编码

包括 0-9,大写的A到Z(没有小写),以及符号$ % * + – . / : 包括空格。这些字符会映射成一个字符索引表。如下所示:(其中的SP是空格,Char是字符,Value是其索引值)。编码的过程是把字符两两分组,然后转成下表的45进制,然后转成11bits的二进制。如果最后有一个落单的,那就转成6bits的二进制。而编码模式和字符的个数需要根据不同的Version尺寸编成9, 11或13个二进制(如下表中Table 3)

image

Byte mode, 字节编码

可以是0-255的ISO-8859-1字符。有些二维码的扫描器可以自动检测是否是UTF-8的编码。

Kanji mode

这是日文编码,也是双字节编码。同样,也可以用于中文编码。

文和汉字的编码会减去一个值。如:在0X8140 to 0X9FFC中的字符会减去8140,在0XE040到0XEBBF中的字符要减去0XC140,然后把结果前两个16进制位拿出来乘以0XC0,然后再加上后两个16进制位,最后转成13bit的编码。

image

Extended Channel Interpretation (ECI) mode

主要用于特殊的字符集。并不是所有的扫描器都支持这种编码。

Structured Append mode 用于混合编码,也就是说,这个二维码中包含了多种编码格式。

FNC1 mode 这种编码方式主要是给一些特殊的工业或行业用的,比如GS1条形码之类的。

纠错码

Error Correction Code Level,二维码中有四种级别的纠错,这就是为什么二维码有残缺还能扫出来

image

三、画二维码

image image image

这15个bits中包括:

- 5个数据bits:其中,2个bits用于表示使用什么样的Error Correction Level, 3个bits表示使用什么样的Mask
- 10个纠错bits。主要通过BCH Code来计算

然后15个bits还要与101010000010010做XOR操作。这样就保证不会因为我们选用了00的纠错级别和000的Mask,从而造成全部为白色

再接下来是Version Information(版本7以后需要这个编码),下图中的蓝色部分

image

Version Information一共是18个bits,其中包括6个bits的版本号以及12个bits的纠错码,下面是一个示例:

image image

然后是填接我们的最终编码,最终编码的填充方式如下:从左下角开始沿着红线填我们的各个bits,1是黑色,0是白色。如果遇到了上面的非数据区,则绕开或跳过。

image image

下面是Mask后的一些样子,我们可以看到被某些Mask XOR了的数据变得比较零散了。


image

四、代码生成二维码

利用Zxing 生成二维码

build.gradle 引入zxing-lite

implementation 'com.king.zxing:zxing-lite:1.1.7-androidx'

利用CodeUtils 可以生成一个承载二维码的bitmap

  private fun doGenerateQRcode(content: String, ratio: Float): Bitmap {
        val bmp = BitmapFactory.decodeResource(resources, R.drawable.ic_default_profile)
        val qrCode: Bitmap = CodeUtils.createQRCode(content, 600, bmp, ratio)
        return qrCode
    }
    /**
     * 生成二维码
     * @param content 二维码的内容
     * @param heightPix 二维码的高
     * @param logo 二维码中间的logo
     * @param ratio  logo所占比例 因为二维码的最大容错率为30%,所以建议ratio的范围小于0.3
     * @return
     */
    public static Bitmap createQRCode(String content, int heightPix, Bitmap logo,@FloatRange(from = 0.0f,to = 1.0f)float ratio) {
        //配置参数
        Map<EncodeHintType, Object> hints = new HashMap<>();
        hints.put( EncodeHintType.CHARACTER_SET, "utf-8");
        //容错级别
        hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);
        //设置空白边距的宽度
        hints.put(EncodeHintType.MARGIN, 1); //default is 4
        return createQRCode(content,heightPix,logo,ratio,hints);
    }
    public static Bitmap createQRCode(String content, int heightPix, Bitmap logo,@FloatRange(from = 0.0f,to = 1.0f)float ratio,Map<EncodeHintType,?> hints,int codeColor) {
        try {

            // 图像数据转换,使用了矩阵转换
            BitMatrix bitMatrix = new QRCodeWriter().encode(content, BarcodeFormat.QR_CODE, heightPix, heightPix, hints);
            int[] pixels = new int[heightPix * heightPix];
            // 下面这里按照二维码的算法,逐个生成二维码的图片,
            // 两个for循环是图片横列扫描的结果
            for (int y = 0; y < heightPix; y++) {
                for (int x = 0; x < heightPix; x++) {
                    if (bitMatrix.get(x, y)) {
                        pixels[y * heightPix + x] = codeColor;
                    } else {
                        pixels[y * heightPix + x] = Color.WHITE;
                    }
                }
            }

            // 生成二维码图片的格式
            Bitmap bitmap = Bitmap.createBitmap(heightPix, heightPix, Bitmap.Config.ARGB_8888);
            bitmap.setPixels(pixels, 0, heightPix, 0, 0, heightPix, heightPix);

            if (logo != null) {
                bitmap = addLogo(bitmap, logo,ratio);
            }

            return bitmap;
        } catch (WriterException e) {
            Log.w(CaptureHelper.TAG,e.getMessage());
        }

        return null;
    }

二维码生成 最核心的代码:

  BitMatrix bitMatrix = new QRCodeWriter().encode(content, BarcodeFormat.QR_CODE, heightPix, heightPix, hints);
  @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 (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);
  }

EncodeHintType 代表二维码的一些格式化参数,可以指定二维码的纠错级别、字符集、外边框(白色边框)的宽度、二维码版本(QR_VERSION)等。

public enum EncodeHintType {

  /**
   * Specifies what degree of error correction to use, for example in QR Codes.
   * Type depends on the encoder. For example for QR codes it's type
   * {@link com.google.zxing.qrcode.decoder.ErrorCorrectionLevel ErrorCorrectionLevel}.
   * For Aztec it is of type {@link Integer}, representing the minimal percentage of error correction words.
   * For PDF417 it is of type {@link Integer}, valid values being 0 to 8.
   * In all cases, it can also be a {@link String} representation of the desired value as well.
   * Note: an Aztec symbol should have a minimum of 25% EC words.
   */
  ERROR_CORRECTION,

  /**
   * Specifies what character encoding to use where applicable (type {@link String})
   */
  CHARACTER_SET,

  /**
   * Specifies the matrix shape for Data Matrix (type {@link com.google.zxing.datamatrix.encoder.SymbolShapeHint})
   */
  DATA_MATRIX_SHAPE,

  /**
   * Specifies a minimum barcode size (type {@link Dimension}). Only applicable to Data Matrix now.
   *
   * @deprecated use width/height params in
   * {@link com.google.zxing.datamatrix.DataMatrixWriter#encode(String, BarcodeFormat, int, int)}
   */
  @Deprecated
  MIN_SIZE,

  /**
   * Specifies a maximum barcode size (type {@link Dimension}). Only applicable to Data Matrix now.
   *
   * @deprecated without replacement
   */
  @Deprecated
  MAX_SIZE,

  /**
   * Specifies margin, in pixels, to use when generating the barcode. The meaning can vary
   * by format; for example it controls margin before and after the barcode horizontally for
   * most 1D formats. (Type {@link Integer}, or {@link String} representation of the integer value).
   */
  MARGIN,

  /**
   * Specifies whether to use compact mode for PDF417 (type {@link Boolean}, or "true" or "false"
   * {@link String} value).
   */
  PDF417_COMPACT,

  /**
   * Specifies what compaction mode to use for PDF417 (type
   * {@link com.google.zxing.pdf417.encoder.Compaction Compaction} or {@link String} value of one of its
   * enum values).
   */
  PDF417_COMPACTION,

  /**
   * Specifies the minimum and maximum number of rows and columns for PDF417 (type
   * {@link com.google.zxing.pdf417.encoder.Dimensions Dimensions}).
   */
  PDF417_DIMENSIONS,

  /**
   * Specifies the required number of layers for an Aztec code.
   * A negative number (-1, -2, -3, -4) specifies a compact Aztec code.
   * 0 indicates to use the minimum number of layers (the default).
   * A positive number (1, 2, .. 32) specifies a normal (non-compact) Aztec code.
   * (Type {@link Integer}, or {@link String} representation of the integer value).
   */
   AZTEC_LAYERS,

   /**
    * Specifies the exact version of QR code to be encoded.
    * (Type {@link Integer}, or {@link String} representation of the integer value).
    */
   QR_VERSION,

  /**
   * Specifies whether the data should be encoded to the GS1 standard (type {@link Boolean}, or "true" or "false"
   * {@link String } value).
   */
  GS1_FORMAT,
}

示例代码,定制二维码参数

private fun testCreateQRCodeBySelf():Bitmap{
        val str = "https://www.baidu.com"

        val hints = HashMap<EncodeHintType, Any>()
        //编码格式
        hints[EncodeHintType.CHARACTER_SET] = "utf-8"
        //容错级别
        hints[EncodeHintType.ERROR_CORRECTION] = ErrorCorrectionLevel.H
        //设置空白边距的宽度
        hints[EncodeHintType.MARGIN] = 1 //default is 4
        //QR 版本->决定二维码大小
        hints[EncodeHintType.QR_VERSION] = 3

        return createQRCodeBySelf(str, 100,hints)
    }

    private fun createQRCodeBySelf(content: String, heightPix: Int, hints: Map<EncodeHintType, Any>):Bitmap{
        // 图像数据转换,使用了矩阵转换
        val bitMatrix =
            QRCodeWriter().encode(content, BarcodeFormat.QR_CODE, heightPix, heightPix, hints)
        val pixels = IntArray(heightPix * heightPix)
        // 下面这里按照二维码的算法,逐个生成二维码的图片,
        // 两个for循环是图片横列扫描的结果
        // 下面这里按照二维码的算法,逐个生成二维码的图片,
        // 两个for循环是图片横列扫描的结果
        for (y in 0 until heightPix) {
            for (x in 0 until heightPix) {
                if (bitMatrix[x, y]) {
                    pixels[y * heightPix + x] = Color.BLACK
                } else {
                    pixels[y * heightPix + x] = Color.WHITE
                }
            }
        }

        // 生成二维码图片的格式
        val bitmap = Bitmap.createBitmap(heightPix, heightPix, Bitmap.Config.ARGB_8888)
        bitmap.setPixels(pixels, 0, heightPix, 0, 0, heightPix, heightPix)
        return bitmap
    }

六、参考文章

https://www.cnblogs.com/alantu2018/p/8504373.html

上一篇下一篇

猜你喜欢

热点阅读