capacitor-card-scanner 0.3.0 → 0.3.2

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.
package/README.md CHANGED
@@ -328,17 +328,19 @@ requestPermissions() => Promise<PermissionStatus>
328
328
 
329
329
  #### DetectionOptions
330
330
 
331
- | Prop | Type | Description | Default |
332
- | --------------------- | -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------- |
333
- | **`intervalMs`** | <code>number</code> | Minimum time between two OCR runs, in milliseconds. The red rectangle is updated on every frame regardless. | <code>300</code> |
334
- | **`stableFrames`** | <code>number</code> | How many consecutive OCR runs must read the same card before `cardDetected` fires. | <code>2</code> |
335
- | **`includeImage`** | <code>boolean</code> | Include a JPEG of the detected card in the event. | <code>false</code> |
336
- | **`cardAspectRatio`** | <code>number</code> | Expected width / height of an upright card. Only upright cards are detected; a card lying sideways falls outside the tolerance. | <code>0.716 (63 x 88 mm)</code> |
337
- | **`aspectTolerance`** | <code>number</code> | Allowed relative deviation from `cardAspectRatio` (0.15 = ±15%). Increase it if tilted cards are not detected. | <code>0.15</code> |
338
- | **`minCardArea`** | <code>number</code> | Smallest card size, as a fraction of the camera frame (0-1). | <code>0.08</code> |
339
- | **`maxCardArea`** | <code>number</code> | Largest card size, as a fraction of the camera frame (0-1). | <code>0.9</code> |
340
- | **`requireCard`** | <code>boolean</code> | Only read text when a card-shaped object is visible. When false, the whole frame is read if no card is found. | <code>true</code> |
341
- | **`debug`** | <code>boolean</code> | Draw every object found, labelled with its area (`a`) and width/height ratio (`r`): red when accepted as a card, grey when rejected (with the filter that rejected it). Use it to tune the filters above. | <code>false</code> |
331
+ | Prop | Type | Description | Default |
332
+ | --------------------- | -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------- |
333
+ | **`intervalMs`** | <code>number</code> | Minimum time between two OCR runs, in milliseconds. The red rectangle is updated on every frame regardless. | <code>300</code> |
334
+ | **`stableFrames`** | <code>number</code> | How many consecutive OCR runs must read the same card before `cardDetected` fires. | <code>2</code> |
335
+ | **`includeImage`** | <code>boolean</code> | Include a JPEG of the detected card in the event. | <code>false</code> |
336
+ | **`cardAspectRatio`** | <code>number</code> | Expected width / height of an upright card. Only upright cards are detected; a card lying sideways falls outside the tolerance. | <code>0.716 (63 x 88 mm)</code> |
337
+ | **`aspectTolerance`** | <code>number</code> | Allowed relative deviation from `cardAspectRatio` (0.15 = ±15%). Increase it if tilted cards are not detected. | <code>0.15</code> |
338
+ | **`minCardArea`** | <code>number</code> | Smallest card size, as a fraction of the camera frame (0-1). | <code>0.08</code> |
339
+ | **`maxCardArea`** | <code>number</code> | Largest card size, as a fraction of the camera frame (0-1). | <code>0.9</code> |
340
+ | **`requireCard`** | <code>boolean</code> | Only read text when a card-shaped object is visible. When false, the whole frame is read if no card is found. | <code>true</code> |
341
+ | **`debug`** | <code>boolean</code> | Every object found is always outlined: red when accepted as a card, grey when rejected. With `debug`, each outline is also labelled with its area (`a`), width/height ratio (`r`) and the filter that rejected it. Use it to tune the filters above. | <code>false</code> |
342
+ | **`showRejected`** | <code>boolean</code> | Outline (in grey) the objects that were not accepted as a card. The accepted card is always outlined in red. | <code>true</code> |
343
+ | **`repeatAfterMs`** | <code>number</code> | How long (ms) the card must be out of view before the same card can be reported again. While a card stays in view it is reported only once. | <code>1500</code> |
342
344
 
343
345
 
344
346
  #### PluginListenerHandle
@@ -25,7 +25,6 @@ import com.google.mlkit.vision.text.TextRecognizer;
25
25
  import com.google.mlkit.vision.text.latin.TextRecognizerOptions;
26
26
  import java.io.ByteArrayOutputStream;
27
27
  import java.util.ArrayList;
28
- import java.util.Collections;
29
28
  import java.util.List;
30
29
  import java.util.Locale;
