Skip to content

Commit ac3822f

Browse files
committedApr 22, 2021
1.2.0
1 parent 268184a commit ac3822f

File tree

12 files changed

+258
-111
lines changed

12 files changed

+258
-111
lines changed
 

‎CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
## 1.2.0
2+
- fix android decode qrcode from image
13
## 1.1.1
24
- fix ios config
35
## 1.1.0

‎README.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@ flutter widget to scan qrcode customly.
44

55
Get qrcode from image.
66

7+
> if you want to generate qrcode image, you should use [qr_flutter](https://pub.dev/packages/qr_flutter)
8+
79
### Features
810

911
- use `ScanView` in widget tree to show scan view.
1012
- custom identifiable area.
11-
- get qrcode string from image path by `Scan.parse`.
13+
- decode qrcode from image path by `Scan.parse`.
1214

1315
### prepare
1416

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
package com.chavesgu.scan;
2+
3+
import android.graphics.Bitmap;
4+
import android.graphics.BitmapFactory;
5+
import android.util.Log;
6+
7+
import com.google.zxing.ChecksumException;
8+
import com.google.zxing.FormatException;
9+
import com.google.zxing.NotFoundException;
10+
import com.google.zxing.PlanarYUVLuminanceSource;
11+
import com.google.zxing.Result;
12+
import com.google.zxing.qrcode.QRCodeReader;
13+
import com.google.zxing.BarcodeFormat;
14+
import com.google.zxing.BinaryBitmap;
15+
import com.google.zxing.DecodeHintType;
16+
import com.google.zxing.MultiFormatReader;
17+
import com.google.zxing.RGBLuminanceSource;
18+
import com.google.zxing.common.GlobalHistogramBinarizer;
19+
import com.google.zxing.common.HybridBinarizer;
20+
21+
import java.util.ArrayList;
22+
import java.util.Arrays;
23+
import java.util.EnumMap;
24+
import java.util.Hashtable;
25+
import java.util.List;
26+
import java.util.Map;
27+
28+
public class QRCodeDecoder {
29+
private static byte[] yuvs;
30+
public static int MAX_PICTURE_PIXEL = 256;
31+
public static final Map<DecodeHintType, Object> HINTS = new EnumMap<>(DecodeHintType.class);
32+
public static void config() {
33+
HINTS.clear();
34+
List<BarcodeFormat> allFormats = new ArrayList<>();
35+
allFormats.add(BarcodeFormat.AZTEC);
36+
allFormats.add(BarcodeFormat.CODABAR);
37+
allFormats.add(BarcodeFormat.CODE_39);
38+
allFormats.add(BarcodeFormat.CODE_93);
39+
allFormats.add(BarcodeFormat.CODE_128);
40+
allFormats.add(BarcodeFormat.DATA_MATRIX);
41+
allFormats.add(BarcodeFormat.EAN_8);
42+
allFormats.add(BarcodeFormat.EAN_13);
43+
allFormats.add(BarcodeFormat.ITF);
44+
allFormats.add(BarcodeFormat.MAXICODE);
45+
allFormats.add(BarcodeFormat.PDF_417);
46+
allFormats.add(BarcodeFormat.QR_CODE);
47+
allFormats.add(BarcodeFormat.RSS_14);
48+
allFormats.add(BarcodeFormat.RSS_EXPANDED);
49+
allFormats.add(BarcodeFormat.UPC_A);
50+
allFormats.add(BarcodeFormat.UPC_E);
51+
allFormats.add(BarcodeFormat.UPC_EAN_EXTENSION);
52+
HINTS.put(DecodeHintType.TRY_HARDER, Boolean.TRUE);
53+
HINTS.put(DecodeHintType.POSSIBLE_FORMATS, allFormats);
54+
HINTS.put(DecodeHintType.CHARACTER_SET, "utf-8");
55+
// HINTS.put(DecodeHintType.PURE_BARCODE, Boolean.TRUE);
56+
}
57+
public static String syncDecodeQRCode(String path) {
58+
config();
59+
Bitmap bitmap = pathToBitMap(path, MAX_PICTURE_PIXEL, MAX_PICTURE_PIXEL);
60+
int width = bitmap.getWidth();
61+
int height = bitmap.getHeight();
62+
byte[] mData = getYUV420sp(bitmap.getWidth(), bitmap.getHeight(), bitmap);
63+
64+
Result result = decodeImage(mData, width, height);
65+
if (result!=null) return result.getText();
66+
return null;
67+
}
68+
69+
private static Result decodeImage(byte[] data, int width, int height) {
70+
// 处理
71+
Result result = null;
72+
try {
73+
PlanarYUVLuminanceSource source =
74+
new PlanarYUVLuminanceSource(data, width, height, 0, 0, width, height, false);
75+
BinaryBitmap bitmap1 = new BinaryBitmap(new GlobalHistogramBinarizer(source));
76+
QRCodeReader reader2 = new QRCodeReader();
77+
result = reader2.decode(bitmap1, HINTS);
78+
} catch (FormatException | ChecksumException ignored) {
79+
} catch (NotFoundException e) {
80+
PlanarYUVLuminanceSource source =
81+
new PlanarYUVLuminanceSource(data, width, height, 0, 0, width, height, false);
82+
BinaryBitmap bitmap1 = new BinaryBitmap(new HybridBinarizer(source));
83+
QRCodeReader reader2 = new QRCodeReader();
84+
try {
85+
result = reader2.decode(bitmap1, HINTS);
86+
} catch (NotFoundException | ChecksumException | FormatException ignored) {
87+
}
88+
}
89+
return result;
90+
}
91+
92+
private static Bitmap pathToBitMap(String imgPath, int reqWidth, int reqHeight) {
93+
final BitmapFactory.Options options = new BitmapFactory.Options();
94+
options.inJustDecodeBounds = true;
95+
BitmapFactory.decodeFile(imgPath, options);
96+
97+
// Calculate inSampleSize
98+
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
99+
100+
// Decode bitmap with inSampleSize set
101+
options.inJustDecodeBounds = false;
102+
return BitmapFactory.decodeFile(imgPath, options);
103+
}
104+
105+
private static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
106+
// Raw height and width of image
107+
final int height = options.outHeight;
108+
final int width = options.outWidth;
109+
int inSampleSize = 1;
110+
111+
if (height > reqHeight || width > reqWidth) {
112+
113+
final int halfHeight = height / 2;
114+
final int halfWidth = width / 2;
115+
116+
// Calculate the largest inSampleSize value that is a power of 2 and keeps both
117+
// height and width larger than the requested height and width.
118+
while ((halfHeight / inSampleSize) > reqHeight && (halfWidth / inSampleSize) > reqWidth) {
119+
inSampleSize *= 2;
120+
}
121+
}
122+
123+
return inSampleSize;
124+
}
125+
126+
private static byte[] getYUV420sp(int inputWidth, int inputHeight, Bitmap scaled) {
127+
int[] argb = new int[inputWidth * inputHeight];
128+
129+
scaled.getPixels(argb, 0, inputWidth, 0, 0, inputWidth, inputHeight);
130+
131+
int requiredWidth = inputWidth % 2 == 0 ? inputWidth : inputWidth + 1;
132+
int requiredHeight = inputHeight % 2 == 0 ? inputHeight : inputHeight + 1;
133+
134+
int byteLength = requiredWidth * requiredHeight * 3 / 2;
135+
if (yuvs == null || yuvs.length < byteLength) {
136+
yuvs = new byte[byteLength];
137+
} else {
138+
Arrays.fill(yuvs, (byte) 0);
139+
}
140+
141+
encodeYUV420SP(yuvs, argb, inputWidth, inputHeight);
142+
143+
scaled.recycle();
144+
145+
return yuvs;
146+
}
147+
148+
private static void encodeYUV420SP(byte[] yuv420sp, int[] argb, int width, int height) {
149+
// 帧图片的像素大小
150+
final int frameSize = width * height;
151+
// ---YUV数据---
152+
int Y, U, V;
153+
// Y的index从0开始
154+
int yIndex = 0;
155+
// UV的index从frameSize开始
156+
int uvIndex = frameSize;
157+
158+
// ---颜色数据---
159+
// int a, R, G, B;
160+
int R, G, B;
161+
//
162+
int argbIndex = 0;
163+
//
164+
165+
// ---循环所有像素点,RGB转YUV---
166+
for (int j = 0; j < height; j++) {
167+
for (int i = 0; i < width; i++) {
168+
169+
// a is not used obviously
170+
// a = (argb[argbIndex] & 0xff000000) >> 24;
171+
R = (argb[argbIndex] & 0xff0000) >> 16;
172+
G = (argb[argbIndex] & 0xff00) >> 8;
173+
B = (argb[argbIndex] & 0xff);
174+
//
175+
argbIndex++;
176+
177+
// well known RGB to YUV algorithm
178+
Y = ((66 * R + 129 * G + 25 * B + 128) >> 8) + 16;
179+
U = ((-38 * R - 74 * G + 112 * B + 128) >> 8) + 128;
180+
V = ((112 * R - 94 * G - 18 * B + 128) >> 8) + 128;
181+
182+
//
183+
Y = Math.max(0, Math.min(Y, 255));
184+
U = Math.max(0, Math.min(U, 255));
185+
V = Math.max(0, Math.min(V, 255));
186+
187+
// NV21 has a plane of Y and interleaved planes of VU each sampled by a factor of 2
188+
// meaning for every 4 Y pixels there are 1 V and 1 U. Note the sampling is every other
189+
// pixel AND every other scanline.
190+
// ---Y---
191+
yuv420sp[yIndex++] = (byte) Y;
192+
// ---UV---
193+
if ((j % 2 == 0) && (i % 2 == 0)) {
194+
//
195+
yuv420sp[uvIndex++] = (byte) V;
196+
//
197+
yuv420sp[uvIndex++] = (byte) U;
198+
}
199+
}
200+
}
201+
}
202+
}

