Android 二維碼生成,掃描,近距離識別優化,從相簿選擇二維碼識別
做了一個二維碼掃描圖片,主要是掃描不出來,看到一篇部落格,其中的第二種方法可以掃描到,在此做筆記,以備後用,前面的進入相簿,返回,到獲取圖片路徑方法都一樣;
(1):二維碼生成的方法順便貼上:
private Bitmap createQRImage(String url, final int width, final int height) { try { // 判斷URL合法性 if (url == null || "".equals(url) || url.length() < 1) { return null; } Hashtable<EncodeHintType, String> hints = new Hashtable<EncodeHintType, String>(); hints.put(EncodeHintType.CHARACTER_SET, "utf-8"); // 影象資料轉換,使用了矩陣轉換 BitMatrix bitMatrix = new QRCodeWriter().encode(url, BarcodeFormat.QR_CODE, width, height, hints); int[] pixels = new int[width * height]; // 下面這裡按照二維碼的演算法,逐個生成二維碼的圖片, // 兩個for迴圈是圖片橫列掃描的結果 for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { if (bitMatrix.get(x, y)) { pixels[y * width + x] = 0xff000000; } else { pixels[y * width + x] = 0xffffffff; } } } // 生成二維碼圖片的格式,使用ARGB_8888 Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); bitmap.setPixels(pixels, 0, width, 0, 0, width, height); return bitmap; } catch (WriterException e) { e.printStackTrace(); } return null; }
(2)二維碼掃描的方法:https://blog.csdn.net/qq_25815655/article/details/79927786
(3)下面從相簿選擇二維碼
1.專案需求:知道專案需求,才知道先從哪裡入手,見圖一。(點選相簿,開啟相簿)
2.程式碼:
1.開啟相簿程式碼:
mBtnOpenPicture.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //開啟相簿 openGallery(); } }); /**開啟相簿*/ private void openGallery() { Intent picture = new Intent(Intent.ACTION_PICK, android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI); startActivityForResult(picture, PICTURE); }
2.獲取圖片路徑和解析。
(1). 獲取圖片路徑:getPath(uri);由於Android版本不同,返回的uri會有所不同,需要做特殊處理。
@SuppressLint("NewApi") private String getPath(Uri uri) { int sdkVersion = Build.VERSION.SDK_INT; if (sdkVersion >= 19) { //Log.e("hxy", "uri auth: " + uri.getAuthority()); if (isExternalStorageDocument(uri)) { String docId = DocumentsContract.getDocumentId(uri); String[] split = docId.split(":"); String type = split[0]; if ("primary".equalsIgnoreCase(type)) { return Environment.getExternalStorageDirectory() + "/" + split[1]; } } else if (isDownloadsDocument(uri)) { final String id = DocumentsContract.getDocumentId(uri); final Uri contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), Long.valueOf(id)); return getDataColumn(this, contentUri, null, null); } else if (isMediaDocument(uri)) { final String docId = DocumentsContract.getDocumentId(uri); final String[] split = docId.split(":"); final String type = split[0]; Uri contentUri = null; if ("image".equals(type)) { contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; } else if ("video".equals(type)) { contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI; } else if ("audio".equals(type)) { contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; } final String selection = "_id=?"; final String[] selectionArgs = new String[]{split[1]}; return getDataColumn(this, contentUri, selection, selectionArgs); } else if (isMedia(uri)) { String[] proj = {MediaStore.Images.Media.DATA}; Cursor actualimagecursor = this.managedQuery(uri, proj, null, null, null); int actual_image_column_index = actualimagecursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA); actualimagecursor.moveToFirst(); return actualimagecursor.getString(actual_image_column_index); } } else if ("content".equalsIgnoreCase(uri.getScheme())) { if (isGooglePhotosUri(uri)) return uri.getLastPathSegment(); return getDataColumn(this, uri, null, null); } // File else if ("file".equalsIgnoreCase(uri.getScheme())) { return uri.getPath(); } return null; } public static String getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs) { Cursor cursor = null; final String column = "_data"; final String[] projection = { column }; try { cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null); if (cursor != null && cursor.moveToFirst()) { final int column_index = cursor.getColumnIndexOrThrow(column); return cursor.getString(column_index); } } finally { if (cursor != null) cursor.close(); } return null; } private static boolean isExternalStorageDocument(Uri uri) { return "com.android.externalstorage.documents".equals(uri.getAuthority()); } public static boolean isDownloadsDocument(Uri uri) { return "com.android.providers.downloads.documents".equals(uri.getAuthority()); } public static boolean isMediaDocument(Uri uri) { return "com.android.providers.media.documents".equals(uri.getAuthority()); } public static boolean isMedia(Uri uri) { return "media".equals(uri.getAuthority()); } public static boolean isGooglePhotosUri(Uri uri) { return "com.google.android.apps.photos.content".equals(uri.getAuthority()); }
(2).開闢執行緒,解析圖片,封裝到Result中:我看了網上的demo,大致上有2種方法。如下是第一種,稍後會把第二種方法也貼出來。
/**
* 解析二維碼圖片
* @param path
* @return
*/
protected Result scanningImage(String path) {
if (TextUtils.isEmpty(path)) {
return null;
}
Hashtable<DecodeHintType, String> hints = new Hashtable();
hints.put(DecodeHintType.CHARACTER_SET, "UTF-8"); // 設定二維碼內容的編碼
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true; // 先獲取原大小
scanBitmap = BitmapFactory.decodeFile(path,options);
options.inJustDecodeBounds = false;
int sampleSize = (int) (options.outHeight / (float) 200);
if (sampleSize <= 0)
sampleSize = 1;
options.inSampleSize = sampleSize;
scanBitmap = BitmapFactory.decodeFile(path, options);
int[] data = new int[scanBitmap.getWidth() * scanBitmap.getHeight()];
scanBitmap.getPixels(data, 0, scanBitmap.getWidth(), 0, 0, scanBitmap.getWidth(), scanBitmap.getHeight());
RGBLuminanceSource rgbLuminanceSource = new RGBLuminanceSource(scanBitmap.getWidth(),scanBitmap.getHeight(),data);
BinaryBitmap binaryBitmap = new BinaryBitmap(new HybridBinarizer(rgbLuminanceSource));
QRCodeReader reader = new QRCodeReader();
Result result = null;
try {
result = reader.decode(binaryBitmap, hints);
} catch (NotFoundException e) {
Log.e("hxy","NotFoundException");
}catch (ChecksumException e){
Log.e("hxy","ChecksumException");
}catch(FormatException e){
Log.e("hxy","FormatException");
}
return result;
}
注意:上面方法中有個關鍵的類,RGBLuminanceSource。 我把這個類的構造方法貼出來,因為我做的時候,發現網上demo 這個類中構造傳遞的都是Bitmap, 而我這個類卻不是。分析傳遞的引數之後,我做了個轉化:見如下,然後會發現報:NotFoundException. 這個異常是在QRCodeReader類:private static BitMatrix extractPureBits(BitMatrix image) throws NotFoundException 。(到這裡我已經不是很懂了,然後又去網上搜索了下,最後自己探索出加scanBitmap.getPixels(data, 0, scanBitmap.getWidth(), 0, 0, scanBitmap.getWidth(), scanBitmap.getHeight());),執行正常,能解析出來。
int[] data = new int[scanBitmap.getWidth() * scanBitmap.getHeight()];
//一定要加以下這個程式碼:
//scanBitmap.getPixels(data, 0, scanBitmap.getWidth(), 0, 0, scanBitmap.getWidth(), //scanBitmap.getHeight());
RGBLuminanceSource rgbLuminanceSource = new RGBLuminanceSource(scanBitmap.getWidth(),scanBitmap.getHeight(),data);
public RGBLuminanceSource(int width, int height, int[] pixels) {
super(width, height);
this.dataWidth = width;
this.dataHeight = height;
this.left = 0;
this.top = 0;
this.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 & 255;
int g = pixel >> 8 & 255;
int b = pixel & 255;
if(r == g && g == b) {
this.luminances[offset + x] = (byte)r;
} else {
this.luminances[offset + x] = (byte)((r + 2 * g + b) / 4);
}
}
}
}
現在來看第二種解析方法:
protected Result scanningImage(String path) {
if (TextUtils.isEmpty(path)) {
return null;
}
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true; // 先獲取原大小
scanBitmap = BitmapFactory.decodeFile(path,options);
options.inJustDecodeBounds = false;
int sampleSize = (int) (options.outHeight / (float) 200);
if (sampleSize <= 0)
sampleSize = 1;
options.inSampleSize = sampleSize;
scanBitmap = BitmapFactory.decodeFile(path, options);
byte[] data = getYUV420sp(scanBitmap.getWidth(), scanBitmap.getHeight(), scanBitmap);
Hashtable<DecodeHintType, Object> hints = new Hashtable();
hints.put(DecodeHintType.CHARACTER_SET, "UTF-8"); // 設定二維碼內容的編碼
hints.put(DecodeHintType.TRY_HARDER,Boolean.TRUE);
hints.put(DecodeHintType.POSSIBLE_FORMATS, BarcodeFormat.QR_CODE);
PlanarYUVLuminanceSource source = new PlanarYUVLuminanceSource(data,
scanBitmap.getWidth(),
scanBitmap.getHeight(),
0, 0,
scanBitmap.getWidth(),
scanBitmap.getHeight(),
false);
BinaryBitmap bitmap1 = new BinaryBitmap(new HybridBinarizer(source));
QRCodeReader reader2= new QRCodeReader();
Result result = null;
try {
result = reader2.decode(bitmap1, hints);
Log.e("hxy",result.getText());
} catch (NotFoundException e) {
Log.e("hxy","NotFoundException");
}catch (ChecksumException e){
Log.e("hxy","ChecksumException");
}catch(FormatException e){
Log.e("hxy","FormatException");
}
return result;
}
public byte[] getYUV420sp(int inputWidth, int inputHeight,
Bitmap scaled) {
int[] argb = new int[inputWidth * inputHeight];
scaled.getPixels(argb, 0, inputWidth, 0, 0, inputWidth, inputHeight);
byte[] yuv = new byte[inputWidth * inputHeight * 3 / 2];
encodeYUV420SP(yuv, argb, inputWidth, inputHeight);
scaled.recycle();
return yuv;
}
private void encodeYUV420SP(byte[] yuv420sp, int[] argb, int width,
int height) {
// 幀圖片的畫素大小
final int frameSize = width * height;
// ---YUV資料---
int Y, U, V;
// Y的index從0開始
int yIndex = 0;
// UV的index從frameSize開始
int uvIndex = frameSize;
// ---顏色資料---
// int a, R, G, B;
int R, G, B;
//
int argbIndex = 0;
//
// ---迴圈所有畫素點,RGB轉YUV---
for (int j = 0; j < height; j++) {
for (int i = 0; i < width; i++) {
// a is not used obviously
// a = (argb[argbIndex] & 0xff000000) >> 24;
R = (argb[argbIndex] & 0xff0000) >> 16;
G = (argb[argbIndex] & 0xff00) >> 8;
B = (argb[argbIndex] & 0xff);
//
argbIndex++;
// well known RGB to YUV algorithm
Y = ((66 * R + 129 * G + 25 * B + 128) >> 8) + 16;
U = ((-38 * R - 74 * G + 112 * B + 128) >> 8) + 128;
V = ((112 * R - 94 * G - 18 * B + 128) >> 8) + 128;
//
Y = Math.max(0, Math.min(Y, 255));
U = Math.max(0, Math.min(U, 255));
V = Math.max(0, Math.min(V, 255));
// NV21 has a plane of Y and interleaved planes of VU each
// sampled by a factor of 2
// meaning for every 4 Y pixels there are 1 V and 1 U. Note the
// sampling is every other
// pixel AND every other scanline.
// ---Y---
yuv420sp[yIndex++] = (byte) Y;
// ---UV---
// if ((j % 2 == 0) && (i % 2 == 0)) {
//
//
//
// yuv420sp[uvIndex++] = (byte) V;
//
// yuv420sp[uvIndex++] = (byte) U;
// }
}
}
}
最後2行程式碼在實際執行的時候,如果是拿一個二維碼圖片,能正常解析,但是如果不是二維碼圖片,陣列越界。然後我將其註釋掉之後,一切正常了。在這裡的轉化,我沒有看懂,只是提供一種解析方案,期待對這方面瞭解之人能我和探討。
3.將解析結果回撥給呼叫Activity.:
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if(resultCode==RESULT_OK&&data != null&&requestCode == PICTURE){
Uri selectedImage = data.getData();
final String pathResult = getPath(selectedImage);
Log.e("hxy","pathResult:"+pathResult);
new Thread(new Runnable() {
@Override
public void run() {
Result result = scanningImage(pathResult);
if(result==null){
Looper.prepare();
Toast.makeText(CaptureActivity.this, "未識別到二維碼",Toast.LENGTH_LONG)
.show();
Looper.loop();
}else{
handleDecode(result,new Bundle());
// String recode = recode(result.toString());
// Log.e("hxy","recode:"+recode);
// Intent data = new Intent();
// data.putExtra("result", recode);
// setResult(300, data);
//finish();
}
}
}).start();
}
}
在這裡呼叫handleDecode。是由於CaptureActivity中,將結果就是通過handleDecode回撥的
/**
* A valid barcode has been found, so give an indication of success and show
* the results.
*
* @param rawResult
* The contents of the barcode.
*
* @param bundle
* The extras
*/
public void handleDecode(Result rawResult, Bundle bundle) {
inactivityTimer.onActivity();
beepManager.playBeepSoundAndVibrate();
bundle.putInt("width", mCropRect.width());
bundle.putInt("height", mCropRect.height());
bundle.putString("result", rawResult.getText());
// startActivity(new Intent(CaptureActivity.this, ResultActivity.class).putExtras(bundle));
setResult(RESULT_OK, new Intent().putExtras(bundle));
// Toast.makeText(this, rawResult.getText(), Toast.LENGTH_LONG);
finish();
}
以上就是解析二維碼圖片的兩種方法,借鑑的部落格如下:
http://blog.csdn.net/a102111/article/details/48377537
http://blog.csdn.net/aaawqqq/article/details/24880209