capacitor-card-scanner 0.3.3 → 0.3.4

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.
@@ -1,189 +1,191 @@
1
- package pt.tcgclassifier.cardscanner;
2
-
3
- import android.Manifest;
4
- import androidx.annotation.Nullable;
5
- import com.getcapacitor.JSArray;
6
- import com.getcapacitor.JSObject;
7
- import com.getcapacitor.PermissionState;
8
- import com.getcapacitor.Plugin;
9
- import com.getcapacitor.PluginCall;
10
- import com.getcapacitor.PluginMethod;
11
- import com.getcapacitor.annotation.CapacitorPlugin;
12
- import com.getcapacitor.annotation.Permission;
13
- import com.getcapacitor.annotation.PermissionCallback;
14
- import java.util.ArrayList;
15
- import java.util.List;
16
-
17
- @CapacitorPlugin(name = "CardScanner", permissions = { @Permission(strings = { Manifest.permission.CAMERA }, alias = CardScannerPlugin.CAMERA) })
18
- public class CardScannerPlugin extends Plugin {
19
-
20
- static final String CAMERA = "camera";
21
-
22
- private CardScanner implementation;
23
-
24
- @Override
25
- public void load() {
26
- implementation = new CardScanner(getActivity(), getBridge().getWebView());
27
- }
28
-
29
- @Override
30
- protected void handleOnDestroy() {
31
- getActivity().runOnUiThread(() -> implementation.stop());
32
- }
33
-
34
- @PluginMethod
35
- public void start(PluginCall call) {
36
- if (getPermissionState(CAMERA) != PermissionState.GRANTED) {
37
- requestPermissionForAlias(CAMERA, call, "cameraPermsCallback");
38
- return;
39
- }
40
- doStart(call);
41
- }
42
-
43
- @PermissionCallback
44
- private void cameraPermsCallback(PluginCall call) {
45
- if (getPermissionState(CAMERA) == PermissionState.GRANTED) {
46
- doStart(call);
47
- } else {
48
- call.reject("Camera permission denied", "PERMISSION_DENIED");
49
- }
50
- }
51
-
52
- private void doStart(PluginCall call) {
53
- boolean front = "front".equals(call.getString("position", "rear"));
54
- getActivity().runOnUiThread(() -> implementation.start(front, voidCallback(call)));
55
- }
56
-
57
- @PluginMethod
58
- public void stop(PluginCall call) {
59
- getActivity().runOnUiThread(() -> {
60
- implementation.stop();
61
- call.resolve();
62
- });
63
- }
64
-
65
- @PluginMethod
66
- public void flip(PluginCall call) {
67
- if (!ensureRunning(call)) return;
68
- getActivity().runOnUiThread(() -> implementation.flip(voidCallback(call)));
69
- }
70
-
71
- @PluginMethod
72
- public void capture(PluginCall call) {
73
- if (!ensureRunning(call)) return;
74
- int quality = Math.max(0, Math.min(100, call.getInt("quality", 85)));
75
- implementation.capture(
76
- quality,
77
- new CardScanner.Callback<>() {
78
- @Override
79
- public void onSuccess(CardScanner.Photo photo) {
80
- JSObject ret = new JSObject();
81
- ret.put("base64", photo.base64);
82
- ret.put("width", photo.width);
83
- ret.put("height", photo.height);
84
- call.resolve(ret);
85
- }
86
-
87
- @Override
88
- public void onError(String message, Exception e) {
89
- call.reject(message, e);
90
- }
91
- }
92
- );
93
- }
94
-
95
- @PluginMethod
96
- public void startDetection(PluginCall call) {
97
- if (!ensureRunning(call)) return;
98
- CardDetector.Options options = new CardDetector.Options();
99
- options.intervalMs = call.getInt("intervalMs", (int) options.intervalMs);
100
- options.stableFrames = call.getInt("stableFrames", options.stableFrames);
101
- options.includeImage = call.getBoolean("includeImage", options.includeImage);
102
- options.cardAspectRatio = call.getFloat("cardAspectRatio", options.cardAspectRatio);
103
- options.aspectTolerance = call.getFloat("aspectTolerance", options.aspectTolerance);
104
- options.minCardArea = call.getFloat("minCardArea", options.minCardArea);
105
- options.maxCardArea = call.getFloat("maxCardArea", options.maxCardArea);
106
- options.requireCard = call.getBoolean("requireCard", options.requireCard);
107
- options.debug = call.getBoolean("debug", options.debug);
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;
115
-
116
- CardDetector detector = new CardDetector(options, result -> notifyListeners("cardDetected", result));
117
- getActivity().runOnUiThread(() -> {
118
- try {
119
- implementation.startDetection(detector);
120
- call.resolve();
121
- } catch (Exception e) {
122
- detector.close();
123
- call.reject("Failed to start detection: " + e.getMessage(), e);
124
- }
125
- });
126
- }
127
-
128
- @PluginMethod
129
- public void stopDetection(PluginCall call) {
130
- getActivity().runOnUiThread(() -> {
131
- implementation.stopDetection();
132
- call.resolve();
133
- });
134
- }
135
-
136
- @PluginMethod
137
- public void setTorch(PluginCall call) {
138
- if (!ensureRunning(call)) return;
139
- implementation.setTorch(Boolean.TRUE.equals(call.getBoolean("enabled", false)));
140
- call.resolve();
141
- }
142
-
143
- @PluginMethod
144
- public void setZoom(PluginCall call) {
145
- if (!ensureRunning(call)) return;
146
- Float ratio = call.getFloat("ratio");
147
- if (ratio == null) {
148
- call.reject("Missing 'ratio'");
149
- return;
150
- }
151
- implementation.setZoom(ratio);
152
- call.resolve();
153
- }
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
-
168
- private boolean ensureRunning(PluginCall call) {
169
- if (!implementation.isRunning()) {
170
- call.reject("Camera is not running", "NOT_RUNNING");
171
- return false;
172
- }
173
- return true;
174
- }
175
-
176
- private CardScanner.Callback<Void> voidCallback(PluginCall call) {
177
- return new CardScanner.Callback<>() {
178
- @Override
179
- public void onSuccess(Void result) {
180
- call.resolve();
181
- }
182
-
183
- @Override
184
- public void onError(String message, Exception e) {
185
- call.reject(message, e);
186
- }
187
- };
188
- }
189
- }
1
+ package pt.tcgclassifier.cardscanner;
2
+
3
+ import android.Manifest;
4
+ import androidx.annotation.Nullable;
5
+ import com.getcapacitor.JSArray;
6
+ import com.getcapacitor.JSObject;
7
+ import com.getcapacitor.PermissionState;
8
+ import com.getcapacitor.Plugin;
9
+ import com.getcapacitor.PluginCall;
10
+ import com.getcapacitor.PluginMethod;
11
+ import com.getcapacitor.annotation.CapacitorPlugin;
12
+ import com.getcapacitor.annotation.Permission;
13
+ import com.getcapacitor.annotation.PermissionCallback;
14
+ import java.util.ArrayList;
15
+ import java.util.List;
16
+
17
+ @CapacitorPlugin(name = "CardScanner", permissions = { @Permission(strings = { Manifest.permission.CAMERA }, alias = CardScannerPlugin.CAMERA) })
18
+ public class CardScannerPlugin extends Plugin {
19
+
20
+ static final String CAMERA = "camera";
21
+
22
+ private CardScanner implementation;
23
+
24
+ @Override
25
+ public void load() {
26
+ implementation = new CardScanner(getActivity(), getBridge().getWebView());
27
+ }
28
+
29
+ @Override
30
+ protected void handleOnDestroy() {
31
+ getActivity().runOnUiThread(() -> implementation.stop());
32
+ }
33
+
34
+ @PluginMethod
35
+ public void start(PluginCall call) {
36
+ if (getPermissionState(CAMERA) != PermissionState.GRANTED) {
37
+ requestPermissionForAlias(CAMERA, call, "cameraPermsCallback");
38
+ return;
39
+ }
40
+ doStart(call);
41
+ }
42
+
43
+ @PermissionCallback
44
+ private void cameraPermsCallback(PluginCall call) {
45
+ if (getPermissionState(CAMERA) == PermissionState.GRANTED) {
46
+ doStart(call);
47
+ } else {
48
+ call.reject("Camera permission denied", "PERMISSION_DENIED");
49
+ }
50
+ }
51
+
52
+ private void doStart(PluginCall call) {
53
+ boolean front = "front".equals(call.getString("position", "rear"));
54
+ getActivity().runOnUiThread(() -> implementation.start(front, voidCallback(call)));
55
+ }
56
+
57
+ @PluginMethod
58
+ public void stop(PluginCall call) {
59
+ getActivity().runOnUiThread(() -> {
60
+ implementation.stop();
61
+ call.resolve();
62
+ });
63
+ }
64
+
65
+ @PluginMethod
66
+ public void flip(PluginCall call) {
67
+ if (!ensureRunning(call)) return;
68
+ getActivity().runOnUiThread(() -> implementation.flip(voidCallback(call)));
69
+ }
70
+
71
+ @PluginMethod
72
+ public void capture(PluginCall call) {
73
+ if (!ensureRunning(call)) return;
74
+ int quality = Math.max(0, Math.min(100, call.getInt("quality", 85)));
75
+ implementation.capture(
76
+ quality,
77
+ new CardScanner.Callback<>() {
78
+ @Override
79
+ public void onSuccess(CardScanner.Photo photo) {
80
+ JSObject ret = new JSObject();
81
+ ret.put("base64", photo.base64);
82
+ ret.put("width", photo.width);
83
+ ret.put("height", photo.height);
84
+ call.resolve(ret);
85
+ }
86
+
87
+ @Override
88
+ public void onError(String message, Exception e) {
89
+ call.reject(message, e);
90
+ }
91
+ }
92
+ );
93
+ }
94
+
95
+ @PluginMethod
96
+ public void startDetection(PluginCall call) {
97
+ if (!ensureRunning(call)) return;
98
+ CardDetector.Options options = new CardDetector.Options();
99
+ options.intervalMs = call.getInt("intervalMs", (int) options.intervalMs);
100
+ options.stableFrames = call.getInt("stableFrames", options.stableFrames);
101
+ options.includeImage = call.getBoolean("includeImage", options.includeImage);
102
+ options.cardAspectRatio = call.getFloat("cardAspectRatio", options.cardAspectRatio);
103
+ options.aspectTolerance = call.getFloat("aspectTolerance", options.aspectTolerance);
104
+ options.minCardArea = call.getFloat("minCardArea", options.minCardArea);
105
+ options.maxCardArea = call.getFloat("maxCardArea", options.maxCardArea);
106
+ options.requireCard = call.getBoolean("requireCard", options.requireCard);
107
+ options.debug = call.getBoolean("debug", options.debug);
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
+ options.cardGapMs = call.getInt("cardGapMs", (int) options.cardGapMs);
112
+ options.sameCardMaxDistance = call.getInt("sameCardMaxDistance", options.sameCardMaxDistance);
113
+ List<String> reservedWords = stringList(call, "reservedWords");
114
+ if (reservedWords != null) options.reservedWords = reservedWords;
115
+ List<String> reservedPrefixes = stringList(call, "reservedPrefixes");
116
+ if (reservedPrefixes != null) options.reservedPrefixes = reservedPrefixes;
117
+
118
+ CardDetector detector = new CardDetector(options, result -> notifyListeners("cardDetected", result));
119
+ getActivity().runOnUiThread(() -> {
120
+ try {
121
+ implementation.startDetection(detector);
122
+ call.resolve();
123
+ } catch (Exception e) {
124
+ detector.close();
125
+ call.reject("Failed to start detection: " + e.getMessage(), e);
126
+ }
127
+ });
128
+ }
129
+
130
+ @PluginMethod
131
+ public void stopDetection(PluginCall call) {
132
+ getActivity().runOnUiThread(() -> {
133
+ implementation.stopDetection();
134
+ call.resolve();
135
+ });
136
+ }
137
+
138
+ @PluginMethod
139
+ public void setTorch(PluginCall call) {
140
+ if (!ensureRunning(call)) return;
141
+ implementation.setTorch(Boolean.TRUE.equals(call.getBoolean("enabled", false)));
142
+ call.resolve();
143
+ }
144
+
145
+ @PluginMethod
146
+ public void setZoom(PluginCall call) {
147
+ if (!ensureRunning(call)) return;
148
+ Float ratio = call.getFloat("ratio");
149
+ if (ratio == null) {
150
+ call.reject("Missing 'ratio'");
151
+ return;
152
+ }
153
+ implementation.setZoom(ratio);
154
+ call.resolve();
155
+ }
156
+
157
+ /** A string array option, or null when it was not passed. */
158
+ @Nullable
159
+ private static List<String> stringList(PluginCall call, String key) {
160
+ JSArray array = call.getArray(key, null);
161
+ if (array == null) return null;
162
+ List<String> list = new ArrayList<>();
163
+ for (int i = 0; i < array.length(); i++) {
164
+ String s = array.optString(i, null);
165
+ if (s != null) list.add(s);
166
+ }
167
+ return list;
168
+ }
169
+
170
+ private boolean ensureRunning(PluginCall call) {
171
+ if (!implementation.isRunning()) {
172
+ call.reject("Camera is not running", "NOT_RUNNING");
173
+ return false;
174
+ }
175
+ return true;
176
+ }
177
+
178
+ private CardScanner.Callback<Void> voidCallback(PluginCall call) {
179
+ return new CardScanner.Callback<>() {
180
+ @Override
181
+ public void onSuccess(Void result) {
182
+ call.resolve();
183
+ }
184
+
185
+ @Override
186
+ public void onError(String message, Exception e) {
187
+ call.reject(message, e);
188
+ }
189
+ };
190
+ }
191
+ }
package/dist/docs.json CHANGED
@@ -17,7 +17,7 @@
17
17
  ],
