Android 解析二维码图片
我们项目之前已集成了一个zxing的扫描二维码开源库。现新增需求是将用户存储到相册中的二维码图片识别并解析出来。网上也有很多这样解析的例子,我这篇博客也是在借鉴他们提供的方法之后写出来的,整个解析过程都差不多,不同的是,因为每个人集成的项目会有所不同,中间会有所修改。而且在做这个项目的时候,中间出了几个异常,我提出来一是引以为鉴,二是里面有些错误,我也不是很明白,希望有人看到之后,能顺便帮我解解惑
我们项目之前已集成了一个zxing的扫描二维码开源库。现新增需求是将用户存储到相册中的二维码图片识别并解析出来。网上也有很多这样解析的例子,我这篇博客也是在借鉴他们提供的方法之后写出来的,整个解析过程都差不多,不同的是,因为每个人集成的项目会有所不同,中间会有所修改。而且在做这个项目的时候,中间出了几个异常,我提出来一是引以为鉴,二是里面有些错误,我也不是很明白,希望有人看到之后,能顺便帮我解解惑。
1.项目需求:知道项目需求,才知道先从哪里入手,见图一。(点击相册,打开图库)
1.项目需求:知道项目需求,才知道先从哪里入手,见图一。(点击相册,打开图库)
2.代码:
1.打开图库代码:
@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());),运行正常,能解析出来。
- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
现在来看第二种解析方法
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.
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/huang_xiao_yu/article/details/53419366
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)