Android平臺生成二維碼并實現(xiàn)掃描 & 識別功能
1.二維碼的前世今生
“二維條碼/二維碼(2-dimensional bar code)是用某種特定的幾何圖形按一定規(guī)律在平面(二維方向上)分布的黑白相間的圖形記錄數(shù)據(jù)符號信息的;在代碼編制上巧妙地利用構(gòu)成計算機(jī)內(nèi)部邏輯基礎(chǔ)的“0”、“1”比特流的概念,使用若干個與二進(jìn)制相對應(yīng)的幾何形體來表示文字?jǐn)?shù)值信息,通過圖象輸入設(shè)備或光電掃描設(shè)備自動識讀以實現(xiàn)信息自動處理:它具有條碼技術(shù)的一些共性:每種碼制有其特定的字符集;每個字符占有一定的寬度;具有一定的校驗功能等。同時還具有對不同行的信息自動識別功能、及處理圖形旋轉(zhuǎn)變化點。 [1] ”
上面是百度百科的解釋。既然有二維碼,那么肯定有一維碼。
一維碼。最為常見的就是食品 & 書本后面的條碼。
條碼起源與20世紀(jì)40年代,后來在1970年 UPC碼發(fā)明,并開始廣泛應(yīng)用與食品包裝。
具體的介紹可以看百度百科 一維碼。
其實二維碼與一維碼本質(zhì)上是類似的,就跟一維數(shù)組和二維數(shù)組一樣。
2.二維碼的java支持庫
為了讓java或者說android方便繼承條碼的功能,google就開發(fā)了一個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(); } /** * 生成二維碼內(nèi)容<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的結(jié)果,通過摸個算法,設(shè)置每個點白色,或者黑色。
最后創(chuàng)建一張二維碼的圖片。
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來分析,直到找到合適的為止。
當(dāng)然回了能夠把Bitmap轉(zhuǎn)化成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獲取的東西直接轉(zhuǎn)換,然后進(jìn)行識別。
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預(yù)覽的數(shù)據(jù)放入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"); } }
可以看到,預(yù)覽的數(shù)據(jù)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(); } }
當(dāng)把camera上的圖片轉(zhuǎn)換成BinaryBitmap以后,剩下的事情,就更直接從圖片識別是一樣的。
PlanarYUVLuminanceSource source = CameraManager.get().buildLuminanceSource(rotatedData, width, height);
BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
相關(guān)文章
詳解LeakCanary分析內(nèi)存泄露如何實現(xiàn)
這篇文章主要為大家介紹了詳解LeakCanary分析內(nèi)存泄露如何實現(xiàn),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-12-12Jetpack Compose圖片組件使用實例詳細(xì)講解
在Compose中,圖片組件主要有兩種,分別是顯示圖標(biāo)的Icon組件和顯示圖片的Image組件,當(dāng)我們顯示一系列的小圖標(biāo)的時候,我們可以使用Icon組件,當(dāng)顯示圖片時,我們就用專用的Image組件2023-04-04Android學(xué)習(xí)筆記——Menu介紹(一)
Android3.0(API level 11)開始,Android設(shè)備不再需要專門的菜單鍵。隨著這種變化,Android app應(yīng)該取消對傳統(tǒng)6項菜單的依賴。取而代之的是提供anction bar來提供基本的用戶功能2014-10-10android的activity跳轉(zhuǎn)到另一個activity
這篇文章主要介紹了android實現(xiàn)從一個activity跳轉(zhuǎn)到另一個activity中去2013-11-11Android多線程處理機(jī)制中的Handler使用介紹
本文將為大家介紹下Android的Handler的使用方法,Handler可以發(fā)送Messsage和Runnable對象到與其相關(guān)聯(lián)的線程的消息隊列,感興趣的朋友可以了解下哈2013-06-06Android仿美團(tuán)下拉菜單(商品選購)實例代碼
這篇文章主要介紹了Android仿美團(tuán)下拉菜單(商品選購)實例代碼的相關(guān)資料,需要的朋友可以參考下2016-03-03