18
18
  "returns": "Promise<void>",
19
19
  "tags": [],
20
- "docs": "Opens the camera **behind** the WebView. The WebView background is made\ntransparent, so the HTML page must also use transparent backgrounds\nwhere the camera should be visible.",
20
+ "docs": "Opens the camera **behind** the WebView. The WebView background is made\r\ntransparent, so the HTML page must also use transparent backgrounds\r\nwhere the camera should be visible.",
21
21
  "complexTypes": [
22
22
  "StartOptions"
23
23
  ],
@@ -110,7 +110,7 @@
110
110
  ],
111
111
  "returns": "Promise<void>",
112
112
  "tags": [],
113
- "docs": "Starts looking for cards: draws a red rectangle around the card and reads\nits text, emitting `cardDetected`. Requires `start()` first. Android only.",
113
+ "docs": "Starts looking for cards: draws a red rectangle around the card and reads\r\nits text, emitting `cardDetected`. Requires `start()` first. Android only.",
114
114
  "complexTypes": [
115
115
  "DetectionOptions"
116
116
  ],
@@ -309,7 +309,7 @@
309
309
  "name": "default"
310
310
  }
311
311
  ],
312
- "docs": "Minimum time between two OCR runs, in milliseconds.\nThe red rectangle is updated on every frame regardless.",
312
+ "docs": "Minimum time between two OCR runs, in milliseconds.\r\nThe red rectangle is updated on every frame regardless.",
313
313
  "complexTypes": [],
