Skip to content

Commit 921aad0

Browse files
[firebase_ml_vision] v2 embedding API (#275)
1 parent cf038c8 commit 921aad0

File tree

16 files changed

+371
-210
lines changed

16 files changed

+371
-210
lines changed

packages/firebase_ml_vision/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
## 0.9.3
2+
3+
* Support v2 embedding. This plugin will remain compatible with the original embedding and won't
4+
require app migration.
5+
16
## 0.9.2+3
27

38
* Use `BoxDecoration` `const` constructor in example app.

packages/firebase_ml_vision/android/build.gradle

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,4 +52,30 @@ android {
5252
}
5353
}
5454

55+
// TODO(bparrishMines): Remove this hack once androidx.lifecycle is included on stable. https://github.com/flutter/flutter/issues/42348
56+
afterEvaluate {
57+
def containsEmbeddingDependencies = false
58+
for (def configuration : configurations.all) {
59+
for (def dependency : configuration.dependencies) {
60+
if (dependency.group == 'io.flutter' &&
61+
dependency.name.startsWith('flutter_embedding') &&
62+
dependency.isTransitive())
63+
{
64+
containsEmbeddingDependencies = true
65+
break
66+
}
67+
}
68+
}
69+
if (!containsEmbeddingDependencies) {
70+
android {
71+
dependencies {
72+
def lifecycle_version = "1.1.1"
73+
compileOnly "android.arch.lifecycle:runtime:$lifecycle_version"
74+
compileOnly "android.arch.lifecycle:common:$lifecycle_version"
75+
compileOnly "android.arch.lifecycle:common-java8:$lifecycle_version"
76+
}
77+
}
78+
}
79+
}
80+
5581
apply from: file("./user-agent.gradle")
Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1 @@
11
org.gradle.jvmargs=-Xmx1536M
2-
android.enableJetifier=true
3-
android.useAndroidX=true
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
package io.flutter.plugins.firebasemlvision;
2+
3+
import android.content.Context;
4+
import android.graphics.Bitmap;
5+
import android.graphics.BitmapFactory;
6+
import android.graphics.Matrix;
7+
import android.net.Uri;
8+
import android.util.SparseArray;
9+
import androidx.exifinterface.media.ExifInterface;
10+
import com.google.firebase.ml.vision.FirebaseVision;
11+
import com.google.firebase.ml.vision.common.FirebaseVisionImage;
12+
import com.google.firebase.ml.vision.common.FirebaseVisionImageMetadata;
13+
import io.flutter.plugin.common.MethodCall;
14+
import io.flutter.plugin.common.MethodChannel;
15+
import java.io.File;
16+
import java.io.IOException;
17+
import java.util.Map;
18+
19+
class FirebaseMlVisionHandler implements MethodChannel.MethodCallHandler {
20+
private final SparseArray<Detector> detectors = new SparseArray<>();
21+
private final Context applicationContext;
22+
23+
FirebaseMlVisionHandler(Context applicationContext) {
24+
this.applicationContext = applicationContext;
25+
}
26+
27+
@Override
28+
public void onMethodCall(MethodCall call, MethodChannel.Result result) {
29+
switch (call.method) {
30+
case "BarcodeDetector#detectInImage":
31+
case "FaceDetector#processImage":
32+
case "ImageLabeler#processImage":
33+
case "TextRecognizer#processImage":
34+
handleDetection(call, result);
35+
break;
36+
case "BarcodeDetector#close":
37+
case "FaceDetector#close":
38+
case "ImageLabeler#close":
39+
case "TextRecognizer#close":
40+
closeDetector(call, result);
41+
break;
42+
default:
43+
result.notImplemented();
44+
}
45+
}
46+
47+
private void handleDetection(MethodCall call, MethodChannel.Result result) {
48+
Map<String, Object> options = call.argument("options");
49+
50+
FirebaseVisionImage image;
51+
Map<String, Object> imageData = call.arguments();
52+
try {
53+
image = dataToVisionImage(imageData);
54+
} catch (IOException exception) {
55+
result.error("MLVisionDetectorIOError", exception.getLocalizedMessage(), null);
56+
return;
57+
}
58+
59+
Detector detector = getDetector(call);
60+
if (detector == null) {
61+
switch (call.method.split("#")[0]) {
62+
case "BarcodeDetector":
63+
detector = new BarcodeDetector(FirebaseVision.getInstance(), options);
64+
break;
65+
case "FaceDetector":
66+
detector = new FaceDetector(FirebaseVision.getInstance(), options);
67+
break;
68+
case "ImageLabeler":
69+
detector = new ImageLabeler(FirebaseVision.getInstance(), options);
70+
break;
71+
case "TextRecognizer":
72+
detector = new TextRecognizer(FirebaseVision.getInstance(), options);
73+
break;
74+
}
75+
76+
final Integer handle = call.argument("handle");
77+
addDetector(handle, detector);
78+
}
79+
80+
detector.handleDetection(image, result);
81+
}
82+
83+
private void closeDetector(final MethodCall call, final MethodChannel.Result result) {
84+
final Detector detector = getDetector(call);
85+
86+
if (detector == null) {
87+
final Integer handle = call.argument("handle");
88+
final String message = String.format("Object for handle does not exists: %s", handle);
89+
throw new IllegalArgumentException(message);
90+
}
91+
92+
try {
93+
detector.close();
94+
result.success(null);
95+
} catch (IOException e) {
96+
final String code = String.format("%sIOError", detector.getClass().getSimpleName());
97+
result.error(code, e.getLocalizedMessage(), null);
98+
} finally {
99+
final Integer handle = call.argument("handle");
100+
detectors.remove(handle);
101+
}
102+
}
103+
104+
private FirebaseVisionImage dataToVisionImage(Map<String, Object> imageData) throws IOException {
105+
String imageType = (String) imageData.get("type");
106+
assert imageType != null;
107+
108+
switch (imageType) {
109+
case "file":
110+
final String imageFilePath = (String) imageData.get("path");
111+
final int rotation = getImageExifOrientation(imageFilePath);
112+
113+
if (rotation == 0) {
114+
File file = new File(imageFilePath);
115+
return FirebaseVisionImage.fromFilePath(applicationContext, Uri.fromFile(file));
116+
}
117+
118+
Matrix matrix = new Matrix();
119+
matrix.postRotate(rotation);
120+
121+
final Bitmap bitmap = BitmapFactory.decodeFile(imageFilePath);
122+
final Bitmap rotatedBitmap =
123+
Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
124+
125+
return FirebaseVisionImage.fromBitmap(rotatedBitmap);
126+
case "bytes":
127+
@SuppressWarnings("unchecked")
128+
Map<String, Object> metadataData = (Map<String, Object>) imageData.get("metadata");
129+
130+
FirebaseVisionImageMetadata metadata =
131+
new FirebaseVisionImageMetadata.Builder()
132+
.setWidth((int) (double) metadataData.get("width"))
133+
.setHeight((int) (double) metadataData.get("height"))
134+
.setFormat(FirebaseVisionImageMetadata.IMAGE_FORMAT_NV21)
135+
.setRotation(getRotation((int) metadataData.get("rotation")))
136+
.build();
137+
138+
byte[] bytes = (byte[]) imageData.get("bytes");
139+
assert bytes != null;
140+
141+
return FirebaseVisionImage.fromByteArray(bytes, metadata);
142+
default:
143+
throw new IllegalArgumentException(String.format("No image type for: %s", imageType));
144+
}
145+
}
146+
147+
private int getImageExifOrientation(String imageFilePath) throws IOException {
148+
ExifInterface exif = new ExifInterface(imageFilePath);
149+
int orientation =
150+
exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL);
151+
152+
switch (orientation) {
153+
case ExifInterface.ORIENTATION_ROTATE_90:
154+
return 90;
155+
case ExifInterface.ORIENTATION_ROTATE_180:
156+
return 180;
157+
case ExifInterface.ORIENTATION_ROTATE_270:
158+
return 270;
159+
default:
160+
return 0;
161+
}
162+
}
163+
164+
private int getRotation(int rotation) {
165+
switch (rotation) {
166+
case 0:
167+
return FirebaseVisionImageMetadata.ROTATION_0;
168+
case 90:
169+
return FirebaseVisionImageMetadata.ROTATION_90;
170+
case 180:
171+
return FirebaseVisionImageMetadata.ROTATION_180;
172+
case 270:
173+
return FirebaseVisionImageMetadata.ROTATION_270;
174+
default:
175+
throw new IllegalArgumentException(String.format("No rotation for: %d", rotation));
176+
}
177+
}
178+
179+
private void addDetector(final int handle, final Detector detector) {
180+
if (detectors.get(handle) != null) {
181+
final String message = String.format("Object for handle already exists: %s", handle);
182+
throw new IllegalArgumentException(message);
183+
}
184+
185+
detectors.put(handle, detector);
186+
}
187+
188+
private Detector getDetector(final MethodCall call) {
189+
final Integer handle = call.argument("handle");
190+
return detectors.get(handle);
191+
}
192+
}

0 commit comments

Comments
 (0)