2021SC@SDUSC Zxing开源代码(三)Zxing编码思路及代码分析
2021SC@SDUSC前言:Zxing是一个开源Java类库用于解析多种格式的条形码和二维码。本篇主要分析Zxing的二维码是如何进行编码生成的。博客分析了Zxing可以支持的多种二维码/条形码编码生成的一般步骤,即各种条码编码的共用关键类。在后续的代码分析中,我将分别对不同的二维码/条形码的编码算法进行分析。
2021SC@SDUSC
目录
前言:Zxing是一个开源Java类库用于解析多种格式的条形码和二维码。本篇主要分析Zxing的二维码是如何进行编码生成的。博客分析了Zxing可以支持的多种二维码/条形码编码生成的一般步骤,即各种条码编码的共用关键类。在后续的代码分析中,我将分别对不同的二维码/条形码的编码算法进行分析。
一、demo引入
在上篇博客的demo中可以看出,二维码的生成,是从下面这个函数开始的:
BitMatrix encode = new MultiFormatWriter().encode(content, BarcodeFormat.QR_CODE, width, height, hints);
BitMatrix的matrix对象包含了一个二维码的所有信息,用户得到它之后就可以绘制二维码了。
MultiFormatWriter是一个factory类,MultiFormatWriter.encode用来对不同的码格式的编码方法(write)进行寻找,实际生成二维码的工作是在各个不同码的Encoder.encoder方法中实现的。
二、编码关键类详解
2.1 Writer
Writer类是生成条形码图像的所有对象的基类, 在实现类可以生成二维码、条形码等其他图形编码。共有两个方法,都是用于生成二维码:
public interface Writer {
BitMatrix encode(String contents,
BarcodeFormat format,
int width,
int height)
throws WriterException;
BitMatrix encode(String contents,
BarcodeFormat format,
int width,
int height,
Map<EncodeHintType,?> hints)
throws WriterException;
}
方法参数说明如下:
参数 | 说明 |
---|---|
String contents | 二维码/条形码中要编码的内容 |
BarcodeFormat format | 要生成的二维码/条形码格式 |
int width | 以像素为单位的首选宽度 |
int height | 以像素为单位的首选高度 |
Map<EncodeHintType,?> hints | 提示向编码器提供其他参数 |
从上面可以看出,除了我们常规认为的编码需要内容之外,还有其他不少的信息,如编码的方式,编码的首选宽高度(首选的意思是:生成的图片的参考尺寸,如二维码是正方形,如果给一个矩形,则会留白;条形码为矩形,如果给一个正方形,则也会留白)。
2.2 EncodeHintType
由2.1中encode方法可以看出,编码包括参数Map<EncodeHintType, ?>,key为EncodeHintType枚举,此枚举是一组提示,您可以传递给编写者以指定其行为。枚举参数如下:
参数 | 说明 |
---|---|
ERROR_CORRECTION | 容错率,指定容错等级,例如二维码中使用的ErrorCorrectionLevel, Aztec使用Integer。分为四个等级:L/M/Q/H, 等级越高,容错率越高,识别速度降低。例如一个角被损坏,容错率高的也许能够识别出来。通常为H |
CHARACTER_SET | 编码集,通常有中文,设置为 utf-8 |
DATA_MATRIX_SHAPE | 指定生成的数据矩阵的形状,类型为SymbolShapeHint |
MIN_SIZE | 指定最小二维码/条形码大小 |
MAX_SIZE | 指定最大二维码/条形码大小 |
MARGIN | 生成条码的时候使用,指定边距,单位像素,受格式的影响。类型Integer, 或String代表的数字类型。默认为4 |
PDF417_COMPACT | 指定是否使用PDF417紧凑模式,类型Boolean |
PDF417_COMPACTION | 指定PDF417的紧凑类型 |
PDF417_DIMENSIONS | 指定PDF417的最大最小行列数 |
AZTEC_LAYERS | aztec编码相关 |
QR_VERSION | 指定二维码版本,版本越高越复杂,反而不容易解析 |
QR_MASK_PATTERN | 指定要使用的二维码掩码图案。默认情况下,代码将自动选择最佳遮罩图案。 |
GS1_FORMAT | 指定是否应将数据编码为GS1标准 |
FORCE_CODE_SET | 强制使用哪种编码。目前仅用于Code-128代码集 |
2.3 WriterException
除上述外,我们还可以看到,在类Writer的两个生成方法中,都有异常处理throws WriterException。WriterException是一个基类,涵盖使用Writer框架编码条形码时可能出现的异常范围。
public final class WriterException extends Exception {
public WriterException() {}
public WriterException(String message) {
super(message);
}
public WriterException(Throwable cause) {
super(cause);
}
}
2.4 MultiFormatWriter
MultiFormatWriter是一个工厂类,它为请求的编码格式查找适当的Writer子类,并使用提供的内容对二维码/条形码进行编码。由下面代码可以看出Zxing所支持的二维码、条形码的类型。这些类型在枚举类BarcodeFormat也可以看到。
public final class MultiFormatWriter implements Writer {
@Override
public BitMatrix encode(String contents,
BarcodeFormat format,
int width,
int height) throws WriterException {
return encode(contents, format, width, height, null);
}
@Override
public BitMatrix encode(String contents,
BarcodeFormat format,
int width, int height,
Map<EncodeHintType,?> hints) throws WriterException {
Writer writer;
switch (format) {
case EAN_8:
writer = new EAN8Writer();//EAN-8商品条码
break;
case UPC_E:
writer = new UPCEWriter();//UPC-E条码
break;
case EAN_13:
writer = new EAN13Writer();//EAN-13商品条码
break;
case UPC_A:
writer = new UPCAWriter();//UPC-A条码
break;
case QR_CODE:
writer = new QRCodeWriter();//QR 码
break;
case CODE_39:
writer = new Code39Writer();//Code 39 条码
break;
case CODE_93:
writer = new Code93Writer();//Code 93 条码
break;
case CODE_128:
writer = new Code128Writer();//Code 128 条码
break;
case ITF:
writer = new ITFWriter();//交插二五码
break;
case PDF_417:
writer = new PDF417Writer();//PDF417条码
break;
case CODABAR:
writer = new CodaBarWriter();//Codabar 条形码
break;
case DATA_MATRIX:
writer = new DataMatrixWriter();//Data Matrix二维码
break;
case AZTEC:
writer = new AztecWriter();//Aztec 码
break;
default:
throw new IllegalArgumentException("No encoder available for format " + format);
}
return writer.encode(contents, format, width, height, hints);
}
}
三、生成二维码图片
通过上述类及其方法,选择所需的编码方式进行编码后,得到的是一个BitMatrix, 如果需要显示出来则要根据不同平台来处理。
3.1 Java SE平台
1、将BitMatrix转换成BufferedImage。其方法为:
public static BufferedImage toBufferedImage(BitMatrix matrix, MatrixToImageConfig config)
其中,BitMatrix是二维码的描述对象;MatrixToImageConfig是二维码转换成BufferedImage的配置参数,其对象中只有两个域onColor和offColor, 这样的配置表示生成的BufferedImage用两种颜色来表示二维码上的开关。
2、将BitMatrix转换成图片文件。其方法为:
public static boolean write(RenderedImage im, String formatName, File output) throws IOException
其中,RenderedImage im实现了RenderedImage接口,String formatName是图片文件格式,通常使用 png,File output是图片文件。
上面两步结合起来就直接将BitMatrix转换成图片文件。即:
public static void writeToPath(BitMatrix matrix, String format, Path file, MatrixToImageConfig config) throws IOException
3.2 Android 平台
类似的,在Android中也是先将BitMatrix转换成Bitmap, 然后再写入到文件中。
1、将BitMatrix转换成Bitmap。分为三步:
- BitMatrix
BitMatrix表示位数组的二维矩阵。而它内部则是使用一维int数组来实现的,一个int数组有32位。不过比较特别的是,每一行都是由一个新的int值开始,如果列数不是32的倍数,一行最后一个int值中有没有用到的位。另外位是从int值的最小位开始排的,这是为了和common包中BitArray更好的转换。
BitMatrix中几个比较重要的方法如下:
方法 | 说明 |
---|---|
public boolean get(int x, int y) | 获取(x, y)的位值,true表示黑色 |
public void set(int x, int y) | 设置(x, y)的位值为true |
public void unset(int x, int y) | 设置(x, y)的位值为false |
public void flip(int x, int y) | 对(x, y)的位值做非运算 |
public BitMatrix(int width, int height) | 构造函数,指定宽高 |
除此之外,common包中的BitArray这个类的数据结构和BitMatrix的一行是一样的,使用int数组来表示一维位数组,同样的,最后一位int值可能有部分位没有用到。也同样的,位是从int值的最小位开始排列。
- Bitmap
其内部使用的是一维int数组来实现的,一个int值就表示一个点的颜色。
常用方法有:
方法 | 说明 |
---|---|
public static Bitmap createBitmap(int width, int height, Config config) | 构造方法,创建一个透明的Bitmap |
public void setPixels(@ColorInt int[] pixels, int offset, int stride, int x, int y, int width, int height) | 使用数组中的颜色替换Bitmap的像素点的颜色 |
public void setPixel(int x, int y, @ColorInt int color) | 设置Bitmap中指定像素点的颜色值 |
- BitMatrix转换成Bitmap
①创建一个一维int数组存放转换后的颜色值
②根据BitMatrix中的位值设置相应像素点的颜色值
③创建一个“相同”大小的Bitmap, 使用代表颜色的数组为其赋值(注意:颜色值中前两位默认为00, 表示透明)
代码示例:
private Bitmap bitMatrixToBitmap(BitMatrix bitMatrix) {
final int width = bitMatrix.getWidth();
final int height = bitMatrix.getHeight();
final int[] pixels = new int[width * height];
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
pixels[y * width + x] = bitMatrix.get(x, y) ? 0xFF000000 : 0xFFFFFFFF;
}
}
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
bitmap.setPixels(pixels, 0, width, 0, 0, width, height);
return bitmap;
}
其中,主要分析Bitmap.setPixels(@ColorInt int[] pixels, int offset, int stride, int x, int y, int width, int height)这个方法。
参数 | 说明 |
---|---|
int[] pixels | 像素点颜色数组 |
int offset | 从偏移颜色数组第一个像素多少开始读起 |
int stride | 每隔多少个点跳行,通常和宽度相同,不过也可以更大,设置为负值 |
int x | Bitmap接收值的x轴起点 |
int y | Bitmap接收值的y轴起点 |
int width | 每一行复制多少颜色点 |
int height | 一个复制多少行 |
因为考虑到像素点颜色数组和Bitmap大小本身存在不同所以才有这些参数,实际上,像素点颜色数组的大小和Bitmap的大小是相同的。那么其中的参数分别是:像素点颜色数组、0表示不偏移,直接从第一位复制、Bitmap宽度,复制完刚好一行则开始从下一个点开始进行复制下一行、0表示从左上角开始复制、0表示从左上角开始复制、Bitmap的宽度表示刚好复制到整个Bitmap, Bitmap的宽度表示刚好复制到整个Bitmap
2、Bitmap写入到文件中:
Bitmap.compress(CompressFormat format, int quality, OutputStream stream)
具体细节不再介绍,在下面参考资料链接中都有。这并不是我分析代码的重点,因此不再赘述。
小结
本文分析了Zxing支持的各种二维码、条形码编码的公用方法,是接下来具体分析各种编码算法的前提,也是理解具体编码算法的基础。
参考资料
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)