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_LAYERSaztec编码相关
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 xBitmap接收值的x轴起点
int yBitmap接收值的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支持的各种二维码、条形码编码的公用方法,是接下来具体分析各种编码算法的前提,也是理解具体编码算法的基础。

参考资料

ZXing的应用
Zxing生成二维码思路和源码

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