Android平台生成二维码并实现扫描 & 识别功能
1.二维码的前世今生
“二维条码/二维码(2-dimensional bar code)是用某种特定的几何图形按一定规律在平面(二维方向上)分布的黑白相间的图形记录数据符号信息的;在代码编制上巧妙地利用构成计算机内部逻辑基础的“0”、“1”比特流的概念,使用若干个与二进制相对应的几何形体来表示文字数值信息,通过图象输入设备或光电扫描设备自动识读以实现信息自动处理:它具有条码技术的一些共性:每种码制有其特定的字符集;每个字符占有一定的宽度;具有一定的校验功能等。同时还具有对不同行的信息自动识别功能、及处理图形旋转变化点。 [1] ”
上面是百度百科的解释。既然有二维码,那么肯定有一维码。
一维码。最为常见的就是食品 & 书本后面的条码。
条码起源与20世纪40年代,后来在1970年 upc码发明,并开始广泛应用与食品包装。
具体的介绍可以看百度百科 一维码。
其实二维码与一维码本质上是类似的,就跟一维数组和二维数组一样。
2.二维码的java支持库
为了让java或者说android方便继承条码的功能,google就开发了一个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); } }
过程也是很简单,使用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; } }
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));