314
314
  "type": "number | undefined"
315
315
  },
@@ -321,7 +321,7 @@
321
321
  "name": "default"
322
322
  }
323
323
  ],
324
- "docs": "How many consecutive OCR runs must read the same card before\n`cardDetected` fires.",
324
+ "docs": "How many consecutive OCR runs must read the same card before\r\n`cardDetected` fires.",
325
325
  "complexTypes": [],
326
326
  "type": "number | undefined"
327
327
  },
@@ -345,7 +345,7 @@
345
345
  "name": "default"
346
346
  }
347
347
  ],
348
- "docs": "Expected width / height of an upright card. Only upright cards are\ndetected; a card lying sideways falls outside the tolerance.",
348
+ "docs": "Expected width / height of an upright card. Only upright cards are\r\ndetected; a card lying sideways falls outside the tolerance.",
349
349
  "complexTypes": [],
350
350
  "type": "number | undefined"
351
351
  },
@@ -357,7 +357,7 @@
357
357
  "name": "default"
358
358
  }
359
359
  ],
360
- "docs": "Allowed relative deviation from `cardAspectRatio` (0.15 = ±15%).\nIncrease it if tilted cards are not detected.",
360
+ "docs": "Allowed relative deviation from `cardAspectRatio` (0.15 = ±15%).\r\nIncrease it if tilted cards are not detected.",
361
361
  "complexTypes": [],
