capacitor-card-scanner 0.3.2 → 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,167 +1,191 @@
1
- package pt.tcgclassifier.cardscanner;
2
-
3
- import android.Manifest;
4
- import com.getcapacitor.JSObject;
5
- import com.getcapacitor.PermissionState;
6
- import com.getcapacitor.Plugin;
7
- import com.getcapacitor.PluginCall;
8
- import com.getcapacitor.PluginMethod;
9
- import com.getcapacitor.annotation.CapacitorPlugin;
10
- import com.getcapacitor.annotation.Permission;
11
- import com.getcapacitor.annotation.PermissionCallback;
12
-
13
- @CapacitorPlugin(name = "CardScanner", permissions = { @Permission(strings = { Manifest.permission.CAMERA }, alias = CardScannerPlugin.CAMERA) })
14
- public class CardScannerPlugin extends Plugin {
15
-
16
- static final String CAMERA = "camera";
17
-
18
- private CardScanner implementation;
19
-
20
- @Override
21
- public void load() {
22
- implementation = new CardScanner(getActivity(), getBridge().getWebView());
23
- }
24
-
25
- @Override
26
- protected void handleOnDestroy() {
27
- getActivity().runOnUiThread(() -> implementation.stop());
28
- }
29
-
30
- @PluginMethod
31
- public void start(PluginCall call) {
32
- if (getPermissionState(CAMERA) != PermissionState.GRANTED) {
33
- requestPermissionForAlias(CAMERA, call, "cameraPermsCallback");
34
- return;
35
- }
36
- doStart(call);
37
- }
38
-
39
- @PermissionCallback
40
- private void cameraPermsCallback(PluginCall call) {
41
- if (getPermissionState(CAMERA) == PermissionState.GRANTED) {
42
- doStart(call);
43
- } else {
44
- call.reject("Camera permission denied", "PERMISSION_DENIED");
45
- }
46
- }
47
-
48
- private void doStart(PluginCall call) {
49
- boolean front = "front".equals(call.getString("position", "rear"));
50
- getActivity().runOnUiThread(() -> implementation.start(front, voidCallback(call)));
51
- }
52
-
53
- @PluginMethod
54
- public void stop(PluginCall call) {
55
- getActivity().runOnUiThread(() -> {
56
- implementation.stop();
57
- call.resolve();
58
- });
59
- }
60
-
61
- @PluginMethod
62
- public void flip(PluginCall call) {
63
- if (!ensureRunning(call)) return;
64
- getActivity().runOnUiThread(() -> implementation.flip(voidCallback(call)));
65
- }
66
-
67
- @PluginMethod
68
- public void capture(PluginCall call) {
69
- if (!ensureRunning(call)) return;
70
- int quality = Math.max(0, Math.min(100, call.getInt("quality", 85)));
71
- implementation.capture(
72
- quality,
73
- new CardScanner.Callback<>() {
74
- @Override
75
- public void onSuccess(CardScanner.Photo photo) {
76
- JSObject ret = new JSObject();
77
- ret.put("base64", photo.base64);
78
- ret.put("width", photo.width);
79
- ret.put("height", photo.height);
80
- call.resolve(ret);
81
- }
82
-
83
- @Override
84
- public void onError(String message, Exception e) {
85
- call.reject(message, e);
86
- }
87
- }
88
- );
89
- }
90
-
91
- @PluginMethod
92
- public void startDetection(PluginCall call) {
93
- if (!ensureRunning(call)) return;
94
- CardDetector.Options options = new CardDetector.Options();
95
- options.intervalMs = call.getInt("intervalMs", (int) options.intervalMs);
96
- options.stableFrames = call.getInt("stableFrames", options.stableFrames);
97
- options.includeImage = call.getBoolean("includeImage", options.includeImage);
98
- options.cardAspectRatio = call.getFloat("cardAspectRatio", options.cardAspectRatio);
99
- options.aspectTolerance = call.getFloat("aspectTolerance", options.aspectTolerance);
100
- options.minCardArea = call.getFloat("minCardArea", options.minCardArea);
101
- options.maxCardArea = call.getFloat("maxCardArea", options.maxCardArea);
102
- options.requireCard = call.getBoolean("requireCard", options.requireCard);
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);
106
-
107
- CardDetector detector = new CardDetector(options, result -> notifyListeners("cardDetected", result));
108
- getActivity().runOnUiThread(() -> {
109
- try {
110
- implementation.startDetection(detector);
111
- call.resolve();
112
- } catch (Exception e) {
113
- detector.close();
114
- call.reject("Failed to start detection: " + e.getMessage(), e);
115
- }
116
- });
117
- }
118
-
119
- @PluginMethod
120
- public void stopDetection(PluginCall call) {
121
- getActivity().runOnUiThread(() -> {
122
- implementation.stopDetection();
123
- call.resolve();
124
- });
125
- }
126
-
127
- @PluginMethod
128
- public void setTorch(PluginCall call) {
129
- if (!ensureRunning(call)) return;
130
- implementation.setTorch(Boolean.TRUE.equals(call.getBoolean("enabled", false)));
131
- call.resolve();
132
- }
133
-
134
- @PluginMethod
135
- public void setZoom(PluginCall call) {
136
- if (!ensureRunning(call)) return;
137
- Float ratio = call.getFloat("ratio");
138
- if (ratio == null) {
139
- call.reject("Missing 'ratio'");
140
- return;
141
- }
142
- implementation.setZoom(ratio);
143
- call.resolve();
144
- }
145
-
146
- private boolean ensureRunning(PluginCall call) {
147
- if (!implementation.isRunning()) {
148
- call.reject("Camera is not running", "NOT_RUNNING");
149
- return false;
150
- }
151
- return true;
152
- }
153
-
154
- private CardScanner.Callback<Void> voidCallback(PluginCall call) {
155
- return new CardScanner.Callback<>() {
156
- @Override
157
- public void onSuccess(Void result) {
158
- call.resolve();
159
- }
160
-
161
- @Override
162
- public void onError(String message, Exception e) {
163
- call.reject(message, e);
164
- }
165
- };
166
- }
167
- }
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,9 +429,69 @@
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
433
  "complexTypes": [],
