QRCode 二维码的生成

一、什么是二维码

二维码又称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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容