ZXing 相簿中識別二維碼和條形碼(直接引用就可以了)
*百度了很久一直沒有找到關於相簿獲取條形碼的Demo,真心是醉了,只能苦逼的去自己看
閒話不說直接搞起
分析:
*核心
1,通過路徑轉換成bitmp物件
2,再bitmap物件轉換成二進位制圖片(二值化) == 將影象進行二值化處理,1 , 0 代表黑和白
3,最後解析二進位制圖片中的code(獲取到有用的資訊) ==對符號碼矩陣按照編碼規範進行解碼,得到需要的資訊
ZXing原始碼:
*ZXiong程式碼中是使用HybridBinarizer進行二值化計算的的,其實本質都是是使用Binarizer實現了
*看程式碼我們發現關係如下
*HybridBinarizer extends GlobalHistogramBinarizer
*GlobalHistogramBinarizer extends Binarizer
*在GlobalHistogramBinarizer中,有解析一二維碼的方法
解析一維的方法如下圖
解析二維的方法如下圖
*好了我們看看如何實現
==============================華麗的分割線================================
第一種:
第一步:在ZXing的CaptureActivity中,點選跳轉到系統的相簿選擇圖片,其實這裡因為android版本的不同返回的uri也會有三種情況,一共有兩種方式(相簿跳轉和路徑判斷)解決,在剛剛開始的部落格中,我兩種都寫了,看著看著就亂了,這裡我決定優化下我的部落格,就寫一種解決方式==路徑判斷
按照我的步驟寫,可以解決兩個問題,問題一:android系統不同返回照片路徑不同問題,問題二:解析圖片圖片OOM問題(進行了bitmap大小判斷是否壓縮),
/**
* 跳轉到系統相簿選圖片
* android版本不同,返回路徑不同的情況(一共三種情況,無法解析圖片直接奔潰bug),
*content://com.android.providers.media.documents/document/image%3A137424 sony
*file:///storage/emulated/0/Tencent/QQ_Images/3afe8750f0b4b8ce.jpg xiaomi
*content://media/external/images/media/13323 smartOS
*
*/
private void switchSelectedImage() {
Intent intent = new Intent();
intent.setType("image/*");
intent.setAction(Intent.ACTION_GET_CONTENT);
startActivityForResult(intent, REQUESTCODE_IMAGE);
}
第二步:根據請求碼REQUESTCODE_IMAGE,在onActivityResult中進行結果的處理
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == RESULT_OK) {
switch (requestCode) {
case REQUESTCODE_IMAGE:
/**
*解決問題1,路徑的判斷,拿到真確的路徑
*/
String filePath = "$$";
int sdkInt = Build.VERSION.SDK_INT; //相容4.4
Uri contentUri = data.getData();
if (sdkInt == 19 && DocumentsContract.isDocumentUri(CaptureActivity.this, contentUri)) {
String wholeID = DocumentsContract
.getDocumentId(contentUri);
String id = wholeID.split(":")[1];
String[] column = {MediaStore.Images.Media.DATA};
String sel = MediaStore.Images.Media._ID + "=?";
Cursor cursor = CaptureActivity.this.getContentResolver().query(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
column, sel, new String[]{id}, null);
int columnIndex = cursor.getColumnIndex(column[0]);
if (cursor.moveToFirst()) {
filePath = cursor.getString(columnIndex);
}
cursor.close();
} else if (!TextUtils.isEmpty(contentUri.getAuthority())) {
Cursor cursor = getContentResolver().query(contentUri,
new String[]{MediaStore.Images.Media.DATA},
null, null, null);
if (null == cursor) {
showToast("圖片沒找到");
}
cursor.moveToFirst();
filePath = cursor.getString(cursor
.getColumnIndex(MediaStore.Images.Media.DATA));
cursor.close();
} else {
filePath = data.getData().getPath();
}
/**到此為止路徑判斷完成,問題一解決*/
try {
Bitmap bitmap = BitmapFactory.decodeFile(filePath);
long bitmapsize = getBitmapsize(bitmap);
if (bitmapsize > Contants.SCAN_DEFAULT_SIZE) { //大圖
//解析並且請求資料
/**這裡就解決了問題二*/
analysisAndRequestResultCode(filePath,false);
} else { //小圖 true表示要不用壓縮
analysisAndRequestResultCode(filePath,true);
}
} catch (NotFoundException e) {
showToast("圖片解析失敗");
e.printStackTrace();
} catch (FileNotFoundException e) {
showToast("圖片解析失敗");
e.printStackTrace();
}
break;
default:
LogUtil.d(TAG, "onActivityResult,error,default.");
break;
}
}
}
/**
* 解析並且請求藥品(這裡請求藥品是我的業務邏輯 可以忽略)
*RGBLuminanceSourcee(filePath, isAvailableSize); 這個方法是我在ZXing裡面寫的過載的方法
* @param filePath
* @throws FileNotFoundException
* @throws NotFoundException
*/
private void analysisAndRequestResultCode(String filePath,boolean isAvailableSize ) throws FileNotFoundException, NotFoundException {
RGBLuminanceSourcee rgbLuminanceSource = new RGBLuminanceSourcee(filePath, isAvailableSize);
BinaryBitmap binaryBitmap = new BinaryBitmap(new HybridBinarizer(rgbLuminanceSource));//把可檢視片轉為二進位制圖片
Result result = new MultiFormatReader().decode(binaryBitmap);//解析圖片中的code
handleDecode(result);//根據code,執行網路請求
}
/**
* 獲取bitmap的size
*
* @param bitmap
* @return
*/
public long getBitmapsize(Bitmap bitmap) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1) {
return bitmap.getByteCount();
}
// Pre HC-MR1
return bitmap.getRowBytes() * bitmap.getHeight();
}
相簿中選擇圖片二維碼和條形碼解析的核心程式碼(理解看看就可以了,可以忽略)
//根據相簿的path,返回一個bitmap物件
scanBitmap = BitmapFactory.decodeFile(path, options);
//bitmap物件轉換成二進位制圖片 (說明:輸入bitmap獲得解析結果source二進位制的byte圖片RGBLuminanceSource這個類繼承了LuminanceSource,)
RGBLuminanceSource source = new RGBLuminanceSource(scanBitmap);
//二進位制圖片轉換成bitmap物件(說明:建立HybridBinarizer物件,需要傳入LuminanceSource,所以傳入source(二進位制的圖片),並且通過BinaryBitmap轉換成bitmap物件)
BinaryBitmap bitmap1 = new BinaryBitmap(new HybridBinarizer(source));
//CodaBarReader codaBarReader= new CodaBarReader(); //codaBarReader 二維碼
try {
//MultiFormatReader是讀取影象的類(在core包)
return new MultiFormatReader().decode(bitmap1,hints); //識別條形碼,和二維碼(說明:獲取到我們需要的資訊)
} catch (NotFoundException e) {
e.printStackTrace();
}
這個是我在ZXing包中的RGBLuminanceSourcee類,對loadBitmap()寫的過載方法,在這裡,更具傳進來的boolean值=isOkSize;進行判斷是否進行圖片壓縮,true表示小圖不壓縮,false表示大圖要壓縮,
具體的壓縮臨界值我是更具自己打印出來的size來寫的
/**掃描選擇相簿圖片,判斷是否壓縮的臨界值*/
public static final Long SCAN_DEFAULT_SIZE=(long)4000000;
/**4000000,我發現的問題手機3000000就掛了,所以給了這個值具體的臨界值我也不是很清楚,你可能會問為什麼要給個臨界值盤判斷是否壓縮,那是因為有些小圖壓縮了不能解析識別,所以我進行了判斷,大圖壓縮解析,小圖直接解析*/
private static Bitmap loadBitmap(String path, boolean isOkSize) throws FileNotFoundException {
Bitmap bitmap = null;
if (isOkSize) { //小圖,直接呼叫功能系統的解析返回
bitmap = BitmapFactory.decodeFile(path);
} else { //大圖 先壓縮在返回
bitmap = BitmapUtil.compress(path);
}
//ZXing原始碼
if (bitmap == null) {
throw new FileNotFoundException("Couldn't open " + path);
}
return bitmap;
}
封裝的壓縮px(存寸)的方法
public static Bitmap compress(String srcPath) {
BitmapFactory.Options newOpts = new BitmapFactory.Options();
//開始讀入圖片,此時把options.inJustDecodeBounds 設回true了
newOpts.inJustDecodeBounds = true;
Bitmap bitmap = BitmapFactory.decodeFile(srcPath, newOpts);//此時返回bm為空
newOpts.inJustDecodeBounds = false;
int w = newOpts.outWidth;
int h = newOpts.outHeight;
//現在主流手機比較多是800*480解析度,所以高和寬我們設定為
float hh = 800f;//這裡設定高度為800f 這裡我寫死了尺寸
float ww = 480f;//這裡設定寬度為480f 這裡我寫死了尺寸
//縮放比。由於是固定比例縮放,只用高或者寬其中一個數據進行計算即可
int be = 1;//be=1表示不縮放
if (w > h && w > ww) {//如果寬度大的話根據寬度固定大小縮放
be = (int) (newOpts.outWidth / ww);
} else if (w < h && h > hh) {//如果高度高的話根據寬度固定大小縮放
be = (int) (newOpts.outHeight / hh);
}
if (be <= 0)
be = 1;
newOpts.inSampleSize = be;//設定縮放比例
//重新讀入圖片,注意此時已經把options.inJustDecodeBounds 設回false了
bitmap = BitmapFactory.decodeFile(srcPath, newOpts);
return bitmap;//壓縮好比例大小後再進行質量壓縮
//return bitmap;
}
總結:
**其實實現最為核心的就是這幾行程式碼
scanBitmap = BitmapFactory.decodeFile(path, options);
//輸入bitmap解析出結果
RGBLuminanceSource source = new RGBLuminanceSource(scanBitmap);
BinaryBitmap bitmap1 = new BinaryBitmap(new HybridBinarizer(source));
//CodaBarReader codaBarReader= new CodaBarReader(); //codaBarReader 二維碼
try {
return new MultiFormatReader().decode(bitmap1,hints); //識別條形碼
} catch (NotFoundException e) {
e.printStackTrace();
}
return null;
還有就是開啟相簿返回的路徑問題,系統版本不同一共有三種情況
解決方法1;直接使用這樣的方式開啟相簿
Intent intent = new Intent(Intent.ACTION_PICK,android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
intent.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,"image/*");
activity.startActivityForResult(intent, START_ALBUM_CODE);
解決方法2;在onActivityResult中對路徑進行三次判斷獲取到正確的路徑,在轉車bitmap物件或者直接把path傳遞給RGBLuminanceSourcee解析類,進行解析
/**對路徑進行的判斷(因為我們不知道是三種情況中的那一種)*/
String filePath = "$$";
Uri contentUri = data.getData();
if (DocumentsContract.isDocumentUri(CaptureActivity.this, contentUri)) {
String wholeID = DocumentsContract
.getDocumentId(contentUri);
String id = wholeID.split(":")[1];
String[] column = {MediaStore.Images.Media.DATA};
String sel = MediaStore.Images.Media._ID + "=?";
Cursor cursor = CaptureActivity.this.getContentResolver().query(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
column, sel, new String[]{id}, null);
int columnIndex = cursor.getColumnIndex(column[0]);
if (cursor.moveToFirst()) {
filePath = cursor.getString(columnIndex);
}
cursor.close();
} else {
if (!TextUtils.isEmpty(contentUri.getAuthority())) {
Cursor cursor = getContentResolver().query(contentUri,
new String[]{MediaStore.Images.Media.DATA},
null, null, null);
if (null == cursor) {
showToast("圖片沒找到");
}
cursor.moveToFirst();
filePath = cursor.getString(cursor
.getColumnIndex(MediaStore.Images.Media.DATA));
cursor.close();
} else {
filePath = data.getData().getPath();
}
}
圖片壓縮
public static Bitmap compress(String srcPath) {
BitmapFactory.Options newOpts = new BitmapFactory.Options();
//開始讀入圖片,此時把options.inJustDecodeBounds 設回true了
newOpts.inJustDecodeBounds = true;
Bitmap bitmap = BitmapFactory.decodeFile(srcPath, newOpts);//此時返回bm為空
newOpts.inJustDecodeBounds = false;
int w = newOpts.outWidth;
int h = newOpts.outHeight;
//現在主流手機比較多是800*480解析度,所以高和寬我們設定為
float hh = 800f;//這裡設定高度為800f
float ww = 480f;//這裡設定寬度為480f
//縮放比。由於是固定比例縮放,只用高或者寬其中一個數據進行計算即可
int be = 1;//be=1表示不縮放
if (w > h && w > ww) {//如果寬度大的話根據寬度固定大小縮放
be = (int) (newOpts.outWidth / ww);
} else if (w < h && h > hh) {//如果高度高的話根據寬度固定大小縮放
be = (int) (newOpts.outHeight / hh);
}
if (be <= 0)
be = 1;
newOpts.inSampleSize = be;//設定縮放比例
//重新讀入圖片,注意此時已經把options.inJustDecodeBounds 設回false了
bitmap = BitmapFactory.decodeFile(srcPath, newOpts);
return bitmap;//壓縮好比例大小後再進行質量壓縮
//return bitmap;
}
在華麗的分割線上和下,都可以實現,我給的Demo就是第二種,其實第一種更為的簡單利索,直接在CaptureActivity,Copy我的程式碼,進行相對應的改動即可
—————以上的程式碼就能滿足相簿獲取二維碼,條形碼,和解決上述問題,下面的補充知識方法二,—————可以忽略不看,因為我也沒有實踐過,看網上的部落格分享下而已————–
========================華麗的分割線=============================
看了別人的一波部落格感覺也很好,但是隻能相簿選擇二維碼,想要實現二維碼和條形碼都都能識別,那麼只需要新增一行
new MultiFormatReader().decode(bitmap1,hints); 程式碼即可,最後面我會貼上修改後的Demo
*可以參考他的部落格http://blog.csdn.net/aaawqqq/article/details/24880209
我在他的基礎上修改了,實現識別條形碼和二維碼
第二種:
實現思路
*在Zxing掃描識別和圖片識別的解析物件是相同的
1,獲取相簿的照片
2,解析二維碼圖片
3,返回結果
第一步:獲取相簿照片
/**
* 獲取帶二維碼的相片進行掃描
*/
Intent intent = new Intent(Intent.ACTION_PICK,android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
intent.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,"image/*");
this.startActivityForResult(intent, START_ALBUM_CODE);
第二步:選擇了照片後,然後返回的資料在onActivityResult方法中獲取
/**
* 對相簿獲取的結果進行分析
*/
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
// TODO Auto-generated method stub
if (resultCode == RESULT_OK) { //成功且有資料
switch (requestCode) {
case 1://不同手機返回的照片路徑可能不同
try { //情況一,不是絕對路徑的時候,遊標獲取路徑
Uri uri = data.getData();
if (!TextUtils.isEmpty(uri.getAuthority())) {
Cursor cursor = getContentResolver().query(uri,
new String[] { MediaStore.Images.Media.DATA },
null, null, null);
if (null == cursor) {
Toast.makeText(this, "圖片沒找到", Toast.LENGTH_SHORT)
.show();
return;
}
cursor.moveToFirst();
photo_path = cursor.getString(cursor
.getColumnIndex(MediaStore.Images.Media.DATA));
cursor.close();
} else {//情況2,絕對路徑直接獲取
photo_path = data.getData().getPath();
}
Log.e("main","###############"+photo_path);
mProgress = new ProgressDialog(CaptureActivity.this);
//一些progressbar
mProgress.setMessage("正在掃描...");
mProgress.setCancelable(false);
mProgress.show();
/**重點,開啟執行緒,解析圖片返回code*/
new Thread(new Runnable() {
@Override
/**重點方法下面詳細分析其實和我上面分析的差不多*/
Result result = scanningImage(photo_path);
if (result != null) {
Message m = mHandler.obtainMessage();
m.what = 1;
m.obj = result.getText();
mHandler.sendMessage(m);
} else {
Message m = mHandler.obtainMessage();
m.what = 2;
m.obj = "Scan failed!";
mHandler.sendMessage(m);
}
}
}).start();
} catch (Exception e) {
Toast.makeText(CaptureActivity.this, "解析錯誤!",
Toast.LENGTH_LONG).show();
}
break;
default:
break;
}
}
super.onActivityResult(requestCode, resultCode, data);
}
重點方法的分析:scanningImage(String path)
/**
* 掃描二維碼圖片的方法,返回結果
*
* @param path
* @return 返回結果
*/
public Result scanningImage(String path) {
if (TextUtils.isEmpty(path)) {
return null;
}
Hashtable<DecodeHintType, String> hints = new Hashtable<DecodeHintType, String>();
hints.put(DecodeHintType.TRY_HARDER, "UTF8"); // 設定二維碼內容的編碼
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true; // 先獲取原大小
scanBitmap = BitmapFactory.decodeFile(path, options);
options.inJustDecodeBounds = false; // 獲取新的大小
int sampleSize = (int) (options.outHeight / (float) 100);
if (sampleSize <= 0)
sampleSize = 1;
options.inSampleSize = sampleSize;
//獲取到bitmap物件(相簿圖片物件通過path)
scanBitmap = BitmapFactory.decodeFile(path, options);
//輸入bitmap解析的二值化結果(就是圖片的二進位制形式)
RGBLuminanceSource source = new RGBLuminanceSource(scanBitmap);
//再把圖片的二進位制形式轉換成,圖片bitmap物件
BinaryBitmap bitmap1 = new BinaryBitmap(new HybridBinarizer(source));
//CodaBarReader codaBarReader= new CodaBarReader(); //codaBarReader 二維碼
try {
/**建立MultiFormatReader物件,呼叫decode()獲取我們想要的資訊,比如條形碼的code,二維碼的資料等等.這裡的MultiFormatReader可以理解為就是一個讀取獲取資料的類,最核心的就是decode()方法 */
return new MultiFormatReader().decode(bitmap1,hints); //識別條形碼
} catch (NotFoundException e) {
e.printStackTrace();
}
return null;
}