cordova-plugin-mlkit-suite 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +526 -640
  3. package/docs/screenshots/ios-text-recognition.jpg +0 -0
  4. package/package.json +7 -3
  5. package/plugin.xml +149 -134
  6. package/src/android/java/BarcodeFrameAnalyzer.java +115 -111
  7. package/src/android/java/BarcodeOverlayView.java +83 -25
  8. package/src/android/java/BarcodeScanActivity.java +100 -12
  9. package/src/android/java/BarcodeScanConfig.java +6 -1
  10. package/src/android/java/BarcodeScanRegion.java +26 -8
  11. package/src/android/java/MlKitModelsModuleInstaller.java +35 -16
  12. package/src/android/java/MlKitSuitePlugin.java +315 -33
  13. package/src/android/java/SelfieSegmentationProcessor.java +215 -0
  14. package/src/android/java/StaticImageProcessor.java +208 -151
  15. package/src/android/java/SubjectSegmentationModuleInstaller.java +154 -0
  16. package/src/android/java/SubjectSegmentationProcessor.java +189 -0
  17. package/src/android/mlkit-suite.gradle +30 -0
  18. package/src/android/res/values/mlkit_suite_strings.xml +8 -0
  19. package/src/android/res/values-es/mlkit_suite_strings.xml +8 -0
  20. package/src/ios/BarcodeOverlayView.swift +57 -6
  21. package/src/ios/BarcodeScanTypes.swift +3 -1
  22. package/src/ios/BarcodeScanViewController.swift +120 -5
  23. package/src/ios/MlKitSuitePlugin.swift +222 -13
  24. package/src/ios/MlKitSuiteResources.bundle/en.lproj/Localizable.strings +4 -0
  25. package/src/ios/MlKitSuiteResources.bundle/es.lproj/Localizable.strings +4 -0
  26. package/src/ios/SelfieSegmentationProcessor.swift +105 -0
  27. package/src/ios/TextRecognitionProcessor.swift +99 -0
  28. package/tests/ios-image-labeling.test.js +17 -0
  29. package/tests/ios-live-barcode.test.js +5 -0
  30. package/tests/ios-selfie-segmentation.test.js +24 -0
  31. package/tests/ios-static-text.test.js +21 -0
  32. package/tests/java/BarcodeScanRegionTest.java +12 -0
  33. package/tests/java-confirmation.test.js +41 -41
  34. package/tests/js-api.test.js +42 -2
  35. package/www/MlKit.js +111 -7
  36. package/docs/screenshots/capabilities.jpg +0 -0
  37. package/docs/screenshots/demo-tour.gif +0 -0
  38. package/docs/screenshots/text-recognition.jpg +0 -0
@@ -1,5 +1,6 @@
1
1
  package com.andreszs.plugins.mlkitsuite;
2
2
 
3
+ import android.animation.ValueAnimator;
3
4
  import android.content.Context;
4
5
  import android.graphics.Canvas;
5
6
  import android.graphics.Color;
@@ -9,14 +10,20 @@ import android.graphics.Rect;
9
10
  import android.graphics.RectF;
10
11
  import android.util.AttributeSet;
11
12
  import android.view.View;
13
+ import android.view.animation.DecelerateInterpolator;
12
14
 
