你的位置:首页 > 操作系统

[操作系统]Android平台二维码之生成,扫描 识别

1.二维码的前世今生

“二维条码/二维码(2-dimensional bar code)是用某种特定的几何图形按一定规律在平面(二维方向上)分布的黑白相间的图形记录数据符号信息的;在代码编制上巧妙地利用构成计算机内部逻辑基础的“0”、“1”比特流的概念,使用若干个与二进制相对应的几何形体来表示文字数值信息,通过图象输入设备或光电扫描设备自动识读以实现信息自动处理:它具有条码技术的一些共性:每种码制有其特定的字符集;每个字符占有一定的宽度;具有一定的校验功能等。同时还具有对不同行的信息自动识别功能、及处理图形旋转变化点。 [1] ”

上面是百度百科的解释。既然有二维码,那么肯定有一维码。

一维码。最为常见的就是食品 & 书本后面的条码。

条码起源与20世纪40年代,后来在1970年 UPC码发明,并开始广泛应用与食品包装。

具体的介绍可以看百度百科 一维码。

其实二维码与一维码本质上是类似的,就跟一维数组和二维数组一样。

 

2.二维码的java支持库

为了让java或者说android方便继承条码的功能,google就开发了一个zxing的库:

https://github.com/zxing/zxing

 

3.生成二维码

public class EncodeThread {  public static void encode(final String url, final int width, final int height, final EncodeResult result) {    if (result == null) {      return;    }    if (TextUtils.isEmpty(url)) {      result.onEncodeResult(null);      return;    }    new Thread() {      @Override      public void run() {        try {          MultiFormatWriter writer = new MultiFormatWriter();          Hashtable<EncodeHintType, String> hints = new Hashtable<>();          hints.put(EncodeHintType.CHARACTER_SET, "utf-8");          BitMatrix bitMatrix = writer.encode(url, BarcodeFormat.QR_CODE, width, height, hints);          Bitmap bitmap = parseBitMatrix(bitMatrix);          result.onEncodeResult(bitmap);          return;        } catch (WriterException e) {          e.printStackTrace();        }        result.onEncodeResult(null);      }    }.start();  }  /**   * 生成二维码内容<br>   *   * @param matrix   * @return   */  public static Bitmap parseBitMatrix(BitMatrix matrix) {    final int QR_WIDTH = matrix.getWidth();    final int QR_HEIGHT = matrix.getHeight();    int[] pixels = new int[QR_WIDTH * QR_HEIGHT];    //this we using qrcode algorithm    for (int y = 0; y < QR_HEIGHT; y++) {      for (int x = 0; x < QR_WIDTH; x++) {        if (matrix.get(x, y)) {          pixels[y * QR_WIDTH + x] = 0xff000000;        } else {          pixels[y * QR_WIDTH + x] = 0xffffffff;        }      }    }    Bitmap bitmap = Bitmap.createBitmap(QR_WIDTH, QR_HEIGHT, Bitmap.Config.ARGB_8888);    bitmap.setPixels(pixels, 0, QR_WIDTH, 0, 0, QR_WIDTH, QR_HEIGHT);    return bitmap;  }  public interface EncodeResult {    void onEncodeResult(Bitmap bitmap);  }}

zxing 支持很多条码格式:我们这里使用QR_CODE码。也就是我们常见的微信里面的二维码。

我们先来分析下这段代码:

 MultiFormatWriter writer = new MultiFormatWriter();

这个是一个工具类,把所有支持的几个write写在里面了。

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();    break;   case UPC_E:    writer = new UPCEWriter();    break;   case EAN_13:    writer = new EAN13Writer();    break;   case UPC_A:    writer = new UPCAWriter();    break;   case QR_CODE:    writer = new QRCodeWriter();    break;   case CODE_39:    writer = new Code39Writer();    break;   case CODE_93:    writer = new Code93Writer();    break;   case CODE_128:    writer = new Code128Writer();    break;   case ITF:    writer = new ITFWriter();    break;   case PDF_417:    writer = new PDF417Writer();    break;   case CODABAR:    writer = new CodaBarWriter();    break;   case DATA_MATRIX:    writer = new DataMatrixWriter();    break;   case AZTEC:    writer = new AztecWriter();    break;   default:    throw new IllegalArgumentException("No encoder available for format " + format);  }  return writer.encode(contents, format, width, height, hints); }

这是官方最新支持的格式,具体看引入的jar里面支持的格式。

对与bitmatrix的结果,通过摸个算法,设置每个点白色,或者黑色。

最后创建一张二维码的图片。

4.识别二维码

如何从一张图片上面,识别二维码呢:

public class ReDecodeThread {  public static void encode(final Bitmap bitmap, final ReDecodeThreadResult listener) {    if (listener == null) {      return;    }    if (bitmap == null) {      listener.onReDecodeResult(null);      return;    }    new Thread() {      @Override      public void run() {        try {          MultiFormatReader multiFormatReader = new MultiFormatReader();          BitmapLuminanceSource source = new BitmapLuminanceSource(bitmap);          BinaryBitmap bitmap1 = new BinaryBitmap(new HybridBinarizer(source));          Result result1 = multiFormatReader.decode(bitmap1);          listener.onReDecodeResult(result1.getText());          return;        } catch (NotFoundException e) {          e.printStackTrace();        }        listener.onReDecodeResult(null);      }    }.start();  }  public interface ReDecodeThreadResult {    void onReDecodeResult(String url);  }}