362
362
  "type": "number | undefined"
363
363
  },
@@ -393,7 +393,7 @@
393
393
  "name": "default"
394
394
  }
395
395
  ],
396
- "docs": "Only read text when a card-shaped object is visible. When false, the whole\nframe is read if no card is found.",
396
+ "docs": "Only read text when a card-shaped object is visible. When false, the whole\r\nframe is read if no card is found.",
397
397
  "complexTypes": [],
398
398
  "type": "boolean | undefined"
399
399
  },
@@ -405,7 +405,7 @@
405
405
  "name": "default"
406
406
  }
407
407
  ],
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.",
408
+ "docs": "Every object found is always outlined: red when accepted as a card, grey\r\nwhen rejected. With `debug`, each outline is also labelled with its area\r\n(`a`), width/height ratio (`r`) and the filter that rejected it. Use it to\r\ntune the filters above.",
409
409
  "complexTypes": [],
410
410
  "type": "boolean | undefined"
411
411
  },
@@ -417,7 +417,7 @@
417
417
  "name": "default"
418
418
  }
419
419
  ],
420
- "docs": "Outline (in grey) the objects that were not accepted as a card.\nThe accepted card is always outlined in red.",
420
+ "docs": "Outline (in grey) the objects that were not accepted as a card.\r\nThe accepted card is always outlined in red.",
421
421
  "complexTypes": [],