13
15
  final class BarcodeOverlayView extends View {
14
16
  private final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
15
17
  private final RectF guide = new RectF();
18
+ private final RectF animatedGuide = new RectF();
19
+ private final RectF animationStart = new RectF();
20
+ private final RectF animationTarget = new RectF();
16
21
  private final String instruction;
17
22
  private final Integer guideColor;
18
23
  private String message;
19
24
  private boolean candidateVisible;
25
+ private boolean successVisible;
26
+ private ValueAnimator successAnimator;
20
27
 
21
28
  BarcodeOverlayView(Context context, String instruction, Integer guideColor,
22
29
  String initialMessage) {
@@ -49,35 +56,76 @@ final class BarcodeOverlayView extends View {
49
56
  );
50
57
  }
51
58
 
59
+ void showSuccess(Rect bounds, int imageWidth, int imageHeight, String text) {
60
+ updateGuide(getWidth(), getHeight());
61
+ float[] mapped = bounds == null ? null : BarcodeScanRegion.map(
62
+ getWidth(), getHeight(), imageWidth, imageHeight,
63
+ bounds.left, bounds.top, bounds.right, bounds.bottom
64
+ );
65
+ animationStart.set(guide);
66
+ if (mapped == null) {
67
+ animationTarget.set(guide);
68
+ } else {
69
+ float padding = dp(10);
70
+ animationTarget.set(
71
+ Math.max(0, mapped[0] - padding),
72
+ Math.max(0, mapped[1] - padding),
73
+ Math.min(getWidth(), mapped[2] + padding),
74
+ Math.min(getHeight(), mapped[3] + padding)
75
+ );
76
+ }
77
+ animatedGuide.set(animationStart);
78
+ message = text;
79
+ candidateVisible = true;
80
+ successVisible = true;
81
+ if (successAnimator != null) successAnimator.cancel();
82
+ successAnimator = ValueAnimator.ofFloat(0f, 1f);
83
+ successAnimator.setDuration(220L);
84
+ successAnimator.setInterpolator(new DecelerateInterpolator());
85
+ successAnimator.addUpdateListener(animation -> {
86
+ float progress = (float) animation.getAnimatedValue();
87
+ animatedGuide.set(
88
+ lerp(animationStart.left, animationTarget.left, progress),
89
+ lerp(animationStart.top, animationTarget.top, progress),
90
+ lerp(animationStart.right, animationTarget.right, progress),
91
+ lerp(animationStart.bottom, animationTarget.bottom, progress)
92
+ );
93
+ invalidate();
94
+ });
95
+ successAnimator.start();
96
+ }
97
+
52
98
  @Override
53
99
  protected void onDraw(Canvas canvas) {
54
100
  super.onDraw(canvas);
55
101
  float width = getWidth();
56
102
  float height = getHeight();
57
103
  updateGuide(width, height);
104
+ RectF frame = successVisible ? animatedGuide : guide;
58
105
 
59
106
  Path shade = new Path();
60
107
  shade.setFillType(Path.FillType.EVEN_ODD);
61
108
  shade.addRect(0, 0, width, height, Path.Direction.CW);
62
- shade.addRoundRect(guide, dp(18), dp(18), Path.Direction.CCW);
109
+ shade.addRoundRect(frame, dp(18), dp(18), Path.Direction.CCW);
63
110
  paint.setStyle(Paint.Style.FILL);
64
111
  paint.setColor(0x99000000);
65
112
  canvas.drawPath(shade, paint);
66
113
 
67
114
  int color = candidateVisible ? 0xff4ade80 : Color.WHITE;
68
115
  if (candidateVisible) {
69
- drawCornerFrame(canvas, color, color, color, color);
116
+ drawCornerFrame(canvas, frame, color, color, color, color);
70
117
  } else if (guideColor != null) {
71
- drawCornerFrame(canvas, guideColor, guideColor, guideColor, guideColor);
118
+ drawCornerFrame(canvas, frame, guideColor, guideColor, guideColor, guideColor);
72
119
  } else {
73
- drawCornerFrame(canvas, 0xff4285f4, 0xfffbbc04, 0xff34a853, 0xffea4335);
120
+ drawCornerFrame(canvas, frame, 0xff4285f4, 0xfffbbc04, 0xff34a853,
121
+ 0xffea4335);
74
122
  }
75
123
 
76
- if (!instruction.isEmpty()) {
124
+ if (!successVisible && !instruction.isEmpty()) {
77
125
  drawCenteredText(canvas, instruction, Math.max(dp(72), guide.top - dp(42)),
78
126
  dp(17), Color.WHITE);
79
127
  }
80
- drawCenteredText(canvas, message, Math.min(height - dp(72), guide.bottom + dp(54)),
128
+ drawCenteredText(canvas, message, Math.min(height - dp(72), frame.bottom + dp(54)),
81
129
  dp(17), color);
82
130
  }
83
131
 
@@ -88,44 +136,44 @@ final class BarcodeOverlayView extends View {
88
136
  width / 2f + guideSize / 2f, centerY + guideSize / 2f);
89
137
  }
90
138
 
91
- private void drawCornerFrame(Canvas canvas, int topLeft, int topRight,
139
+ private void drawCornerFrame(Canvas canvas, RectF frame, int topLeft, int topRight,
92
140
  int bottomLeft, int bottomRight) {
93
- float radius = dp(22);
94
- float leg = Math.min(guide.width() * 0.22f, dp(76));
141
+ float radius = Math.min(dp(22), Math.min(frame.width(), frame.height()) * 0.25f);
142
+ float leg = Math.min(Math.min(frame.width(), frame.height()) * 0.32f, dp(76));
95
143
  paint.setStyle(Paint.Style.STROKE);
96
144
  paint.setStrokeWidth(dp(4));
97
145
  paint.setStrokeCap(Paint.Cap.ROUND);
98
146
  paint.setStrokeJoin(Paint.Join.ROUND);
99
147
 
100
148
  Path path = new Path();
101
- path.moveTo(guide.left, guide.top + leg);
102
- path.lineTo(guide.left, guide.top + radius);
103
- path.quadTo(guide.left, guide.top, guide.left + radius, guide.top);
104
- path.lineTo(guide.left + leg, guide.top);
149
+ path.moveTo(frame.left, frame.top + leg);
150
+ path.lineTo(frame.left, frame.top + radius);
151
+ path.quadTo(frame.left, frame.top, frame.left + radius, frame.top);
152
+ path.lineTo(frame.left + leg, frame.top);
105
153
  paint.setColor(topLeft);
106
154
  canvas.drawPath(path, paint);
107
155
 
108
156
  path.reset();
109
- path.moveTo(guide.right - leg, guide.top);
110
- path.lineTo(guide.right - radius, guide.top);
111
- path.quadTo(guide.right, guide.top, guide.right, guide.top + radius);
112
- path.lineTo(guide.right, guide.top + leg);
157
+ path.moveTo(frame.right - leg, frame.top);
158
+ path.lineTo(frame.right - radius, frame.top);
159
+ path.quadTo(frame.right, frame.top, frame.right, frame.top + radius);
160
+ path.lineTo(frame.right, frame.top + leg);
113
161
  paint.setColor(topRight);
114
162
  canvas.drawPath(path, paint);
115
163
 
116
164
  path.reset();
117
- path.moveTo(guide.left, guide.bottom - leg);
118
- path.lineTo(guide.left, guide.bottom - radius);
119
- path.quadTo(guide.left, guide.bottom, guide.left + radius, guide.bottom);
120
- path.lineTo(guide.left + leg, guide.bottom);
165
+ path.moveTo(frame.left, frame.bottom - leg);
166
+ path.lineTo(frame.left, frame.bottom - radius);
167
+ path.quadTo(frame.left, frame.bottom, frame.left + radius, frame.bottom);
168
+ path.lineTo(frame.left + leg, frame.bottom);
121
169
  paint.setColor(bottomLeft);
122
170
  canvas.drawPath(path, paint);
123
171
 
124
172
  path.reset();
125
- path.moveTo(guide.right - leg, guide.bottom);
126
- path.lineTo(guide.right - radius, guide.bottom);
127
- path.quadTo(guide.right, guide.bottom, guide.right, guide.bottom - radius);
128
- path.lineTo(guide.right, guide.bottom - leg);
173
+ path.moveTo(frame.right - leg, frame.bottom);
174
+ path.lineTo(frame.right - radius, frame.bottom);
175
+ path.quadTo(frame.right, frame.bottom, frame.right, frame.bottom - radius);
176
+ path.lineTo(frame.right, frame.bottom - leg);
129
177
  paint.setColor(bottomRight);
130
178
  canvas.drawPath(path, paint);
131
179
  paint.setStrokeCap(Paint.Cap.BUTT);
@@ -147,4 +195,14 @@ final class BarcodeOverlayView extends View {
147
195
  private float dp(float value) {
148
196
  return value * getResources().getDisplayMetrics().density;
149
197
  }
198
+
199
+ private float lerp(float start, float end, float progress) {
200
+ return start + (end - start) * progress;
201
+ }
202
+
203
+ @Override
204
+ protected void onDetachedFromWindow() {
205
+ if (successAnimator != null) successAnimator.cancel();
206
+ super.onDetachedFromWindow();
207
+ }
150
208
  }
@@ -2,7 +2,13 @@ package com.andreszs.plugins.mlkitsuite;
2
2
 
3
3
  import android.app.Activity;
4
4
  import android.content.Intent;
5
+ import android.graphics.Bitmap;
5
6
  import android.graphics.Color;
7
+ import android.graphics.Rect;
8
+ import android.media.AudioAttributes;
9
+ import android.media.AudioFormat;
10
+ import android.media.AudioManager;
11
+ import android.media.AudioTrack;
6
12
  import android.os.Bundle;
7
13
  import android.os.Handler;
8
14
  import android.os.Looper;
@@ -13,6 +19,7 @@ import android.view.ViewGroup;
13
19
  import android.view.WindowManager;
14
20
  import android.widget.FrameLayout;
15
21
  import android.widget.ImageButton;
22
+ import android.widget.ImageView;
16
23
 
17
24
  import androidx.annotation.NonNull;
18
25
  import androidx.annotation.Nullable;
@@ -38,6 +45,8 @@ import java.util.concurrent.Executors;
38
45
  public final class BarcodeScanActivity extends Activity
39
46
  implements LifecycleOwner, BarcodeFrameAnalyzer.Listener {
40
47
 
48
+ private static final long SUCCESS_FEEDBACK_MS = 450L;
49
+
41
50
  public static final String EXTRA_OPTIONS_JSON = "mlKitSuite.barcode.options";
42
51
  public static final String EXTRA_RESULT_JSON = "mlKitSuite.barcode.result";
43
52
  public static final String EXTRA_ERROR_CODE = "mlKitSuite.barcode.errorCode";
@@ -48,13 +57,16 @@ public final class BarcodeScanActivity extends Activity
48
57
  private final ExecutorService analysisExecutor = Executors.newSingleThreadExecutor();
49
58
  private LifecycleRegistry lifecycleRegistry;
50
59
  private BarcodeScanConfig config;
60
+ private FrameLayout rootView;
51
61
  private PreviewView previewView;
52
62
  private BarcodeOverlayView overlayView;
63
+ private ImageButton closeButton;
53
64
  private ImageButton torchButton;
54
65
  private ProcessCameraProvider cameraProvider;
55
66
  private ImageAnalysis imageAnalysis;
56
67
  private BarcodeFrameAnalyzer analyzer;
57
68
  private Camera camera;
69
+ private AudioTrack successBeep;
58
70
  private boolean torchEnabled;
59
71
  private boolean completed;
60
72
  private Runnable timeoutRunnable;
@@ -112,6 +124,7 @@ public final class BarcodeScanActivity extends Activity
112
124
  if (imageAnalysis != null) imageAnalysis.clearAnalyzer();
113
125
  if (analyzer != null) analyzer.close();
114
126
  if (cameraProvider != null) cameraProvider.unbindAll();
127
+ releaseSuccessBeep();
115
128
  analysisExecutor.shutdownNow();
116
129
  lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY);
117
130
  super.onDestroy();
@@ -123,27 +136,27 @@ public final class BarcodeScanActivity extends Activity
123
136
  }
124
137
 
125
138
  private void createInterface() {
126
- FrameLayout root = new FrameLayout(this);
127
- root.setBackgroundColor(Color.BLACK);
139
+ rootView = new FrameLayout(this);
140
+ rootView.setBackgroundColor(Color.BLACK);
128
141
  previewView = new PreviewView(this);
129
142
  previewView.setImplementationMode(PreviewView.ImplementationMode.COMPATIBLE);
130
143
  previewView.setScaleType(PreviewView.ScaleType.FILL_CENTER);
131
- root.addView(previewView, matchParent());
144
+ rootView.addView(previewView, matchParent());
132
145
 
133
146
  overlayView = new BarcodeOverlayView(this, config.instructionText, config.guideColor,
134
147
  config.localization.text("barcode_searching"));
135
- root.addView(overlayView, matchParent());
148
+ rootView.addView(overlayView, matchParent());
136
149
 
137
150
  if (config.showCloseButton) {
138
- ImageButton close = cameraIconButton(
151
+ closeButton = cameraIconButton(
139
152
  BarcodeIconDrawable.CLOSE, config.localization.text("close_barcode"), false
140
153
  );
141
- close.setOnClickListener(view -> cancelByUser());
154
+ closeButton.setOnClickListener(view -> cancelByUser());
142
155
  FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(dp(52), dp(52));
143
156
  params.gravity = Gravity.TOP | Gravity.START;
144
157
  params.topMargin = dp(12);
145
158
  params.leftMargin = dp(12);
146
- root.addView(close, params);
159
+ rootView.addView(closeButton, params);
147
160
  }
148
161
 
149
162
  if (config.showTorchButton) {
@@ -156,9 +169,9 @@ public final class BarcodeScanActivity extends Activity
156
169
  params.gravity = Gravity.TOP | Gravity.END;
157
170
  params.topMargin = dp(12);
158
171
  params.rightMargin = dp(12);
159
- root.addView(torchButton, params);
172
+ rootView.addView(torchButton, params);
160
173
  }
161
- setContentView(root);
174
+ setContentView(rootView);
162
175
  }
163
176
 
164
177
  private ImageButton cameraIconButton(int icon, String description, boolean active) {
@@ -246,9 +259,26 @@ public final class BarcodeScanActivity extends Activity
246
259
  }
247
260
 
248
261
  @Override
249
- public void onConfirmed(String text, String format) {
262
+ public void onConfirmed(String text, String format, Rect bounds,
263
+ int imageWidth, int imageHeight) {
250
264
  if (completed) return;
251
265
  completed = true;
266
+ if (timeoutRunnable != null) mainHandler.removeCallbacks(timeoutRunnable);
267
+ if (imageAnalysis != null) imageAnalysis.clearAnalyzer();
268
+ Bitmap frozenFrame = previewView.getBitmap();
269
+ if (frozenFrame != null) {
270
+ ImageView frozenView = new ImageView(this);
271
+ frozenView.setImageBitmap(frozenFrame);
272
+ frozenView.setScaleType(ImageView.ScaleType.FIT_XY);
273
+ rootView.addView(frozenView, 1, matchParent());
274
+ }
275
+ if (cameraProvider != null) cameraProvider.unbindAll();
276
+ if (closeButton != null) closeButton.setVisibility(View.GONE);
277
+ if (torchButton != null) torchButton.setVisibility(View.GONE);
278
+ overlayView.showSuccess(bounds, imageWidth, imageHeight,
279
+ config.localization.text("barcode_success"));
280
+ playSuccessBeep();
281
+
252
282
  JSONObject result = new JSONObject();
253
283
  try {
254
284
  result.put("status", "success");
@@ -257,8 +287,10 @@ public final class BarcodeScanActivity extends Activity
257
287
  } catch (Exception ignored) { }
258
288
  Intent intent = new Intent();
259
289
  intent.putExtra(EXTRA_RESULT_JSON, result.toString());
260
- setResult(RESULT_OK, intent);
261
- finish();
290
+ mainHandler.postDelayed(() -> {
291
+ setResult(RESULT_OK, intent);
292
+ finish();
293
+ }, SUCCESS_FEEDBACK_MS);
262
294
  }
263
295
 
264
296
  @Override
@@ -297,6 +329,62 @@ public final class BarcodeScanActivity extends Activity
297
329
  mainHandler.postDelayed(timeoutRunnable, config.timeoutMs);
298
330
  }
299
331
 
332
+ private void playSuccessBeep() {
333
+ if (!config.beepEnabled) return;
334
+ AudioManager audioManager = (AudioManager) getSystemService(AUDIO_SERVICE);
335
+ if (audioManager != null && audioManager.getRingerMode() != AudioManager.RINGER_MODE_NORMAL) {
336
+ return;
337
+ }
338
+ final int sampleRate = 22050;
339
+ final int sampleCount = sampleRate / 10;
340
+ byte[] pcm = new byte[sampleCount * 2];
341
+ for (int index = 0; index < sampleCount; index++) {
342
+ double time = index / (double) sampleRate;
343
+ double remaining = (sampleCount - index) / (double) sampleRate;
344
+ double envelope = Math.min(1d, time / 0.005d) * Math.min(1d, remaining / 0.025d);
345
+ short sample = (short) Math.round(Math.sin(2d * Math.PI * 1800d * time)
346
+ * envelope * Short.MAX_VALUE * 0.55d);
347
+ pcm[index * 2] = (byte) (sample & 0xff);
348
+ pcm[index * 2 + 1] = (byte) ((sample >>> 8) & 0xff);
349
+ }
350
+ try {
351
+ AudioTrack track = new AudioTrack.Builder()
352
+ .setAudioAttributes(new AudioAttributes.Builder()
353
+ .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
354
+ .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
355
+ .build())
356
+ .setAudioFormat(new AudioFormat.Builder()
357
+ .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
358
+ .setSampleRate(sampleRate)
359
+ .setChannelMask(AudioFormat.CHANNEL_OUT_MONO)
360
+ .build())
361
+ .setTransferMode(AudioTrack.MODE_STATIC)
362
+ .setBufferSizeInBytes(pcm.length)
363
+ .build();
364
+ if (track.write(pcm, 0, pcm.length) <= 0) {
365
+ track.release();
366
+ return;
367
+ }
368
+ releaseSuccessBeep();
369
+ successBeep = track;
370
+ track.play();
371
+ mainHandler.postDelayed(() -> {
372
+ if (successBeep == track) releaseSuccessBeep();
373
+ }, 250L);
374
+ } catch (Exception ignored) {
375
+ releaseSuccessBeep();
376
+ }
377
+ }
378
+
379
+ private void releaseSuccessBeep() {
380
+ if (successBeep == null) return;
381
+ try {
382
+ successBeep.stop();
383
+ } catch (Exception ignored) { }
384
+ successBeep.release();
385
+ successBeep = null;
386
+ }
387
+
300
388
  private FrameLayout.LayoutParams matchParent() {
301
389
  return new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
302
390
  ViewGroup.LayoutParams.MATCH_PARENT);
@@ -43,6 +43,7 @@ final class BarcodeScanConfig {
43
43
  final int[] formatValues;
44
44
  final boolean showCloseButton;
45
45
  final boolean showTorchButton;
46
+ final boolean beepEnabled;
46
47
  final String instructionText;
47
48
  final MlKitLocalization localization;
48
49
  final Integer guideColor;
@@ -50,6 +51,7 @@ final class BarcodeScanConfig {
50
51
 
51
52
  private BarcodeScanConfig(List<String> formatNames,
52
53
  boolean showCloseButton, boolean showTorchButton,
54
+ boolean beepEnabled,
53
55
  String instructionText, MlKitLocalization localization,
54
56
  Integer guideColor, long timeoutMs) {
55
57
  this.formatNames = Collections.unmodifiableList(formatNames);
@@ -59,6 +61,7 @@ final class BarcodeScanConfig {
59
61
  }
60
62
  this.showCloseButton = showCloseButton;
61
63
  this.showTorchButton = showTorchButton;
64
+ this.beepEnabled = beepEnabled;
62
65
  this.instructionText = instructionText;
63
66
  this.localization = localization;
64
67
  this.guideColor = guideColor;
@@ -72,11 +75,12 @@ final class BarcodeScanConfig {
72
75
  List<String> formats = parseFormats(value.opt("formats"));
73
76
  boolean close = strictBoolean(value, "showCloseButton", true);
74
77
  boolean torch = strictBoolean(value, "showTorchButton", true);
78
+ boolean beep = strictBoolean(value, "beepEnabled", true);
75
79
  String instruction = strictString(value, "instructionText",
76
80
  localization.text("barcode_instruction"));
77
81
  Integer guideColor = strictColor(value, "guideColor");
78
82
  long timeout = strictLong(value, "timeoutMs", 0L, 0L, 600000L);
79
- return new BarcodeScanConfig(formats, close, torch, instruction, localization,
83
+ return new BarcodeScanConfig(formats, close, torch, beep, instruction, localization,
80
84
  guideColor, timeout);
81
85
  }
82
86
 
@@ -146,6 +150,7 @@ final class BarcodeScanConfig {
146
150
  String key = keys.next();
147
151
  if (!("formats".equals(key)
148
152
  || "showCloseButton".equals(key) || "showTorchButton".equals(key)
153
+ || "beepEnabled".equals(key)
149
154
  || "instructionText".equals(key) || "guideColor".equals(key)
150
155
  || "timeoutMs".equals(key)
151
156
  || "moduleLoadingMessage".equals(key) || "locale".equals(key))) {
@@ -17,17 +17,35 @@ final class BarcodeScanRegion {
17
17
  float barcodeRight,
18
18
  float barcodeBottom
19
19
  ) {
20
- if (viewWidth <= 0 || viewHeight <= 0 || imageWidth <= 0 || imageHeight <= 0) {
21
- return false;
20
+ float[] mapped = map(viewWidth, viewHeight, imageWidth, imageHeight,
21
+ barcodeLeft, barcodeTop, barcodeRight, barcodeBottom);
22
+ return mapped != null
23
+ && mapped[0] >= guideLeft && mapped[1] >= guideTop
24
+ && mapped[2] <= guideRight && mapped[3] <= guideBottom;
25
+ }
26
+
27
+ static float[] map(
28
+ float viewWidth,
29
+ float viewHeight,
30
+ int imageWidth,
31
+ int imageHeight,
32
+ float barcodeLeft,
33
+ float barcodeTop,
34
+ float barcodeRight,
35
+ float barcodeBottom
36
+ ) {
37
+ if (viewWidth <= 0 || viewHeight <= 0 || imageWidth <= 0 || imageHeight <= 0
38
+ || barcodeRight <= barcodeLeft || barcodeBottom <= barcodeTop) {
39
+ return null;
22
40
  }
23
41
  float scale = Math.max(viewWidth / imageWidth, viewHeight / imageHeight);
24
42
  float offsetX = (viewWidth - imageWidth * scale) / 2f;
25
43
  float offsetY = (viewHeight - imageHeight * scale) / 2f;
26
- float left = barcodeLeft * scale + offsetX;
27
- float top = barcodeTop * scale + offsetY;
28
- float right = barcodeRight * scale + offsetX;
29
- float bottom = barcodeBottom * scale + offsetY;
30
- return left >= guideLeft && top >= guideTop
31
- && right <= guideRight && bottom <= guideBottom;
44
+ return new float[] {
45
+ barcodeLeft * scale + offsetX,
46
+ barcodeTop * scale + offsetY,
47
+ barcodeRight * scale + offsetX,
48
+ barcodeBottom * scale + offsetY
49
+ };
32
50
  }
33
51
  }
@@ -14,6 +14,9 @@ import com.google.mlkit.vision.barcode.BarcodeScanning;
14
14
  import com.google.mlkit.vision.face.FaceDetection;
15
15
  import com.google.mlkit.vision.face.FaceDetector;
16
16
  import com.google.mlkit.vision.face.FaceDetectorOptions;
17
+ import com.google.mlkit.vision.label.ImageLabeler;
18
+ import com.google.mlkit.vision.label.ImageLabeling;
19
+ import com.google.mlkit.vision.label.defaults.ImageLabelerOptions;
17
20
  import com.google.mlkit.vision.text.TextRecognition;
18
21
  import com.google.mlkit.vision.text.TextRecognizer;
19
22
  import com.google.mlkit.vision.text.latin.TextRecognizerOptions;
@@ -38,10 +41,11 @@ final class MlKitModelsModuleInstaller {
38
41
  private MlKitModelsModuleInstaller() { }
39
42
 
40
43
  static void checkAvailability(Activity activity, boolean includeFace, boolean includeBarcode,
41
- boolean includeText, AvailabilityCallback callback) {
44
+ boolean includeText, boolean includeImageLabeling,
45
+ AvailabilityCallback callback) {
42
46
  JSONObject result = new JSONObject();
43
47
  int requestedCount = (includeFace ? 1 : 0) + (includeBarcode ? 1 : 0)
44
- + (includeText ? 1 : 0);
48
+ + (includeText ? 1 : 0) + (includeImageLabeling ? 1 : 0);
45
49
  if (requestedCount == 0) {
46
50
  callback.onResult(result);
47
51
  return;
@@ -64,6 +68,13 @@ final class MlKitModelsModuleInstaller {
64
68
  checkOne(client, "text", (OptionalModuleApi) text, text::close,
65
69
  result, remaining, callback);
66
70
  }
71
+ if (includeImageLabeling) {
72
+ ImageLabeler imageLabeling = ImageLabeling.getClient(
73
+ ImageLabelerOptions.DEFAULT_OPTIONS
74
+ );
75
+ checkOne(client, "imageLabeling", (OptionalModuleApi) imageLabeling,
76
+ imageLabeling::close, result, remaining, callback);
77
+ }
67
78
  }
68
79
 
69
80
  private static void checkOne(ModuleInstallClient client, String name, OptionalModuleApi module,
@@ -95,36 +106,39 @@ final class MlKitModelsModuleInstaller {
95
106
  }
96
107
 
97
108
  static void prepare(Activity activity, boolean includeFace, boolean includeBarcode,
98
- boolean includeText, String loadingMessage,
109
+ boolean includeText, boolean includeImageLabeling, String loadingMessage,
99
110
  MlKitLocalization localization, Callback callback) {
100
111
  final FaceDetector face = includeFace
101
112
  ? FaceDetection.getClient(new FaceDetectorOptions.Builder().build()) : null;
102
113
  final BarcodeScanner barcode = includeBarcode ? BarcodeScanning.getClient() : null;
103
114
  final TextRecognizer text = includeText
104
115
  ? TextRecognition.getClient(TextRecognizerOptions.DEFAULT_OPTIONS) : null;
116
+ final ImageLabeler imageLabeling = includeImageLabeling
117
+ ? ImageLabeling.getClient(ImageLabelerOptions.DEFAULT_OPTIONS) : null;
105
118
  List<OptionalModuleApi> selectedModules = new ArrayList<>();
106
119
  if (face != null) selectedModules.add((OptionalModuleApi) face);
107
120
  if (barcode != null) selectedModules.add((OptionalModuleApi) barcode);
108
121
  if (text != null) selectedModules.add((OptionalModuleApi) text);
122
+ if (imageLabeling != null) selectedModules.add((OptionalModuleApi) imageLabeling);
109
123
  OptionalModuleApi[] modules = selectedModules.toArray(new OptionalModuleApi[0]);
110
124
  ModuleInstallClient client = ModuleInstall.getClient(activity);
111
125
 
112
126
  client.areModulesAvailable(modules)
113
127
  .addOnSuccessListener(response -> {
114
128
  if (response.areModulesAvailable()) {
115
- close(face, barcode, text);
129
+ close(face, barcode, text, imageLabeling);
116
130
  callback.onReady(readyResult(true));
117
131
  } else {
118
132
  MlKitLoadingDialog loadingDialog = new MlKitLoadingDialog(
119
133
  activity, loadingMessage
120
134
  );
121
135
  loadingDialog.show();
122
- install(client, modules, face, barcode, text, loadingDialog,
136
+ install(client, modules, face, barcode, text, imageLabeling, loadingDialog,
123
137
  localization, callback);
124
138
  }
125
139
  })
126
140
  .addOnFailureListener(error -> {
127
- close(face, barcode, text);
141
+ close(face, barcode, text, imageLabeling);
128
142
  callback.onError(MlKitSuitePlugin.error(
129
143
  "MODULE_DOWNLOAD_FAILED",
130
144
  localization.text("models_check_failed"),
@@ -135,6 +149,7 @@ final class MlKitModelsModuleInstaller {
135
149
 
136
150
  private static void install(ModuleInstallClient client, OptionalModuleApi[] modules,
137
151
  FaceDetector face, BarcodeScanner barcode, TextRecognizer text,
152
+ ImageLabeler imageLabeling,
138
153
  MlKitLoadingDialog loadingDialog,
139
154
  MlKitLocalization localization, Callback callback) {
140
155
  AtomicBoolean finished = new AtomicBoolean(false);
@@ -142,16 +157,16 @@ final class MlKitModelsModuleInstaller {
142
157
  listenerHolder[0] = update -> {
143
158
  int state = update.getInstallState();
144
159
  if (state == ModuleInstallStatusUpdate.InstallState.STATE_COMPLETED) {
145
- finishSuccess(client, listenerHolder[0], face, barcode, text,
160
+ finishSuccess(client, listenerHolder[0], face, barcode, text, imageLabeling,
146
161
  loadingDialog, callback, finished, false);
147
162
  } else if (state == ModuleInstallStatusUpdate.InstallState.STATE_CANCELED) {
148
- finishError(client, listenerHolder[0], face, barcode, text, loadingDialog,
163
+ finishError(client, listenerHolder[0], face, barcode, text, imageLabeling, loadingDialog,
149
164
  callback, finished,
150
165
  "MODULE_DOWNLOAD_CANCELLED",
151
166
  localization.text("models_download_cancelled"));
152
167
  } else if (state == ModuleInstallStatusUpdate.InstallState.STATE_FAILED) {
153
168
  String status = ModuleInstallStatusCodes.getStatusCodeString(update.getErrorCode());
154
- finishError(client, listenerHolder[0], face, barcode, text, loadingDialog,
169
+ finishError(client, listenerHolder[0], face, barcode, text, imageLabeling, loadingDialog,
155
170
  callback, finished,
156
171
  "MODULE_DOWNLOAD_FAILED",
157
172
  localization.format("models_download_failed", status));
@@ -164,44 +179,48 @@ final class MlKitModelsModuleInstaller {
164
179
  client.installModules(request)
165
180
  .addOnSuccessListener(response -> {
166
181
  if (response.areModulesAlreadyInstalled()) {
167
- finishSuccess(client, listenerHolder[0], face, barcode, text,
182
+ finishSuccess(client, listenerHolder[0], face, barcode, text, imageLabeling,
168
183
  loadingDialog, callback, finished, true);
169
184
  }
170
185
  })
171
186
  .addOnFailureListener(error -> finishError(
172
- client, listenerHolder[0], face, barcode, text, loadingDialog, callback, finished,
187
+ client, listenerHolder[0], face, barcode, text, imageLabeling, loadingDialog, callback, finished,
173
188
  "MODULE_DOWNLOAD_FAILED", localization.text("models_download_start_failed")
174
189
  ));
175
190
  }
176
191
 
177
192
  private static void finishSuccess(ModuleInstallClient client, InstallStatusListener listener,
178
193
  FaceDetector face, BarcodeScanner barcode,
179
- TextRecognizer text, MlKitLoadingDialog loadingDialog,
194
+ TextRecognizer text, ImageLabeler imageLabeling,
195
+ MlKitLoadingDialog loadingDialog,
180
196
  Callback callback,
181
197
  AtomicBoolean finished, boolean alreadyAvailable) {
182
198
  if (!finished.compareAndSet(false, true)) return;
183
199
  client.unregisterListener(listener);
184
200
  loadingDialog.dismiss();
185
- close(face, barcode, text);
201
+ close(face, barcode, text, imageLabeling);
186
202
  callback.onReady(readyResult(alreadyAvailable));
187
203
  }
188
204
 
189
205
  private static void finishError(ModuleInstallClient client, InstallStatusListener listener,
190
206
  FaceDetector face, BarcodeScanner barcode,
191
- TextRecognizer text, MlKitLoadingDialog loadingDialog,
207
+ TextRecognizer text, ImageLabeler imageLabeling,
208
+ MlKitLoadingDialog loadingDialog,
192
209
  Callback callback,
193
210
  AtomicBoolean finished, String code, String message) {
194
211
  if (!finished.compareAndSet(false, true)) return;
195
212
  client.unregisterListener(listener);
196
213
  loadingDialog.dismiss();
197
- close(face, barcode, text);
214
+ close(face, barcode, text, imageLabeling);
198
215
  callback.onError(MlKitSuitePlugin.error(code, message, true));
199
216
  }
200
217
 
201
- private static void close(FaceDetector face, BarcodeScanner barcode, TextRecognizer text) {
218
+ private static void close(FaceDetector face, BarcodeScanner barcode, TextRecognizer text,
219
+ ImageLabeler imageLabeling) {
202
220
  if (face != null) face.close();
203
221
  if (barcode != null) barcode.close();
204
222
  if (text != null) text.close();
223
+ if (imageLabeling != null) imageLabeling.close();
205
224
  }
206
225
 
207
226
  private static JSONObject readyResult(boolean alreadyAvailable) {