31
30
  import java.util.regex.Matcher;
@@ -61,8 +60,12 @@ public class CardDetector implements ImageAnalysis.Analyzer {
61
60
  public float maxCardArea = 0.9f;
62
61
  /** Only run OCR when a card-shaped object is visible. */
63
62
  public boolean requireCard = true;
64
- /** Report every detected object (with its area/ratio) so the overlay can show why it was rejected. */
63
+ /** Label every detected object with its area/ratio and the filter that rejected it. */
65
64
  public boolean debug = false;
65
+ /** Outline (in grey) the objects that were not accepted as a card. */
66
+ public boolean showRejected = true;
67
+ /** How long the card must be out of view before the same card can be reported again. */
68
+ public long repeatAfterMs = 1500;
66
69
  }
67
70
 
68
71
  /** An object found by ML Kit, with the values the card filters were checked against. */
@@ -94,7 +97,7 @@ public class CardDetector implements ImageAnalysis.Analyzer {
94
97
  public interface BoxListener {
95
98
  /**
96
99
  * @param card card bounds normalized (0-1) to the upright frame, or null when no card is visible
97
- * @param candidates every detected object; empty unless debug is on
100
+ * @param candidates every detected object, accepted or not
98
101
  */
99
102
  void onDetections(@Nullable RectF card, List<Candidate> candidates, int frameWidth, int frameHeight);
100
103
  }
@@ -107,8 +110,8 @@ public class CardDetector implements ImageAnalysis.Analyzer {
107
110
  );
108
111
  private static final Pattern HP_SUFFIX = Pattern.compile("\\s*HP\\s*\\d+\\s*$", Pattern.CASE_INSENSITIVE);
109
112
  private static final Pattern LETTER = Pattern.compile("\\p{L}");
110
- /** After this long without reading a card, the same card can be reported again. */
111
- private static final long RESET_AFTER_MS = 1500;
113
+ /** Names at least this similar are treated as the same card. */
114
+ private static final float SAME_NAME_SIMILARITY = 0.8f;
112
115
  /** Extra margin around the detected card before running OCR. */
113
116
  private static final float CROP_MARGIN = 0.04f;
114
117
 
@@ -125,7 +128,10 @@ public class CardDetector implements ImageAnalysis.Analyzer {
125
128
  private long lastSeenAt;
126
129
  private String candidateKey;
127
130
  private int candidateCount;
128
- private String lastEmittedKey;
131
+ // Last reported card. A reading is the same card if either its name or its
132
+ // number matches, because OCR does not read the number on every frame.
133
+ private String lastEmittedName;
134
+ private String lastEmittedNumber;
129
135
 
130
136
  public CardDetector(Options options, Listener listener) {
131
137
  this.options = options;
@@ -133,6 +139,14 @@ public class CardDetector implements ImageAnalysis.Analyzer {
133
139
  this.listener = listener;
134
140
  }
135
141
 
142
+ public boolean isDebug() {
143
+ return options.debug;
144
+ }
145
+
146
+ public boolean isShowRejected() {
147
+ return options.showRejected;
148
+ }
149
+
136
150
  public void setBoxListener(BoxListener boxListener) {
137
151
  this.boxListener = boxListener;
138
152
  }
@@ -157,9 +171,12 @@ public class CardDetector implements ImageAnalysis.Analyzer {
157
171
 
158
172
  try {
159
173
  List<DetectedObject> objects = Tasks.await(objectDetector.process(InputImage.fromBitmap(upright, 0)));
160
- List<Candidate> candidates = options.debug ? new ArrayList<>() : Collections.emptyList();
174
+ List<Candidate> candidates = new ArrayList<>();
161
175
  Rect card = pickCard(objects, frameW, frameH, candidates);
162
176
  RectF cardBox = card == null ? null : normalize(card, frameW, frameH);
177
+ // The card counts as "in view" while its outline is visible, even if
178
+ // its text is not readable on this frame.
179
+ if (card != null) lastSeenAt = now;
163
180
  BoxListener bl = boxListener;
164
181
  if (bl != null) bl.onDetections(cardBox, candidates, frameW, frameH);
165
182
 
@@ -181,7 +198,7 @@ public class CardDetector implements ImageAnalysis.Analyzer {
181
198
 
182
199
  /**
183
200
  * The biggest detected object with the size and upright proportions of a card.
184
- * When debug is on, every object is added to {@code candidates}.
201
+ * Every object, accepted or not, is added to {@code candidates}.
185
202
  */
186
203
  @Nullable
187
204
  private Rect pickCard(List<DetectedObject> objects, int frameW, int frameH, List<Candidate> candidates) {
@@ -203,9 +220,7 @@ public class CardDetector implements ImageAnalysis.Analyzer {
203
220
  } else if (ratio < minRatio || ratio > maxRatio) {
204
221
  rejectedBy = "ratio";
205
222
  }
206
- if (options.debug) {
207
- candidates.add(new Candidate(normalize(r, frameW, frameH), fraction, ratio, rejectedBy));
208
- }
223
+ candidates.add(new Candidate(normalize(r, frameW, frameH), fraction, ratio, rejectedBy));
209
224
  if (rejectedBy != null) continue;
210
225
 
211
226
  if (area > bestArea) {
@@ -280,8 +295,9 @@ public class CardDetector implements ImageAnalysis.Analyzer {
280
295
  candidateKey = key;
281
296
  candidateCount = 1;
282
297
  }
283
- if (candidateCount < options.stableFrames || key.equals(lastEmittedKey)) return;
284
- lastEmittedKey = key;
298
+ if (candidateCount < options.stableFrames || isLastEmitted(name, number)) return;
299
+ lastEmittedName = name;
300
+ lastEmittedNumber = number;
285
301
 
286
302
  JSObject result = new JSObject();
287
303
  result.put("name", name);
@@ -302,7 +318,33 @@ public class CardDetector implements ImageAnalysis.Analyzer {
302
318
  private void noCardRead(long now) {
303
319
  candidateKey = null;
304
320
  candidateCount = 0;
305
- if (now - lastSeenAt > RESET_AFTER_MS) lastEmittedKey = null;
321
+ if (now - lastSeenAt > options.repeatAfterMs) {
322
+ lastEmittedName = null;
323
+ lastEmittedNumber = null;
324
+ }
325
+ }
326
+
327
+ private boolean isLastEmitted(String name, String number) {
328
+ if (number != null && number.equals(lastEmittedNumber)) return true;
329
+ return name != null && lastEmittedName != null && similarity(name, lastEmittedName) >= SAME_NAME_SIMILARITY;
330
+ }
331
+
332
+ /** 0-1 similarity (LCS based), so an OCR slip like "Evolutlon" still matches "Evolution". */
333
+ private static float similarity(String a, String b) {
334
+ a = a.toLowerCase(Locale.ROOT);
335
+ b = b.toLowerCase(Locale.ROOT);
336
+ if (a.equals(b)) return 1f;
337
+ int[] prev = new int[b.length() + 1];
338
+ int[] cur = new int[b.length() + 1];
339
+ for (int i = 1; i <= a.length(); i++) {
340
+ for (int j = 1; j <= b.length(); j++) {
341
+ cur[j] = a.charAt(i - 1) == b.charAt(j - 1) ? prev[j - 1] + 1 : Math.max(prev[j], cur[j - 1]);
342
+ }
343
+ int[] tmp = prev;
344
+ prev = cur;
345
+ cur = tmp;
346
+ }
347
+ return 2f * prev[b.length()] / (a.length() + b.length());
306
348
  }
307
349
 
308
350
  private static JSObject box(float x, float y, float width, float height) {
@@ -151,6 +151,8 @@ public class CardScanner {
151
151
  closeDetector();
152
152
  detector = newDetector;
153
153
  DetectionOverlay target = overlay;
154
+ target.setShowLabels(newDetector.isDebug());
155
+ target.setShowRejected(newDetector.isShowRejected());
154
156
  detector.setBoxListener((card, candidates, frameW, frameH) ->
155
157
  target.post(() -> {
156
158
  // Ignore frames that arrive after detection was stopped or restarted.
@@ -101,6 +101,8 @@ public class CardScannerPlugin extends Plugin {
101
101
  options.maxCardArea = call.getFloat("maxCardArea", options.maxCardArea);
102
102
  options.requireCard = call.getBoolean("requireCard", options.requireCard);
103
103
  options.debug = call.getBoolean("debug", options.debug);
104
+ options.showRejected = call.getBoolean("showRejected", options.showRejected);
105
+ options.repeatAfterMs = call.getInt("repeatAfterMs", (int) options.repeatAfterMs);
104
106
 
105
107
  CardDetector detector = new CardDetector(options, result -> notifyListeners("cardDetected", result));
106
108
  getActivity().runOnUiThread(() -> {
@@ -1,114 +1,127 @@
1
- package pt.tcgclassifier.cardscanner;
2
-
3
- import android.content.Context;
4
- import android.graphics.Canvas;
5
- import android.graphics.Color;
6
- import android.graphics.Paint;
7
- import android.graphics.RectF;
8
- import android.os.SystemClock;
9
- import android.view.View;
10
- import androidx.annotation.Nullable;
11
- import java.util.Collections;
12
- import java.util.List;
13
-
14
- /**
15
- * Draws a red rectangle around the detected card. Sits between the camera
16
- * preview and the (transparent) WebView, and maps frame coordinates the same
17
- * way PreviewView's FILL_CENTER does. In debug mode it also draws every object
18
- * ML Kit found, labelled with its area/ratio (grey when rejected).
19
- */
20
- public class DetectionOverlay extends View {
21
-
22
- /** Keep the rectangle this long after the card was last seen, to avoid flicker. */
23
- private static final long HIDE_AFTER_MS = 300;
24
-
25
- private final Paint cardPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
26
- private final Paint rejectedPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
27
- private final Paint labelPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
28
- private final float radius;
29
- private final float labelPadding;
30
- private final RectF drawRect = new RectF();
31
-
32
- @Nullable
33
- private RectF card;
34
- private List<CardDetector.Candidate> candidates = Collections.emptyList();
35
- private int frameWidth;
36
- private int frameHeight;
37
- private boolean mirror;
38
- private long lastCardAt;
39
-
40
- public DetectionOverlay(Context context) {
41
- super(context);
42
- float density = context.getResources().getDisplayMetrics().density;
43
- radius = 8 * density;
44
- labelPadding = 4 * density;
45
-
46
- cardPaint.setColor(Color.RED);
47
- cardPaint.setStyle(Paint.Style.STROKE);
48
- cardPaint.setStrokeWidth(3 * density);
49
-
50
- rejectedPaint.setColor(Color.argb(200, 180, 180, 180));
51
- rejectedPaint.setStyle(Paint.Style.STROKE);
52
- rejectedPaint.setStrokeWidth(2 * density);
53
-
54
- labelPaint.setColor(Color.WHITE);
55
- labelPaint.setTextSize(13 * density);
56
- labelPaint.setShadowLayer(3 * density, 0, 0, Color.BLACK);
57
- }
58
-
59
- public void setMirror(boolean mirror) {
60
- this.mirror = mirror;
61
- }
62
-
63
- /** Must be called on the UI thread. */
64
- public void setDetections(@Nullable RectF card, List<CardDetector.Candidate> candidates, int frameWidth, int frameHeight) {
65
- long now = SystemClock.uptimeMillis();
66
- if (card != null) {
67
- lastCardAt = now;
68
- this.card = card;
69
- } else if (now - lastCardAt >= HIDE_AFTER_MS) {
70
- this.card = null;
71
- }
72
- this.candidates = candidates;
73
- this.frameWidth = frameWidth;
74
- this.frameHeight = frameHeight;
75
- invalidate();
76
- }
77
-
78
- public void clear() {
79
- card = null;
80
- candidates = Collections.emptyList();
81
- invalidate();
82
- }
83
-
84
- @Override
85
- protected void onDraw(Canvas canvas) {
86
- if (frameWidth == 0 || frameHeight == 0) return;
87
-
88
- for (CardDetector.Candidate c : candidates) {
89
- map(c.box);
90
- canvas.drawRoundRect(drawRect, radius, radius, c.rejectedBy == null ? cardPaint : rejectedPaint);
91
- canvas.drawText(c.label(), drawRect.left + labelPadding, drawRect.top - labelPadding, labelPaint);
92
- }
93
-
94
- if (card != null) {
95
- map(card);
96
- canvas.drawRoundRect(drawRect, radius, radius, cardPaint);
97
- }
98
- }
99
-
100
- /** Maps a normalized frame rectangle into drawRect, in view coordinates. */
101
- private void map(RectF box) {
102
- float vw = getWidth();
103
- float vh = getHeight();
104
- float scale = Math.max(vw / frameWidth, vh / frameHeight);
105
- float sw = frameWidth * scale;
106
- float sh = frameHeight * scale;
107
- float dx = (vw - sw) / 2f;
108
- float dy = (vh - sh) / 2f;
109
-
110
- float left = mirror ? 1f - box.right : box.left;
111
- float right = mirror ? 1f - box.left : box.right;
112
- drawRect.set(dx + left * sw, dy + box.top * sh, dx + right * sw, dy + box.bottom * sh);
113
- }
114
- }
1
+ package pt.tcgclassifier.cardscanner;
2
+
3
+ import android.content.Context;
4
+ import android.graphics.Canvas;
5
+ import android.graphics.Color;
6
+ import android.graphics.Paint;
7
+ import android.graphics.RectF;
8
+ import android.os.SystemClock;
9
+ import android.view.View;
10
+ import androidx.annotation.Nullable;
11
+ import java.util.Collections;
12
+ import java.util.List;
13
+
14
+ /**
15
+ * Draws a red rectangle around the detected card. Sits between the camera
16
+ * preview and the (transparent) WebView, and maps frame coordinates the same
17
+ * way PreviewView's FILL_CENTER does. Objects that are not accepted as a card
18
+ * are drawn in grey; with labels on, each one shows its area/ratio.
19
+ */
20
+ public class DetectionOverlay extends View {
21
+
22
+ /** Keep the rectangle this long after the card was last seen, to avoid flicker. */
23
+ private static final long HIDE_AFTER_MS = 300;
24
+
25
+ private final Paint cardPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
26
+ private final Paint rejectedPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
27
+ private final Paint labelPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
28
+ private final float radius;
29
+ private final float labelPadding;
30
+ private final RectF drawRect = new RectF();
31
+
32
+ @Nullable
33
+ private RectF card;
34
+ private List<CardDetector.Candidate> candidates = Collections.emptyList();
35
+ private int frameWidth;
36
+ private int frameHeight;
37
+ private boolean mirror;
38
+ private boolean showLabels;
39
+ private boolean showRejected = true;
40
+ private long lastCardAt;
41
+
42
+ public DetectionOverlay(Context context) {
43
+ super(context);
44
+ float density = context.getResources().getDisplayMetrics().density;
45
+ radius = 8 * density;
46
+ labelPadding = 4 * density;
47
+
48
+ cardPaint.setColor(Color.RED);
49
+ cardPaint.setStyle(Paint.Style.STROKE);
50
+ cardPaint.setStrokeWidth(3 * density);
51
+
52
+ rejectedPaint.setColor(Color.argb(200, 180, 180, 180));
53
+ rejectedPaint.setStyle(Paint.Style.STROKE);
54
+ rejectedPaint.setStrokeWidth(2 * density);
55
+
56
+ labelPaint.setColor(Color.WHITE);
57
+ labelPaint.setTextSize(13 * density);
58
+ labelPaint.setShadowLayer(3 * density, 0, 0, Color.BLACK);
59
+ }
60
+
61
+ public void setMirror(boolean mirror) {
62
+ this.mirror = mirror;
63
+ }
64
+
65
+ public void setShowLabels(boolean showLabels) {
66
+ this.showLabels = showLabels;
67
+ }
68
+
69
+ public void setShowRejected(boolean showRejected) {
70
+ this.showRejected = showRejected;
71
+ }
72
+
73
+ /** Must be called on the UI thread. */
74
+ public void setDetections(@Nullable RectF card, List<CardDetector.Candidate> candidates, int frameWidth, int frameHeight) {
75
+ long now = SystemClock.uptimeMillis();
76
+ if (card != null) {
77
+ lastCardAt = now;
78
+ this.card = card;
79
+ } else if (now - lastCardAt >= HIDE_AFTER_MS) {
80
+ this.card = null;
81
+ }
82
+ this.candidates = candidates;
83
+ this.frameWidth = frameWidth;
84
+ this.frameHeight = frameHeight;
85
+ invalidate();
86
+ }
87
+
88
+ public void clear() {
89
+ card = null;
90
+ candidates = Collections.emptyList();
91
+ invalidate();
92
+ }
93
+
94
+ @Override
95
+ protected void onDraw(Canvas canvas) {
96
+ if (frameWidth == 0 || frameHeight == 0) return;
97
+
98
+ for (CardDetector.Candidate c : candidates) {
99
+ if (c.rejectedBy != null && !showRejected) continue;
100
+ map(c.box);
101
+ canvas.drawRoundRect(drawRect, radius, radius, c.rejectedBy == null ? cardPaint : rejectedPaint);
102
+ if (showLabels) {
103
+ canvas.drawText(c.label(), drawRect.left + labelPadding, drawRect.top - labelPadding, labelPaint);
104
+ }
105
+ }
106
+
107
+ if (card != null) {
108
+ map(card);
109
+ canvas.drawRoundRect(drawRect, radius, radius, cardPaint);
110
+ }
111
+ }
112
+
113
+ /** Maps a normalized frame rectangle into drawRect, in view coordinates. */
114
+ private void map(RectF box) {
115
+ float vw = getWidth();
116
+ float vh = getHeight();
117
+ float scale = Math.max(vw / frameWidth, vh / frameHeight);
118
+ float sw = frameWidth * scale;
119
+ float sh = frameHeight * scale;
120
+ float dx = (vw - sw) / 2f;
121
+ float dy = (vh - sh) / 2f;
122
+
123
+ float left = mirror ? 1f - box.right : box.left;
124
+ float right = mirror ? 1f - box.left : box.right;
125
+ drawRect.set(dx + left * sw, dy + box.top * sh, dx + right * sw, dy + box.bottom * sh);
126
+ }
127
+ }
package/dist/docs.json CHANGED
@@ -405,9 +405,33 @@
405
405
  "name": "default"
406
406
  }
407
407
  ],
408
- "docs": "Draw every object found, labelled with its area (`a`) and width/height\nratio (`r`): red when accepted as a card, grey when rejected (with the\nfilter that rejected it). Use it to tune the filters above.",
408
+ "docs": "Every object found is always outlined: red when accepted as a card, grey\nwhen rejected. With `debug`, each outline is also labelled with its area\n(`a`), width/height ratio (`r`) and the filter that rejected it. Use it to\ntune the filters above.",
409
409
  "complexTypes": [],
410
410
  "type": "boolean | undefined"
411
+ },
412
+ {
413
+ "name": "showRejected",
414
+ "tags": [
415
+ {
416
+ "text": "true",
417
+ "name": "default"
418
+ }
419
+ ],
420
+ "docs": "Outline (in grey) the objects that were not accepted as a card.\nThe accepted card is always outlined in red.",
421
+ "complexTypes": [],
422
+ "type": "boolean | undefined"
423
+ },
424
+ {
425
+ "name": "repeatAfterMs",
426
+ "tags": [
427
+ {
428
+ "text": "1500",
429
+ "name": "default"
430
+ }
431
+ ],
432
+ "docs": "How long (ms) the card must be out of view before the same card can be\nreported again. While a card stays in view it is reported only once.",
433
+ "complexTypes": [],
434
+ "type": "number | undefined"
411
435
  }
412
436
  ]
413
437
  },
@@ -77,12 +77,25 @@ export interface DetectionOptions {
77
77
  */
78
78
  requireCard?: boolean;
79
79
  /**
80
- * Draw every object found, labelled with its area (`a`) and width/height
81
- * ratio (`r`): red when accepted as a card, grey when rejected (with the
82
- * filter that rejected it). Use it to tune the filters above.
80
+ * Every object found is always outlined: red when accepted as a card, grey
81
+ * when rejected. With `debug`, each outline is also labelled with its area
82
+ * (`a`), width/height ratio (`r`) and the filter that rejected it. Use it to
83
+ * tune the filters above.
83
84
  * @default false
84
85
  */
85
86
  debug?: boolean;
87
+ /**
88
+ * Outline (in grey) the objects that were not accepted as a card.
89
+ * The accepted card is always outlined in red.
90
+ * @default true
91
+ */
92
+ showRejected?: boolean;
93
+ /**
94
+ * How long (ms) the card must be out of view before the same card can be
95
+ * reported again. While a card stays in view it is reported only once.
96
+ * @default 1500
97
+ */
98
+ repeatAfterMs?: number;
86
99
  }
87
100
  /** Normalized rectangle (0-1). */
88
101
  export interface Box {
@@ -1 +1 @@
1
- {"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"","sourcesContent":["import type { PermissionState, PluginListenerHandle } from '@capacitor/core';\n\nexport type CameraPosition = 'rear' | 'front';\n\nexport interface StartOptions {\n /**\n * Which camera to use.\n * @default 'rear'\n */\n position?: CameraPosition;\n}\n\nexport interface CaptureOptions {\n /**\n * JPEG quality (0-100).\n * @default 85\n */\n quality?: number;\n}\n\nexport interface CaptureResult {\n /** Base64 encoded JPEG (without the `data:` prefix). */\n base64: string;\n width: number;\n height: number;\n}\n\nexport interface TorchOptions {\n enabled: boolean;\n}\n\nexport interface ZoomOptions {\n /** Zoom ratio, clamped between the camera's min and max. */\n ratio: number;\n}\n\nexport interface PermissionStatus {\n camera: PermissionState;\n}\n\nexport interface DetectionOptions {\n /**\n * Minimum time between two OCR runs, in milliseconds.\n * The red rectangle is updated on every frame regardless.\n * @default 300\n */\n intervalMs?: number;\n /**\n * How many consecutive OCR runs must read the same card before\n * `cardDetected` fires.\n * @default 2\n */\n stableFrames?: number;\n /**\n * Include a JPEG of the detected card in the event.\n * @default false\n */\n includeImage?: boolean;\n /**\n * Expected width / height of an upright card. Only upright cards are\n * detected; a card lying sideways falls outside the tolerance.\n * @default 0.716 (63 x 88 mm)\n */\n cardAspectRatio?: number;\n /**\n * Allowed relative deviation from `cardAspectRatio` (0.15 = ±15%).\n * Increase it if tilted cards are not detected.\n * @default 0.15\n */\n aspectTolerance?: number;\n /**\n * Smallest card size, as a fraction of the camera frame (0-1).\n * @default 0.08\n */\n minCardArea?: number;\n /**\n * Largest card size, as a fraction of the camera frame (0-1).\n * @default 0.9\n */\n maxCardArea?: number;\n /**\n * Only read text when a card-shaped object is visible. When false, the whole\n * frame is read if no card is found.\n * @default true\n */\n requireCard?: boolean;\n /**\n * Draw every object found, labelled with its area (`a`) and width/height\n * ratio (`r`): red when accepted as a card, grey when rejected (with the\n * filter that rejected it). Use it to tune the filters above.\n * @default false\n */\n debug?: boolean;\n}\n\n/** Normalized rectangle (0-1). */\nexport interface Box {\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\nexport interface TextLine {\n text: string;\n /** Position inside the card image. */\n box: Box;\n}\n\nexport interface CardDetectedEvent {\n /** Best guess for the card name (topmost line of text on the card). */\n name?: string;\n /** Collector number, e.g. `025/165`. */\n collectorNumber?: string;\n /** All text read on the card. */\n text: string;\n lines: TextLine[];\n /** Where the card is in the camera frame. Missing if no card outline was found. */\n cardBox?: Box;\n /** Base64 JPEG of the card, when `includeImage` is true. */\n image?: string;\n}\n\nexport interface CardScannerPlugin {\n /**\n * Opens the camera **behind** the WebView. The WebView background is made\n * transparent, so the HTML page must also use transparent backgrounds\n * where the camera should be visible.\n */\n start(options?: StartOptions): Promise<void>;\n\n /** Stops the camera and removes the preview. */\n stop(): Promise<void>;\n\n /** Takes a photo from the running camera. */\n capture(options?: CaptureOptions): Promise<CaptureResult>;\n\n /** Turns the flashlight on/off. */\n setTorch(options: TorchOptions): Promise<void>;\n\n /** Sets the zoom ratio. */\n setZoom(options: ZoomOptions): Promise<void>;\n\n /** Switches between the rear and front camera. */\n flip(): Promise<void>;\n\n /**\n * Starts looking for cards: draws a red rectangle around the card and reads\n * its text, emitting `cardDetected`. Requires `start()` first. Android only.\n */\n startDetection(options?: DetectionOptions): Promise<void>;\n\n /** Stops card detection and hides the rectangle. */\n stopDetection(): Promise<void>;\n\n /** Fired once per card, when its text has been read consistently. */\n addListener(eventName: 'cardDetected', listener: (event: CardDetectedEvent) => void): Promise<PluginListenerHandle>;\n\n removeAllListeners(): Promise<void>;\n\n checkPermissions(): Promise<PermissionStatus>;\n\n requestPermissions(): Promise<PermissionStatus>;\n}\n"]}
1
+ {"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"","sourcesContent":["import type { PermissionState, PluginListenerHandle } from '@capacitor/core';\n\nexport type CameraPosition = 'rear' | 'front';\n\nexport interface StartOptions {\n /**\n * Which camera to use.\n * @default 'rear'\n */\n position?: CameraPosition;\n}\n\nexport interface CaptureOptions {\n /**\n * JPEG quality (0-100).\n * @default 85\n */\n quality?: number;\n}\n\nexport interface CaptureResult {\n /** Base64 encoded JPEG (without the `data:` prefix). */\n base64: string;\n width: number;\n height: number;\n}\n\nexport interface TorchOptions {\n enabled: boolean;\n}\n\nexport interface ZoomOptions {\n /** Zoom ratio, clamped between the camera's min and max. */\n ratio: number;\n}\n\nexport interface PermissionStatus {\n camera: PermissionState;\n}\n\nexport interface DetectionOptions {\n /**\n * Minimum time between two OCR runs, in milliseconds.\n * The red rectangle is updated on every frame regardless.\n * @default 300\n */\n intervalMs?: number;\n /**\n * How many consecutive OCR runs must read the same card before\n * `cardDetected` fires.\n * @default 2\n */\n stableFrames?: number;\n /**\n * Include a JPEG of the detected card in the event.\n * @default false\n */\n includeImage?: boolean;\n /**\n * Expected width / height of an upright card. Only upright cards are\n * detected; a card lying sideways falls outside the tolerance.\n * @default 0.716 (63 x 88 mm)\n */\n cardAspectRatio?: number;\n /**\n * Allowed relative deviation from `cardAspectRatio` (0.15 = ±15%).\n * Increase it if tilted cards are not detected.\n * @default 0.15\n */\n aspectTolerance?: number;\n /**\n * Smallest card size, as a fraction of the camera frame (0-1).\n * @default 0.08\n */\n minCardArea?: number;\n /**\n * Largest card size, as a fraction of the camera frame (0-1).\n * @default 0.9\n */\n maxCardArea?: number;\n /**\n * Only read text when a card-shaped object is visible. When false, the whole\n * frame is read if no card is found.\n * @default true\n */\n requireCard?: boolean;\n /**\n * Every object found is always outlined: red when accepted as a card, grey\n * when rejected. With `debug`, each outline is also labelled with its area\n * (`a`), width/height ratio (`r`) and the filter that rejected it. Use it to\n * tune the filters above.\n * @default false\n */\n debug?: boolean;\n /**\n * Outline (in grey) the objects that were not accepted as a card.\n * The accepted card is always outlined in red.\n * @default true\n */\n showRejected?: boolean;\n /**\n * How long (ms) the card must be out of view before the same card can be\n * reported again. While a card stays in view it is reported only once.\n * @default 1500\n */\n repeatAfterMs?: number;\n}\n\n/** Normalized rectangle (0-1). */\nexport interface Box {\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\nexport interface TextLine {\n text: string;\n /** Position inside the card image. */\n box: Box;\n}\n\nexport interface CardDetectedEvent {\n /** Best guess for the card name (topmost line of text on the card). */\n name?: string;\n /** Collector number, e.g. `025/165`. */\n collectorNumber?: string;\n /** All text read on the card. */\n text: string;\n lines: TextLine[];\n /** Where the card is in the camera frame. Missing if no card outline was found. */\n cardBox?: Box;\n /** Base64 JPEG of the card, when `includeImage` is true. */\n image?: string;\n}\n\nexport interface CardScannerPlugin {\n /**\n * Opens the camera **behind** the WebView. The WebView background is made\n * transparent, so the HTML page must also use transparent backgrounds\n * where the camera should be visible.\n */\n start(options?: StartOptions): Promise<void>;\n\n /** Stops the camera and removes the preview. */\n stop(): Promise<void>;\n\n /** Takes a photo from the running camera. */\n capture(options?: CaptureOptions): Promise<CaptureResult>;\n\n /** Turns the flashlight on/off. */\n setTorch(options: TorchOptions): Promise<void>;\n\n /** Sets the zoom ratio. */\n setZoom(options: ZoomOptions): Promise<void>;\n\n /** Switches between the rear and front camera. */\n flip(): Promise<void>;\n\n /**\n * Starts looking for cards: draws a red rectangle around the card and reads\n * its text, emitting `cardDetected`. Requires `start()` first. Android only.\n */\n startDetection(options?: DetectionOptions): Promise<void>;\n\n /** Stops card detection and hides the rectangle. */\n stopDetection(): Promise<void>;\n\n /** Fired once per card, when its text has been read consistently. */\n addListener(eventName: 'cardDetected', listener: (event: CardDetectedEvent) => void): Promise<PluginListenerHandle>;\n\n removeAllListeners(): Promise<void>;\n\n checkPermissions(): Promise<PermissionStatus>;\n\n requestPermissions(): Promise<PermissionStatus>;\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "capacitor-card-scanner",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "description": "Recognize a pokemon card with camera open in background",
5
5
  "main": "dist/plugin.cjs.js",
6
6
  "module": "dist/esm/index.js",