View Code

过程也是很简单,使用MultiFormatReader来分析图片,这里不需要缺人图片的条码格式。

如果分析下源码,就是依次使用每种格式的reader来分析,直到找到合适的为止。

当然回了能够把Bitmap转化成Bitmatrix,然后在分析。

public final class BitmapLuminanceSource extends LuminanceSource{  private final byte[] luminances;  public BitmapLuminanceSource(String path) throws FileNotFoundException {    this(loadBitmap(path));  }  public BitmapLuminanceSource(Bitmap bitmap) {    super(bitmap.getWidth(), bitmap.getHeight());    int width = bitmap.getWidth();    int height = bitmap.getHeight();    int[] pixels = new int[width * height];    bitmap.getPixels(pixels, 0, width, 0, 0, width, height);    // In order to measure pure decoding speed, we convert the entire image    // to a greyscale array    // up front, which is the same as the Y channel of the    // YUVLuminanceSource in the real app.    luminances = new byte[width * height];    for (int y = 0; y < height; y++) {      int offset = y * width;      for (int x = 0; x < width; x++) {        int pixel = pixels[offset + x];        int r = (pixel >> 16) & 0xff;        int g = (pixel >> 8) & 0xff;        int b = pixel & 0xff;        if (r == g && g == b) {          // Image is already greyscale, so pick any channel.          luminances[offset + x] = (byte) r;        } else {          // Calculate luminance cheaply, favoring green.          luminances[offset + x] = (byte) ((r + g + g + b) >> 2);        }      }    }  }  @Override  public byte[] getRow(int y, byte[] row) {    if (y < 0 || y >= getHeight()) {      throw new IllegalArgumentException("Requested row is outside the image: " + y);    }    int width = getWidth();    if (row == null || row.length < width) {      row = new byte[width];    }    System.arraycopy(luminances, y * width, row, 0, width);    return row;  }  // Since this class does not support cropping, the underlying byte array  // already contains  // exactly what the caller is asking for, so give it to them without a copy.  @Override  public byte[] getMatrix() {    return luminances;  }  private static Bitmap loadBitmap(String path) throws FileNotFoundException {    Bitmap bitmap = BitmapFactory.decodeFile(path);    if (bitmap == null) {      throw new FileNotFoundException("Couldn't open " + path);    }    return bitmap;  }}

BitmapLuminanceSource

5.扫描二维码

扫描二维码,其实比上面只多了一步,就是把camera获取的东西直接转换,然后进行识别。

 public void requestPreviewFrame(Handler handler, int message) {  if (camera != null && previewing) {   previewCallback.setHandler(handler, message);   if (useOneShotPreviewCallback) {     camera.setOneShotPreviewCallback(previewCallback);   } else {    camera.setPreviewCallback(previewCallback);   }  } }

首先把camera预览的数据放入previewCallback中。

final class PreviewCallback implements Camera.PreviewCallback

public void onPreviewFrame(byte[] data, Camera camera) {  Point cameraResolution = configManager.getCameraResolution();  if (!useOneShotPreviewCallback) {   camera.setPreviewCallback(null);  }  if (previewHandler != null) {   Message message = previewHandler.obtainMessage(previewMessage, cameraResolution.x,     cameraResolution.y, data);   message.sendToTarget();   previewHandler = null;  } else {   Log.d(TAG, "Got preview callback, but no handler for it");  } }

可以看到,预览的数据data,回传递过来,然后handler的方式传递出去。

接收data的地方:

 @Override public void handleMessage(Message message) {  switch (message.what) {   case R.id.decode:    //Log.d(TAG, "Got decode message");    decode((byte[]) message.obj, message.arg1, message.arg2);    break;   case R.id.quit:    Looper.myLooper().quit();    break;  } }

然后是decode data

private void decode(byte[] data, int width, int height) {  long start = System.currentTimeMillis();  Result rawResult = null;    //modify here  byte[] rotatedData = new byte[data.length];  for (int y = 0; y < height; y++) {    for (int x = 0; x < width; x++)      rotatedData[x * height + height - y - 1] = data[x + y * width];  }  int tmp = width; // Here we are swapping, that's the difference to #11  width = height;  height = tmp;    PlanarYUVLuminanceSource source = CameraManager.get().buildLuminanceSource(rotatedData, width, height);  BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));  try {   rawResult = multiFormatReader.decodeWithState(bitmap);  } catch (ReaderException re) {   // continue  } finally {   multiFormatReader.reset();  }  if (rawResult != null) {   long end = System.currentTimeMillis();   Log.d(TAG, "Found barcode (" + (end - start) + " ms):\n" + rawResult.toString());   Message message = Message.obtain(activity.getHandler(), R.id.decode_succeeded, rawResult);   Bundle bundle = new Bundle();   bundle.putParcelable(DecodeThread.BARCODE_BITMAP, source.renderCroppedGreyscaleBitmap());   message.setData(bundle);   //Log.d(TAG, "Sending decode succeeded message...");   message.sendToTarget();  } else {   Message message = Message.obtain(activity.getHandler(), R.id.decode_failed);   message.sendToTarget();  } }

当把camera上的图片转换成BinaryBitmap以后,剩下的事情,就更直接从图片识别是一样的。

PlanarYUVLuminanceSource source = CameraManager.get().buildLuminanceSource(rotatedData, width, height);  BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));

参考:

http://www.cnblogs.com/weixing/archive/2013/08/28/3287120.html