434
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.",
457
+ "complexTypes": [],
458
+ "type": "number | undefined"
459
+ },
460
+ {
461
+ "name": "nameLines",
462
+ "tags": [
463
+ {
464
+ "text": "3",
465
+ "name": "default"
466
+ }
467
+ ],
468
+ "docs": "The card name is looked for only in the first N lines of text on the\r\ncard, top to bottom.",
469
+ "complexTypes": [],
470
+ "type": "number | undefined"
471
+ },
472
+ {
473
+ "name": "reservedWords",
474
+ "tags": [
475
+ {
476
+ "text": "['Basic', 'Stage', 'Stage 1', 'Stage 2', 'Trainer', 'Item', 'Supporter', 'Stadium', 'Energy', 'Special Energy', 'Basic Energy', 'Pokémon', 'Pokémon Tool', 'Tera']",
477
+ "name": "default"
478
+ }
479
+ ],
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\".",
481
+ "complexTypes": [],
482
+ "type": "string[] | undefined"
483
+ },
484
+ {
485
+ "name": "reservedPrefixes",
486
+ "tags": [
487
+ {
488
+ "text": "['Evolves from', 'HP']",
489
+ "name": "default"
490
+ }
491
+ ],
492
+ "docs": "Lines starting with any of these are never the card name.\r\nReplaces the defaults.",
493
+ "complexTypes": [],
494
+ "type": "string[] | undefined"
435
495
  }
436
496
  ]
437
497
  },
@@ -503,6 +563,13 @@
503
563
  "docs": "Base64 JPEG of the card, when `includeImage` is true.",
504
564
  "complexTypes": [],
505
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"
506
573
  }
507
574
  ]
508
575
  },
@@ -96,6 +96,40 @@ 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;
113
+ /**
114
+ * The card name is looked for only in the first N lines of text on the
115
+ * card, top to bottom.
116
+ * @default 3
117
+ */
118
+ nameLines?: number;
119
+ /**
120
+ * Words/phrases that are never the card name (card types, subtypes...).
121
+ * A line made only of these words is skipped, so "Trainer" or "Stage 1" are
122
+ * ignored but "Energy Retrieval" is still a name. Replaces the defaults.
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']
125
+ */
126
+ reservedWords?: string[];
127
+ /**
128
+ * Lines starting with any of these are never the card name.
129
+ * Replaces the defaults.
130
+ * @default ['Evolves from', 'HP']
131
+ */
132
+ reservedPrefixes?: string[];
99
133
  }
100
134
  /** Normalized rectangle (0-1). */
101
135
  export interface Box {
@@ -121,6 +155,11 @@ export interface CardDetectedEvent {
121
155
  cardBox?: Box;
122
156
  /** Base64 JPEG of the card, when `includeImage` is true. */
123
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;
124
163
  }
125
164
  export interface CardScannerPlugin {
126
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\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.2",
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",