一、什么是二维码
二维码又称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)
Byte mode, 字节编码
可以是0-255的ISO-8859-1字符。有些二维码的扫描器可以自动检测是否是UTF-8的编码。
Kanji mode
这是日文编码,也是双字节编码。同样,也可以用于中文编码。
文和汉字的编码会减去一个值。如:在0X8140 to 0X9FFC中的字符会减去8140,在0XE040到0XEBBF中的字符要减去0XC140,然后把结果前两个16进制位拿出来乘以0XC0,然后再加上后两个16进制位,最后转成13bit的编码。
Extended Channel Interpretation (ECI) mode
主要用于特殊的字符集。并不是所有的扫描器都支持这种编码。
Structured Append mode 用于混合编码,也就是说,这个二维码中包含了多种编码格式。
FNC1 mode 这种编码方式主要是给一些特殊的工业或行业用的,比如GS1条形码之类的。
纠错码
Error Correction Code Level,二维码中有四种级别的纠错,这就是为什么二维码有残缺还能扫出来
三、画二维码
- Position Detection Pattern
首先,先把Position Detection图案画在三个角上。(无论Version如何,这个图案的尺寸就是这么大)
- Alignment Pattern
关于Alignment的位置
-
Timing Pattern
-
Formation Information
再接下来是Formation Information,下图中的蓝色部分。
Formation Information是一个15个bits的信息,每一个bit的位置如下图所示:(注意图中的Dark Module,那是永远出现的)
这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以后需要这个编码),下图中的蓝色部分
Version Information一共是18个bits,其中包括6个bits的版本号以及12个bits的纠错码,下面是一个示例:
- 数据和数据纠错码
然后是填接我们的最终编码,最终编码的填充方式如下:从左下角开始沿着红线填我们的各个bits,1是黑色,0是白色。如果遇到了上面的非数据区,则绕开或跳过。
- 掩码图案
这样下来,我们的图就填好了,但是,也许那些点并不均衡,如果出现大面积的空白或黑块,会告诉我们扫描识别的困难。所以,我们还要做Masking操作。QR有8个Mask你可以使用,如下所示。所谓mask,说白了,就是和上面生成的图做XOR操作。Mask只会和数据区进行XOR,不会影响功能区。
下面是Mask后的一些样子,我们可以看到被某些Mask XOR了的数据变得比较零散了。
四、代码生成二维码
利用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
}