美文网首页
QRCode 二维码的生成

QRCode 二维码的生成

作者: feifei_fly | 来源:发表于2021-07-12 00:05 被阅读0次

    一、什么是二维码

    二维码又称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、定位图案

    • position Detection Pattern是定位图案,用于标记二维码的矩形大小。这三个定位图案有白边叫Separators for Postion Detection Patterns。之所以三个而不是四个意思就是三个就可以标识一个矩形了。
    • Timing Patterns也是用于定位的。原因是二维码有40种尺寸,尺寸过大了后需要有根标准线,不然扫描的时候可能会扫歪了。
    • Alignment Patterns 只有Version 2以上(包括Version2)的二维码需要这个东东,同样是为了定位用的。

    2.3、功能性数据

    • Format Information 存在于所有的尺寸中,用于存放一些格式化数据的。如数据编码格式(数字、字母集、还是Byte)
    • Version Information 在 >= Version 7以上,需要预留两块3 x 6的区域存放一些版本信息。版本信息主要决定二维码的大小

    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

    三、画二维码

    • Position Detection Pattern
      首先,先把Position Detection图案画在三个角上。(无论Version如何,这个图案的尺寸就是这么大)
    image
    • Alignment Pattern
      关于Alignment的位置
    image
    • Timing Pattern


      Time pattern
    • Formation Information
      再接下来是Formation Information,下图中的蓝色部分。


      image

      Formation Information是一个15个bits的信息,每一个bit的位置如下图所示:(注意图中的Dark Module,那是永远出现的)

    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

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

    image

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

    image image
    • 数据和数据纠错码

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

    image
    • 掩码图案
      这样下来,我们的图就填好了,但是,也许那些点并不均衡,如果出现大面积的空白或黑块,会告诉我们扫描识别的困难。所以,我们还要做Masking操作。QR有8个Mask你可以使用,如下所示。所谓mask,说白了,就是和上面生成的图做XOR操作。Mask只会和数据区进行XOR,不会影响功能区。
    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);
    
    • contents:编码的字符串
    • format:BarcodeFormat 二维码的格式,此处是BarcodeFormat.QR_CODE
    • width 生成的二维码的宽度(px)
    • height 生成二维码的高度(px)
    • hints Map<EncodeHintType,?> 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

    相关文章

      网友评论

          本文标题:QRCode 二维码的生成

          本文链接:https://www.haomeiwen.com/subject/ebnlpltx.html