422
422
  "type": "boolean | undefined"
423
423
  },
@@ -429,7 +429,31 @@
429
429
  "name": "default"
430
430
  }
431
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.",
432
+ "docs": "How long (ms) the card must be out of view before the same card can be\r\nreported again. While a card stays in view it is reported only once.",
433
+ "complexTypes": [],
434
+ "type": "number | undefined"
435
+ },
436
+ {
437
+ "name": "cardGapMs",
438
+ "tags": [
439
+ {
440
+ "text": "500",
441
+ "name": "default"
442
+ }
443
+ ],
444
+ "docs": "While the card outline stays in view, at most one card is reported, even\r\nif later readings give a different name. The outline must disappear for\r\nlonger than this (ms) for the next card to be read, e.g. when swapping cards.",
445
+ "complexTypes": [],
446
+ "type": "number | undefined"
447
+ },
448
+ {
449
+ "name": "sameCardMaxDistance",
450
+ "tags": [
451
+ {
452
+ "text": "16",
453
+ "name": "default"
454
+ }
455
+ ],
456
+ "docs": "Two card images whose perceptual hashes differ in at most this many bits\r\n(of 64) are treated as the same card. Lower = stricter. Lets a card slid\r\nover another one be reported without the outline disappearing.",
433
457
  "complexTypes": [],
434
458
  "type": "number | undefined"
435
459
  },
@@ -441,7 +465,7 @@
441
465
  "name": "default"
442
466
  }
443
467
  ],
444
- "docs": "The card name is looked for only in the first N lines of text on the\ncard, top to bottom.",
468
+ "docs": "The card name is looked for only in the first N lines of text on the\r\ncard, top to bottom.",
445
469
  "complexTypes": [],
446
470
  "type": "number | undefined"
447
471
  },