‎android/src/main/java/com/chavesgu/scan/ScanPlugin.java

+34-69
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import android.app.Activity;
44
import android.graphics.Bitmap;
55
import android.graphics.BitmapFactory;
6+
import android.os.AsyncTask;
67

78
import com.google.zxing.BarcodeFormat;
89
import com.google.zxing.BinaryBitmap;
@@ -13,7 +14,9 @@
1314
import com.google.zxing.RGBLuminanceSource;
1415
import com.google.zxing.common.GlobalHistogramBinarizer;
1516
import com.google.zxing.common.HybridBinarizer;
17+
import com.journeyapps.barcodescanner.CaptureActivity;
1618

19+
import java.lang.ref.WeakReference;
1720
import java.util.ArrayList;
1821
import java.util.EnumMap;
1922
import java.util.List;
@@ -36,6 +39,8 @@ public class ScanPlugin implements FlutterPlugin, MethodCallHandler, ActivityAwa
3639
private MethodChannel channel;
3740
private Activity activity;
3841
private FlutterPluginBinding flutterPluginBinding;
42+
private Result _result;
43+
private QrCodeAsyncTask task;
3944

4045
@Override
4146
public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) {
@@ -81,84 +86,44 @@ public void onDetachedFromActivity() {
8186

8287
@Override
8388
public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) {
89+
_result = result;
8490
if (call.method.equals("getPlatformVersion")) {
8591
result.success("Android " + android.os.Build.VERSION.RELEASE);
8692
} else if (call.method.equals("parse")) {
87-
result.success(getCodeFromImagePath((String) call.arguments));
93+
String path = (String) call.arguments;
94+
task = new QrCodeAsyncTask(this, path);
95+
task.execute(path);
8896
} else {
8997
result.notImplemented();
9098
}
9199
}
92100

93-
private String getCodeFromImagePath(String path) {
94-
final Map<DecodeHintType, Object> HINTS = new EnumMap<>(DecodeHintType.class);
95-
List<BarcodeFormat> allFormats = new ArrayList<>();
96-
allFormats.add(BarcodeFormat.AZTEC);
97-
allFormats.add(BarcodeFormat.CODABAR);
98-
allFormats.add(BarcodeFormat.CODE_39);
99-
allFormats.add(BarcodeFormat.CODE_93);
100-
allFormats.add(BarcodeFormat.CODE_128);
101-
allFormats.add(BarcodeFormat.DATA_MATRIX);
102-
allFormats.add(BarcodeFormat.EAN_8);
103-
allFormats.add(BarcodeFormat.EAN_13);
104-
allFormats.add(BarcodeFormat.ITF);
105-
allFormats.add(BarcodeFormat.MAXICODE);
106-
allFormats.add(BarcodeFormat.PDF_417);
107-
allFormats.add(BarcodeFormat.QR_CODE);
108-
allFormats.add(BarcodeFormat.RSS_14);
109-
allFormats.add(BarcodeFormat.RSS_EXPANDED);
110-
allFormats.add(BarcodeFormat.UPC_A);
111-
allFormats.add(BarcodeFormat.UPC_E);
112-
allFormats.add(BarcodeFormat.UPC_EAN_EXTENSION);
113-
HINTS.put(DecodeHintType.TRY_HARDER, BarcodeFormat.QR_CODE);
114-
HINTS.put(DecodeHintType.POSSIBLE_FORMATS, allFormats);
115-
HINTS.put(DecodeHintType.CHARACTER_SET, "utf-8");
116-
117-
BitmapFactory.Options options = new BitmapFactory.Options();
118-
options.inJustDecodeBounds = true;
119-
BitmapFactory.decodeFile(path, options);
120-
int sampleSize = options.outHeight / 400;
121-
if (sampleSize <= 0) {
122-
sampleSize = 1;
101+
/**
102+
* AsyncTask 静态内部类,防止内存泄漏
103+
*/
104+
static class QrCodeAsyncTask extends AsyncTask<String, Integer, String> {
105+
private final WeakReference<ScanPlugin> mWeakReference;
106+
private final String path;
107+
108+
public QrCodeAsyncTask(ScanPlugin plugin, String path) {
109+
mWeakReference = new WeakReference<>(plugin);
110+
this.path = path;
111+
}
112+
113+
@Override
114+
protected String doInBackground(String... strings) {
115+
// 解析二维码/条码
116+
return QRCodeDecoder.syncDecodeQRCode(path);
123117
}
124-
options.inSampleSize = sampleSize;
125-
options.inJustDecodeBounds = false;
126-
Bitmap bitmap = BitmapFactory.decodeFile(path, options);
127-
128-
com.google.zxing.Result result = null;
129-
RGBLuminanceSource source = null;
130-
try {
131-
int width = bitmap.getWidth();
132-
int height = bitmap.getHeight();
133-
int[] pixels = new int[width * height];
134-
bitmap.getPixels(pixels, 0, width, 0, 0, width, height);
135-
source = new RGBLuminanceSource(width, height, pixels);
136-
result = new MultiFormatReader().decode(new BinaryBitmap(new HybridBinarizer(source)), HINTS);
137-
return result.getText();
138-
} catch (Exception e) {
139-
if (source != null) {
140-
try {
141-
result = new MultiFormatReader().decode(new BinaryBitmap(new GlobalHistogramBinarizer(source)), HINTS);
142-
return result.getText();
143-
} catch (Throwable e2) {
144-
MultiFormatReader multiFormatReader = new MultiFormatReader();
145-
try {
146-
LuminanceSource invertedSource = source.invert();
147-
BinaryBitmap binaryBitmap = new BinaryBitmap(new HybridBinarizer(invertedSource));
148-
149-
result = multiFormatReader.decode(binaryBitmap, HINTS);
150-
return result.getText();
151-
} catch (NotFoundException exception) {
152-
e.printStackTrace();
153-
e2.printStackTrace();
154-
exception.printStackTrace();
155-
return null;
156-
} finally {
157-
multiFormatReader.reset();
158-
}
159-
}
160-
}
161-
return null;
118+
119+
@Override
120+
protected void onPostExecute(String s) {
121+
super.onPostExecute(s);
122+
//识别出图片二维码/条码,内容为s
123+
ScanPlugin plugin = (ScanPlugin) mWeakReference.get();
124+
plugin._result.success(s);
125+
plugin.task.cancel(true);
126+
plugin.task = null;
162127
}
163128
}
164129
}

0 commit comments

Comments
 (0)
Please sign in to comment.