capacitor-card-scanner 0.3.1 → 0.3.3
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 +16 -12
- package/android/src/main/java/pt/tcgclassifier/cardscanner/CardDetector.java +106 -19
- package/android/src/main/java/pt/tcgclassifier/cardscanner/CardScannerPlugin.java +23 -0
- package/dist/docs.json +48 -0
- package/dist/esm/definitions.d.ts +25 -0
- package/dist/esm/definitions.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -328,18 +328,22 @@ requestPermissions() => Promise<PermissionStatus>
|
|
|
328
328
|
|
|
329
329
|
#### DetectionOptions
|
|
330
330
|
|
|
331
|
-
| Prop
|
|
332
|
-
|
|
|
333
|
-
| **`intervalMs`**
|
|
334
|
-
| **`stableFrames`**
|
|
335
|
-
| **`includeImage`**
|
|
336
|
-
| **`cardAspectRatio`**
|
|
337
|
-
| **`aspectTolerance`**
|
|
338
|
-
| **`minCardArea`**
|
|
339
|
-
| **`maxCardArea`**
|
|
340
|
-
| **`requireCard`**
|
|
341
|
-
| **`debug`**
|
|
342
|
-
| **`showRejected`**
|
|
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> |
|
|
344
|
+
| **`nameLines`** | <code>number</code> | The card name is looked for only in the first N lines of text on the card, top to bottom. | <code>3</code> |
|
|
345
|
+
| **`reservedWords`** | <code>string[]</code> | Words/phrases that are never the card name (card types, subtypes...). A line made only of these words is skipped, so "Trainer" or "Stage 1" are ignored but "Energy Retrieval" is still a name. Replaces the defaults. | <code>['Basic', 'Stage 1', 'Stage 2', 'Trainer', 'Item', 'Supporter', 'Stadium', 'Energy', 'Special Energy', 'Basic Energy', 'Pokémon', 'Pokémon Tool']</code> |
|
|
346
|
+
| **`reservedPrefixes`** | <code>string[]</code> | Lines starting with any of these are never the card name. Replaces the defaults. | <code>['Evolves from', 'HP']</code> |
|
|
343
347
|
|
|
344
348
|
|
|
345
349
|
#### PluginListenerHandle
|
|
@@ -24,7 +24,10 @@ import com.google.mlkit.vision.text.TextRecognition;
|
|
|
24
24
|
import com.google.mlkit.vision.text.TextRecognizer;
|
|
25
25
|
import com.google.mlkit.vision.text.latin.TextRecognizerOptions;
|
|
26
26
|
import java.io.ByteArrayOutputStream;
|
|
27
|
+
import java.text.Normalizer;
|
|
27
28
|
import java.util.ArrayList;
|
|
29
|
+
import java.util.Arrays;
|
|
30
|
+
import java.util.Comparator;
|
|
28
31
|
import java.util.List;
|
|
29
32
|
import java.util.Locale;
|
|
30
33
|
import java.util.regex.Matcher;
|
|
@@ -64,6 +67,20 @@ public class CardDetector implements ImageAnalysis.Analyzer {
|
|
|
64
67
|
public boolean debug = false;
|
|
65
68
|
/** Outline (in grey) the objects that were not accepted as a card. */
|
|
66
69
|
public boolean showRejected = true;
|
|
70
|
+
/** How long the card must be out of view before the same card can be reported again. */
|
|
71
|
+
public long repeatAfterMs = 1500;
|
|
72
|
+
/** The name is looked for only in the first N lines of the card (top to bottom). */
|
|
73
|
+
public int nameLines = 3;
|
|
74
|
+
/**
|
|
75
|
+
* Words/phrases that are never the card name (card types, subtypes...).
|
|
76
|
+
* A line made only of these words is skipped; "Energy Retrieval" is still a name.
|
|
77
|
+
*/
|
|
78
|
+
public List<String> reservedWords = Arrays.asList(
|
|
79
|
+
"Basic", "Stage 1", "Stage 2", "Trainer", "Item", "Supporter", "Stadium",
|
|
80
|
+
"Energy", "Special Energy", "Basic Energy", "Pokémon", "Pokémon Tool"
|
|
81
|
+
);
|
|
82
|
+
/** Lines starting with any of these are never the card name. */
|
|
83
|
+
public List<String> reservedPrefixes = Arrays.asList("Evolves from", "HP");
|
|
67
84
|
}
|
|
68
85
|
|
|
69
86
|
/** An object found by ML Kit, with the values the card filters were checked against. */
|
|
@@ -102,14 +119,10 @@ public class CardDetector implements ImageAnalysis.Analyzer {
|
|
|
102
119
|
|
|
103
120
|
// e.g. "025/165", "SV001/SV122", "TG05/TG30", "GG01/GG70"
|
|
104
121
|
private static final Pattern NUMBER = Pattern.compile("\\b([A-Za-z]{0,4}\\d{1,3}[a-z]?)\\s*/\\s*([A-Za-z]{0,4}\\d{1,3})\\b");
|
|
105
|
-
private static final Pattern HEADER = Pattern.compile(
|
|
106
|
-
"^(basic|stage\\s*\\d|trainer|item|supporter|stadium|energy|pok[eé]mon|evolves|hp\\b)",
|
|
107
|
-
Pattern.CASE_INSENSITIVE
|
|
108
|
-
);
|
|
109
122
|
private static final Pattern HP_SUFFIX = Pattern.compile("\\s*HP\\s*\\d+\\s*$", Pattern.CASE_INSENSITIVE);
|
|
110
123
|
private static final Pattern LETTER = Pattern.compile("\\p{L}");
|
|
111
|
-
/**
|
|
112
|
-
private static final
|
|
124
|
+
/** Names at least this similar are treated as the same card. */
|
|
125
|
+
private static final float SAME_NAME_SIMILARITY = 0.8f;
|
|
113
126
|
/** Extra margin around the detected card before running OCR. */
|
|
114
127
|
private static final float CROP_MARGIN = 0.04f;
|
|
115
128
|
|
|
@@ -119,6 +132,9 @@ public class CardDetector implements ImageAnalysis.Analyzer {
|
|
|
119
132
|
private final TextRecognizer recognizer = TextRecognition.getClient(TextRecognizerOptions.DEFAULT_OPTIONS);
|
|
120
133
|
private final Options options;
|
|
121
134
|
private final Listener listener;
|
|
135
|
+
/** reservedWords / reservedPrefixes, normalized; words longest first. */
|
|
136
|
+
private final List<String> reservedWords = new ArrayList<>();
|
|
137
|
+
private final List<String> reservedPrefixes = new ArrayList<>();
|
|
122
138
|
private volatile BoxListener boxListener;
|
|
123
139
|
|
|
124
140
|
// Only touched from the analyzer thread.
|
|
@@ -126,12 +142,26 @@ public class CardDetector implements ImageAnalysis.Analyzer {
|
|
|
126
142
|
private long lastSeenAt;
|
|
127
143
|
private String candidateKey;
|
|
128
144
|
private int candidateCount;
|
|
129
|
-
|
|
145
|
+
// Last reported card. A reading is the same card if either its name or its
|
|
146
|
+
// number matches, because OCR does not read the number on every frame.
|
|
147
|
+
private String lastEmittedName;
|
|
148
|
+
private String lastEmittedNumber;
|
|
130
149
|
|
|
131
150
|
public CardDetector(Options options, Listener listener) {
|
|
132
151
|
this.options = options;
|
|
133
152
|
this.options.stableFrames = Math.max(1, options.stableFrames);
|
|
153
|
+
this.options.nameLines = Math.max(1, options.nameLines);
|
|
134
154
|
this.listener = listener;
|
|
155
|
+
for (String w : options.reservedWords) {
|
|
156
|
+
String n = normalize(w);
|
|
157
|
+
if (!n.isEmpty()) reservedWords.add(n);
|
|
158
|
+
}
|
|
159
|
+
// Longest first, so "Pokémon Tool" is removed before "Pokémon".
|
|
160
|
+
reservedWords.sort(Comparator.comparingInt(String::length).reversed());
|
|
161
|
+
for (String p : options.reservedPrefixes) {
|
|
162
|
+
String n = normalize(p);
|
|
163
|
+
if (!n.isEmpty()) reservedPrefixes.add(n);
|
|
164
|
+
}
|
|
135
165
|
}
|
|
136
166
|
|
|
137
167
|
public boolean isDebug() {
|
|
@@ -169,6 +199,9 @@ public class CardDetector implements ImageAnalysis.Analyzer {
|
|
|
169
199
|
List<Candidate> candidates = new ArrayList<>();
|
|
170
200
|
Rect card = pickCard(objects, frameW, frameH, candidates);
|
|
171
201
|
RectF cardBox = card == null ? null : normalize(card, frameW, frameH);
|
|
202
|
+
// The card counts as "in view" while its outline is visible, even if
|
|
203
|
+
// its text is not readable on this frame.
|
|
204
|
+
if (card != null) lastSeenAt = now;
|
|
172
205
|
BoxListener bl = boxListener;
|
|
173
206
|
if (bl != null) bl.onDetections(cardBox, candidates, frameW, frameH);
|
|
174
207
|
|
|
@@ -249,8 +282,7 @@ public class CardDetector implements ImageAnalysis.Analyzer {
|
|
|
249
282
|
float h = region.getHeight();
|
|
250
283
|
JSArray lines = new JSArray();
|
|
251
284
|
String number = null;
|
|
252
|
-
|
|
253
|
-
int nameTop = Integer.MAX_VALUE;
|
|
285
|
+
List<Text.Line> ordered = new ArrayList<>();
|
|
254
286
|
|
|
255
287
|
for (Text.TextBlock block : text.getTextBlocks()) {
|
|
256
288
|
for (Text.Line line : block.getLines()) {
|
|
@@ -267,12 +299,10 @@ public class CardDetector implements ImageAnalysis.Analyzer {
|
|
|
267
299
|
Matcher m = NUMBER.matcher(s);
|
|
268
300
|
if (m.find()) number = m.group(1) + "/" + m.group(2);
|
|
269
301
|
}
|
|
270
|
-
|
|
271
|
-
name = HP_SUFFIX.matcher(s).replaceAll("").trim();
|
|
272
|
-
nameTop = r.top;
|
|
273
|
-
}
|
|
302
|
+
ordered.add(line);
|
|
274
303
|
}
|
|
275
304
|
}
|
|
305
|
+
String name = findName(ordered);
|
|
276
306
|
|
|
277
307
|
String key = number != null ? number : name;
|
|
278
308
|
if (key == null) {
|
|
@@ -287,8 +317,9 @@ public class CardDetector implements ImageAnalysis.Analyzer {
|
|
|
287
317
|
candidateKey = key;
|
|
288
318
|
candidateCount = 1;
|
|
289
319
|
}
|
|
290
|
-
if (candidateCount < options.stableFrames ||
|
|
291
|
-
|
|
320
|
+
if (candidateCount < options.stableFrames || isLastEmitted(name, number)) return;
|
|
321
|
+
lastEmittedName = name;
|
|
322
|
+
lastEmittedNumber = number;
|
|
292
323
|
|
|
293
324
|
JSObject result = new JSObject();
|
|
294
325
|
result.put("name", name);
|
|
@@ -309,7 +340,33 @@ public class CardDetector implements ImageAnalysis.Analyzer {
|
|
|
309
340
|
private void noCardRead(long now) {
|
|
310
341
|
candidateKey = null;
|
|
311
342
|
candidateCount = 0;
|
|
312
|
-
if (now - lastSeenAt >
|
|
343
|
+
if (now - lastSeenAt > options.repeatAfterMs) {
|
|
344
|
+
lastEmittedName = null;
|
|
345
|
+
lastEmittedNumber = null;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
private boolean isLastEmitted(String name, String number) {
|
|
350
|
+
if (number != null && number.equals(lastEmittedNumber)) return true;
|
|
351
|
+
return name != null && lastEmittedName != null && similarity(name, lastEmittedName) >= SAME_NAME_SIMILARITY;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/** 0-1 similarity (LCS based), so an OCR slip like "Evolutlon" still matches "Evolution". */
|
|
355
|
+
private static float similarity(String a, String b) {
|
|
356
|
+
a = a.toLowerCase(Locale.ROOT);
|
|
357
|
+
b = b.toLowerCase(Locale.ROOT);
|
|
358
|
+
if (a.equals(b)) return 1f;
|
|
359
|
+
int[] prev = new int[b.length() + 1];
|
|
360
|
+
int[] cur = new int[b.length() + 1];
|
|
361
|
+
for (int i = 1; i <= a.length(); i++) {
|
|
362
|
+
for (int j = 1; j <= b.length(); j++) {
|
|
363
|
+
cur[j] = a.charAt(i - 1) == b.charAt(j - 1) ? prev[j - 1] + 1 : Math.max(prev[j], cur[j - 1]);
|
|
364
|
+
}
|
|
365
|
+
int[] tmp = prev;
|
|
366
|
+
prev = cur;
|
|
367
|
+
cur = tmp;
|
|
368
|
+
}
|
|
369
|
+
return 2f * prev[b.length()] / (a.length() + b.length());
|
|
313
370
|
}
|
|
314
371
|
|
|
315
372
|
private static JSObject box(float x, float y, float width, float height) {
|
|
@@ -321,11 +378,41 @@ public class CardDetector implements ImageAnalysis.Analyzer {
|
|
|
321
378
|
return box;
|
|
322
379
|
}
|
|
323
380
|
|
|
324
|
-
|
|
325
|
-
|
|
381
|
+
/** First line, among the top {@code nameLines}, that is not made of reserved words. */
|
|
382
|
+
@Nullable
|
|
383
|
+
private String findName(List<Text.Line> lines) {
|
|
384
|
+
lines.sort(Comparator.comparingInt(l -> l.getBoundingBox().top));
|
|
385
|
+
for (int i = 0; i < lines.size() && i < options.nameLines; i++) {
|
|
386
|
+
String s = HP_SUFFIX.matcher(lines.get(i).getText().trim()).replaceAll("").trim();
|
|
387
|
+
if (!isReserved(s) && countLetters(s) >= 3) return s;
|
|
388
|
+
}
|
|
389
|
+
return null;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
private boolean isReserved(String line) {
|
|
393
|
+
String rest = " " + normalize(line) + " ";
|
|
394
|
+
for (String w : reservedWords) {
|
|
395
|
+
rest = rest.replace(" " + w + " ", " ");
|
|
396
|
+
}
|
|
397
|
+
rest = rest.trim();
|
|
398
|
+
if (countLetters(rest) < 3) return true;
|
|
399
|
+
// Checked after removing reserved words, so "STAGE 1 Evolves from Pichu" is caught too.
|
|
400
|
+
for (String p : reservedPrefixes) {
|
|
401
|
+
if (rest.equals(p) || rest.startsWith(p + " ")) return true;
|
|
402
|
+
}
|
|
403
|
+
return false;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/** Upper case, no accents, only letters/digits separated by single spaces. */
|
|
407
|
+
private static String normalize(String s) {
|
|
408
|
+
String n = Normalizer.normalize(s, Normalizer.Form.NFD).replaceAll("\\p{M}", "");
|
|
409
|
+
return n.toUpperCase(Locale.ROOT).replaceAll("[^\\p{L}\\p{Nd}]+", " ").trim();
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
private static int countLetters(String s) {
|
|
326
413
|
Matcher m = LETTER.matcher(s);
|
|
327
414
|
int letters = 0;
|
|
328
415
|
while (m.find()) letters++;
|
|
329
|
-
return letters
|
|
416
|
+
return letters;
|
|
330
417
|
}
|
|
331
418
|
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
package pt.tcgclassifier.cardscanner;
|
|
2
2
|
|
|
3
3
|
import android.Manifest;
|
|
4
|
+
import androidx.annotation.Nullable;
|
|
5
|
+
import com.getcapacitor.JSArray;
|
|
4
6
|
import com.getcapacitor.JSObject;
|
|
5
7
|
import com.getcapacitor.PermissionState;
|
|
6
8
|
import com.getcapacitor.Plugin;
|
|
@@ -9,6 +11,8 @@ import com.getcapacitor.PluginMethod;
|
|
|
9
11
|
import com.getcapacitor.annotation.CapacitorPlugin;
|
|
10
12
|
import com.getcapacitor.annotation.Permission;
|
|
11
13
|
import com.getcapacitor.annotation.PermissionCallback;
|
|
14
|
+
import java.util.ArrayList;
|
|
15
|
+
import java.util.List;
|
|
12
16
|
|
|
13
17
|
@CapacitorPlugin(name = "CardScanner", permissions = { @Permission(strings = { Manifest.permission.CAMERA }, alias = CardScannerPlugin.CAMERA) })
|
|
14
18
|
public class CardScannerPlugin extends Plugin {
|
|
@@ -102,6 +106,12 @@ public class CardScannerPlugin extends Plugin {
|
|
|
102
106
|
options.requireCard = call.getBoolean("requireCard", options.requireCard);
|
|
103
107
|
options.debug = call.getBoolean("debug", options.debug);
|
|
104
108
|
options.showRejected = call.getBoolean("showRejected", options.showRejected);
|
|
109
|
+
options.repeatAfterMs = call.getInt("repeatAfterMs", (int) options.repeatAfterMs);
|
|
110
|
+
options.nameLines = call.getInt("nameLines", options.nameLines);
|
|
111
|
+
List<String> reservedWords = stringList(call, "reservedWords");
|
|
112
|
+
if (reservedWords != null) options.reservedWords = reservedWords;
|
|
113
|
+
List<String> reservedPrefixes = stringList(call, "reservedPrefixes");
|
|
114
|
+
if (reservedPrefixes != null) options.reservedPrefixes = reservedPrefixes;
|
|
105
115
|
|
|
106
116
|
CardDetector detector = new CardDetector(options, result -> notifyListeners("cardDetected", result));
|
|
107
117
|
getActivity().runOnUiThread(() -> {
|
|
@@ -142,6 +152,19 @@ public class CardScannerPlugin extends Plugin {
|
|
|
142
152
|
call.resolve();
|
|
143
153
|
}
|
|
144
154
|
|
|
155
|
+
/** A string array option, or null when it was not passed. */
|
|
156
|
+
@Nullable
|
|
157
|
+
private static List<String> stringList(PluginCall call, String key) {
|
|
158
|
+
JSArray array = call.getArray(key, null);
|
|
159
|
+
if (array == null) return null;
|
|
160
|
+
List<String> list = new ArrayList<>();
|
|
161
|
+
for (int i = 0; i < array.length(); i++) {
|
|
162
|
+
String s = array.optString(i, null);
|
|
163
|
+
if (s != null) list.add(s);
|
|
164
|
+
}
|
|
165
|
+
return list;
|
|
166
|
+
}
|
|
167
|
+
|
|
145
168
|
private boolean ensureRunning(PluginCall call) {
|
|
146
169
|
if (!implementation.isRunning()) {
|
|
147
170
|
call.reject("Camera is not running", "NOT_RUNNING");
|
package/dist/docs.json
CHANGED
|
@@ -420,6 +420,54 @@
|
|
|
420
420
|
"docs": "Outline (in grey) the objects that were not accepted as a card.\nThe accepted card is always outlined in red.",
|
|
421
421
|
"complexTypes": [],
|
|
422
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"
|
|
435
|
+
},
|
|
436
|
+
{
|
|
437
|
+
"name": "nameLines",
|
|
438
|
+
"tags": [
|
|
439
|
+
{
|
|
440
|
+
"text": "3",
|
|
441
|
+
"name": "default"
|
|
442
|
+
}
|
|
443
|
+
],
|
|
444
|
+
"docs": "The card name is looked for only in the first N lines of text on the\ncard, top to bottom.",
|
|
445
|
+
"complexTypes": [],
|
|
446
|
+
"type": "number | undefined"
|
|
447
|
+
},
|
|
448
|
+
{
|
|
449
|
+
"name": "reservedWords",
|
|
450
|
+
"tags": [
|
|
451
|
+
{
|
|
452
|
+
"text": "['Basic', 'Stage 1', 'Stage 2', 'Trainer', 'Item', 'Supporter', 'Stadium', 'Energy', 'Special Energy', 'Basic Energy', 'Pokémon', 'Pokémon Tool']",
|
|
453
|
+
"name": "default"
|
|
454
|
+
}
|
|
455
|
+
],
|
|
456
|
+
"docs": "Words/phrases that are never the card name (card types, subtypes...).\nA line made only of these words is skipped, so \"Trainer\" or \"Stage 1\" are\nignored but \"Energy Retrieval\" is still a name. Replaces the defaults.",
|
|
457
|
+
"complexTypes": [],
|
|
458
|
+
"type": "string[] | undefined"
|
|
459
|
+
},
|
|
460
|
+
{
|
|
461
|
+
"name": "reservedPrefixes",
|
|
462
|
+
"tags": [
|
|
463
|
+
{
|
|
464
|
+
"text": "['Evolves from', 'HP']",
|
|
465
|
+
"name": "default"
|
|
466
|
+
}
|
|
467
|
+
],
|
|
468
|
+
"docs": "Lines starting with any of these are never the card name.\nReplaces the defaults.",
|
|
469
|
+
"complexTypes": [],
|
|
470
|
+
"type": "string[] | undefined"
|
|
423
471
|
}
|
|
424
472
|
]
|
|
425
473
|
},
|
|
@@ -90,6 +90,31 @@ export interface DetectionOptions {
|
|
|
90
90
|
* @default true
|
|
91
91
|
*/
|
|
92
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;
|
|
99
|
+
/**
|
|
100
|
+
* The card name is looked for only in the first N lines of text on the
|
|
101
|
+
* card, top to bottom.
|
|
102
|
+
* @default 3
|
|
103
|
+
*/
|
|
104
|
+
nameLines?: number;
|
|
105
|
+
/**
|
|
106
|
+
* Words/phrases that are never the card name (card types, subtypes...).
|
|
107
|
+
* A line made only of these words is skipped, so "Trainer" or "Stage 1" are
|
|
108
|
+
* ignored but "Energy Retrieval" is still a name. Replaces the defaults.
|
|
109
|
+
* @default ['Basic', 'Stage 1', 'Stage 2', 'Trainer', 'Item', 'Supporter', 'Stadium', 'Energy', 'Special Energy', 'Basic Energy', 'Pokémon', 'Pokémon Tool']
|
|
110
|
+
*/
|
|
111
|
+
reservedWords?: string[];
|
|
112
|
+
/**
|
|
113
|
+
* Lines starting with any of these are never the card name.
|
|
114
|
+
* Replaces the defaults.
|
|
115
|
+
* @default ['Evolves from', 'HP']
|
|
116
|
+
*/
|
|
117
|
+
reservedPrefixes?: string[];
|
|
93
118
|
}
|
|
94
119
|
/** Normalized rectangle (0-1). */
|
|
95
120
|
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 * 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\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 * The card name is looked for only in the first N lines of text on the\n * card, top to bottom.\n * @default 3\n */\n nameLines?: number;\n /**\n * Words/phrases that are never the card name (card types, subtypes...).\n * A line made only of these words is skipped, so \"Trainer\" or \"Stage 1\" are\n * ignored but \"Energy Retrieval\" is still a name. Replaces the defaults.\n * @default ['Basic', 'Stage 1', 'Stage 2', 'Trainer', 'Item', 'Supporter', 'Stadium', 'Energy', 'Special Energy', 'Basic Energy', 'Pokémon', 'Pokémon Tool']\n */\n reservedWords?: string[];\n /**\n * Lines starting with any of these are never the card name.\n * Replaces the defaults.\n * @default ['Evolves from', 'HP']\n */\n reservedPrefixes?: string[];\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"]}
|