@@ -449,11 +473,11 @@
449
473
  "name": "reservedWords",
450
474
  "tags": [
451
475
  {
452
- "text": "['Basic', 'Stage 1', 'Stage 2', 'Trainer', 'Item', 'Supporter', 'Stadium', 'Energy', 'Special Energy', 'Basic Energy', 'Pokémon', 'Pokémon Tool']",
476
+ "text": "['Basic', 'Stage', 'Stage 1', 'Stage 2', 'Trainer', 'Item', 'Supporter', 'Stadium', 'Energy', 'Special Energy', 'Basic Energy', 'Pokémon', 'Pokémon Tool', 'Tera']",
453
477
  "name": "default"
454
478
  }
455
479
  ],
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.",
480
+ "docs": "Words/phrases that are never the card name (card types, subtypes...).\r\nA line made only of these words is skipped, so \"Trainer\" or \"Stage 1\" are\r\nignored but \"Energy Retrieval\" is still a name. Replaces the defaults.\r\nAlso matched without spaces, so \"STAGE1\" counts as \"Stage 1\".",
457
481
  "complexTypes": [],
458
482
  "type": "string[] | undefined"
459
483
  },
@@ -465,7 +489,7 @@
465
489
  "name": "default"
466
490
  }
467
491
  ],
468
- "docs": "Lines starting with any of these are never the card name.\nReplaces the defaults.",
492
+ "docs": "Lines starting with any of these are never the card name.\r\nReplaces the defaults.",
469
493
  "complexTypes": [],
470
494
  "type": "string[] | undefined"
471
495
  }
@@ -539,6 +563,13 @@
539
563
  "docs": "Base64 JPEG of the card, when `includeImage` is true.",
540
564
  "complexTypes": [],
541
565
  "type": "string | undefined"
566
+ },
567
+ {
568
+ "name": "imageHash",
569
+ "tags": [],
570
+ "docs": "64-bit perceptual hash of the card image (16 hex characters). Photos of\r\nthe same card give hashes that differ in only a few bits.",
571
+ "complexTypes": [],
572
+ "type": "string"
542
573
  }
543
574
  ]
544
575
  },
@@ -96,6 +96,20 @@ export interface DetectionOptions {
96
96
  * @default 1500
97
97
  */
98
98
  repeatAfterMs?: number;
99
+ /**
100
+ * While the card outline stays in view, at most one card is reported, even
101
+ * if later readings give a different name. The outline must disappear for
102
+ * longer than this (ms) for the next card to be read, e.g. when swapping cards.
103
+ * @default 500
104
+ */
105
+ cardGapMs?: number;
106
+ /**
107
+ * Two card images whose perceptual hashes differ in at most this many bits
108
+ * (of 64) are treated as the same card. Lower = stricter. Lets a card slid
109
+ * over another one be reported without the outline disappearing.
110
+ * @default 16
111
+ */
112
+ sameCardMaxDistance?: number;
99
113
  /**
100
114
  * The card name is looked for only in the first N lines of text on the
101
115
  * card, top to bottom.
@@ -106,7 +120,8 @@ export interface DetectionOptions {
106
120
  * Words/phrases that are never the card name (card types, subtypes...).
107
121
  * A line made only of these words is skipped, so "Trainer" or "Stage 1" are
108
122
  * 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']
123
+ * Also matched without spaces, so "STAGE1" counts as "Stage 1".
124
+ * @default ['Basic', 'Stage', 'Stage 1', 'Stage 2', 'Trainer', 'Item', 'Supporter', 'Stadium', 'Energy', 'Special Energy', 'Basic Energy', 'Pokémon', 'Pokémon Tool', 'Tera']
110
125
  */
111
126
  reservedWords?: string[];
112
127
  /**
@@ -140,6 +155,11 @@ export interface CardDetectedEvent {
140
155
  cardBox?: Box;
141
156
  /** Base64 JPEG of the card, when `includeImage` is true. */
142
157
  image?: string;
158
+ /**
159
+ * 64-bit perceptual hash of the card image (16 hex characters). Photos of
160
+ * the same card give hashes that differ in only a few bits.
161
+ */
162
+ imageHash: string;
143
163
  }
144
164
  export interface CardScannerPlugin {
145
165
  /**
@@ -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 * 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"]}
1
+ {"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"","sourcesContent":["import type { PermissionState, PluginListenerHandle } from '@capacitor/core';\r\n\r\nexport type CameraPosition = 'rear' | 'front';\r\n\r\nexport interface StartOptions {\r\n /**\r\n * Which camera to use.\r\n * @default 'rear'\r\n */\r\n position?: CameraPosition;\r\n}\r\n\r\nexport interface CaptureOptions {\r\n /**\r\n * JPEG quality (0-100).\r\n * @default 85\r\n */\r\n quality?: number;\r\n}\r\n\r\nexport interface CaptureResult {\r\n /** Base64 encoded JPEG (without the `data:` prefix). */\r\n base64: string;\r\n width: number;\r\n height: number;\r\n}\r\n\r\nexport interface TorchOptions {\r\n enabled: boolean;\r\n}\r\n\r\nexport interface ZoomOptions {\r\n /** Zoom ratio, clamped between the camera's min and max. */\r\n ratio: number;\r\n}\r\n\r\nexport interface PermissionStatus {\r\n camera: PermissionState;\r\n}\r\n\r\nexport interface DetectionOptions {\r\n /**\r\n * Minimum time between two OCR runs, in milliseconds.\r\n * The red rectangle is updated on every frame regardless.\r\n * @default 300\r\n */\r\n intervalMs?: number;\r\n /**\r\n * How many consecutive OCR runs must read the same card before\r\n * `cardDetected` fires.\r\n * @default 2\r\n */\r\n stableFrames?: number;\r\n /**\r\n * Include a JPEG of the detected card in the event.\r\n * @default false\r\n */\r\n includeImage?: boolean;\r\n /**\r\n * Expected width / height of an upright card. Only upright cards are\r\n * detected; a card lying sideways falls outside the tolerance.\r\n * @default 0.716 (63 x 88 mm)\r\n */\r\n cardAspectRatio?: number;\r\n /**\r\n * Allowed relative deviation from `cardAspectRatio` (0.15 = ±15%).\r\n * Increase it if tilted cards are not detected.\r\n * @default 0.15\r\n */\r\n aspectTolerance?: number;\r\n /**\r\n * Smallest card size, as a fraction of the camera frame (0-1).\r\n * @default 0.08\r\n */\r\n minCardArea?: number;\r\n /**\r\n * Largest card size, as a fraction of the camera frame (0-1).\r\n * @default 0.9\r\n */\r\n maxCardArea?: number;\r\n /**\r\n * Only read text when a card-shaped object is visible. When false, the whole\r\n * frame is read if no card is found.\r\n * @default true\r\n */\r\n requireCard?: boolean;\r\n /**\r\n * Every object found is always outlined: red when accepted as a card, grey\r\n * when rejected. With `debug`, each outline is also labelled with its area\r\n * (`a`), width/height ratio (`r`) and the filter that rejected it. Use it to\r\n * tune the filters above.\r\n * @default false\r\n */\r\n debug?: boolean;\r\n /**\r\n * Outline (in grey) the objects that were not accepted as a card.\r\n * The accepted card is always outlined in red.\r\n * @default true\r\n */\r\n showRejected?: boolean;\r\n /**\r\n * How long (ms) the card must be out of view before the same card can be\r\n * reported again. While a card stays in view it is reported only once.\r\n * @default 1500\r\n */\r\n repeatAfterMs?: number;\r\n /**\r\n * While the card outline stays in view, at most one card is reported, even\r\n * if later readings give a different name. The outline must disappear for\r\n * longer than this (ms) for the next card to be read, e.g. when swapping cards.\r\n * @default 500\r\n */\r\n cardGapMs?: number;\r\n /**\r\n * Two card images whose perceptual hashes differ in at most this many bits\r\n * (of 64) are treated as the same card. Lower = stricter. Lets a card slid\r\n * over another one be reported without the outline disappearing.\r\n * @default 16\r\n */\r\n sameCardMaxDistance?: number;\r\n /**\r\n * The card name is looked for only in the first N lines of text on the\r\n * card, top to bottom.\r\n * @default 3\r\n */\r\n nameLines?: number;\r\n /**\r\n * Words/phrases that are never the card name (card types, subtypes...).\r\n * A line made only of these words is skipped, so \"Trainer\" or \"Stage 1\" are\r\n * ignored but \"Energy Retrieval\" is still a name. Replaces the defaults.\r\n * Also matched without spaces, so \"STAGE1\" counts as \"Stage 1\".\r\n * @default ['Basic', 'Stage', 'Stage 1', 'Stage 2', 'Trainer', 'Item', 'Supporter', 'Stadium', 'Energy', 'Special Energy', 'Basic Energy', 'Pokémon', 'Pokémon Tool', 'Tera']\r\n */\r\n reservedWords?: string[];\r\n /**\r\n * Lines starting with any of these are never the card name.\r\n * Replaces the defaults.\r\n * @default ['Evolves from', 'HP']\r\n */\r\n reservedPrefixes?: string[];\r\n}\r\n\r\n/** Normalized rectangle (0-1). */\r\nexport interface Box {\r\n x: number;\r\n y: number;\r\n width: number;\r\n height: number;\r\n}\r\n\r\nexport interface TextLine {\r\n text: string;\r\n /** Position inside the card image. */\r\n box: Box;\r\n}\r\n\r\nexport interface CardDetectedEvent {\r\n /** Best guess for the card name (topmost line of text on the card). */\r\n name?: string;\r\n /** Collector number, e.g. `025/165`. */\r\n collectorNumber?: string;\r\n /** All text read on the card. */\r\n text: string;\r\n lines: TextLine[];\r\n /** Where the card is in the camera frame. Missing if no card outline was found. */\r\n cardBox?: Box;\r\n /** Base64 JPEG of the card, when `includeImage` is true. */\r\n image?: string;\r\n /**\r\n * 64-bit perceptual hash of the card image (16 hex characters). Photos of\r\n * the same card give hashes that differ in only a few bits.\r\n */\r\n imageHash: string;\r\n}\r\n\r\nexport interface CardScannerPlugin {\r\n /**\r\n * Opens the camera **behind** the WebView. The WebView background is made\r\n * transparent, so the HTML page must also use transparent backgrounds\r\n * where the camera should be visible.\r\n */\r\n start(options?: StartOptions): Promise<void>;\r\n\r\n /** Stops the camera and removes the preview. */\r\n stop(): Promise<void>;\r\n\r\n /** Takes a photo from the running camera. */\r\n capture(options?: CaptureOptions): Promise<CaptureResult>;\r\n\r\n /** Turns the flashlight on/off. */\r\n setTorch(options: TorchOptions): Promise<void>;\r\n\r\n /** Sets the zoom ratio. */\r\n setZoom(options: ZoomOptions): Promise<void>;\r\n\r\n /** Switches between the rear and front camera. */\r\n flip(): Promise<void>;\r\n\r\n /**\r\n * Starts looking for cards: draws a red rectangle around the card and reads\r\n * its text, emitting `cardDetected`. Requires `start()` first. Android only.\r\n */\r\n startDetection(options?: DetectionOptions): Promise<void>;\r\n\r\n /** Stops card detection and hides the rectangle. */\r\n stopDetection(): Promise<void>;\r\n\r\n /** Fired once per card, when its text has been read consistently. */\r\n addListener(eventName: 'cardDetected', listener: (event: CardDetectedEvent) => void): Promise<PluginListenerHandle>;\r\n\r\n removeAllListeners(): Promise<void>;\r\n\r\n checkPermissions(): Promise<PermissionStatus>;\r\n\r\n requestPermissions(): Promise<PermissionStatus>;\r\n}\r\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "capacitor-card-scanner",
3
- "version": "0.3.3",
3
+ "version": "0.3.4",
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",