@mleonard9/vin-scanner 0.2.5 → 0.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/README.md +7 -0
  2. package/android/src/main/java/com/visioncamerabarcodescanner/VisionCameraBarcodeScannerModule.kt +83 -53
  3. package/android/src/main/java/com/visioncamerabarcodescanner/VisionCameraBarcodeScannerPackage.kt +2 -2
  4. package/android/src/main/java/com/visioncameratextrecognition/VisionCameraTextRecognitionModule.kt +128 -95
  5. package/ios/VisionCameraBarcodeScanner.m +59 -37
  6. package/ios/VisionCameraTextRecognition.m +106 -56
  7. package/lib/commonjs/scanBarcodes.js +54 -6
  8. package/lib/commonjs/scanBarcodes.js.map +1 -1
  9. package/lib/commonjs/scanText.js +58 -4
  10. package/lib/commonjs/scanText.js.map +1 -1
  11. package/lib/commonjs/useVinScanner.js +11 -1
  12. package/lib/commonjs/useVinScanner.js.map +1 -1
  13. package/lib/commonjs/vinUtils.js +5 -3
  14. package/lib/commonjs/vinUtils.js.map +1 -1
  15. package/lib/module/scanBarcodes.js +54 -6
  16. package/lib/module/scanBarcodes.js.map +1 -1
  17. package/lib/module/scanText.js +58 -4
  18. package/lib/module/scanText.js.map +1 -1
  19. package/lib/module/useVinScanner.js +11 -1
  20. package/lib/module/useVinScanner.js.map +1 -1
  21. package/lib/module/vinUtils.js +5 -3
  22. package/lib/module/vinUtils.js.map +1 -1
  23. package/lib/typescript/src/scanBarcodes.d.ts.map +1 -1
  24. package/lib/typescript/src/scanText.d.ts.map +1 -1
  25. package/lib/typescript/src/types.d.ts +9 -2
  26. package/lib/typescript/src/types.d.ts.map +1 -1
  27. package/lib/typescript/src/useVinScanner.d.ts.map +1 -1
  28. package/lib/typescript/src/vinUtils.d.ts +1 -0
  29. package/lib/typescript/src/vinUtils.d.ts.map +1 -1
  30. package/package.json +1 -1
  31. package/src/scanBarcodes.ts +71 -8
  32. package/src/scanText.ts +72 -4
  33. package/src/types.ts +16 -2
  34. package/src/useVinScanner.ts +16 -1
  35. package/src/vinUtils.ts +5 -0
package/src/scanText.ts CHANGED
@@ -16,21 +16,89 @@ const LINKING_ERROR: string =
16
16
  '- You rebuilt the app after installing the package\n' +
17
17
  '- You are not using Expo Go\n';
18
18
 
19
+ const TEXT_BOX_STRIDE = 12;
20
+
21
+ const toFloat32Array = (input: unknown): Float32Array | undefined => {
22
+ 'worklet';
23
+ if (
24
+ input &&
25
+ typeof input === 'object' &&
26
+ 'byteLength' in (input as ArrayBuffer)
27
+ ) {
28
+ try {
29
+ return new Float32Array(input as ArrayBuffer);
30
+ } catch {
31
+ return undefined;
32
+ }
33
+ }
34
+ return undefined;
35
+ };
36
+
37
+ const normalizeTextDetections = (payload: unknown): TextDetection[] => {
38
+ 'worklet';
39
+ if (!payload) {
40
+ return [];
41
+ }
42
+ if (Array.isArray(payload)) {
43
+ return payload as TextDetection[];
44
+ }
45
+ const record = payload as {
46
+ detections?: Array<TextDetection & { boxIndex?: number }>;
47
+ boxes?: ArrayBuffer;
48
+ };
49
+ const detections = Array.isArray(record?.detections)
50
+ ? record.detections
51
+ : [];
52
+ const buffer = toFloat32Array(record?.boxes);
53
+ return detections.map((detection, index) => {
54
+ const output: TextDetection = { ...detection };
55
+ const boxIndex =
56
+ typeof detection?.boxIndex === 'number'
57
+ ? detection.boxIndex
58
+ : index;
59
+ if (buffer && boxIndex >= 0) {
60
+ const offset = boxIndex * TEXT_BOX_STRIDE;
61
+ if (offset + TEXT_BOX_STRIDE <= buffer.length) {
62
+ output.blockFrameTop = buffer[offset];
63
+ output.blockFrameBottom = buffer[offset + 1];
64
+ output.blockFrameLeft = buffer[offset + 2];
65
+ output.blockFrameRight = buffer[offset + 3];
66
+ output.lineFrameTop = buffer[offset + 4];
67
+ output.lineFrameBottom = buffer[offset + 5];
68
+ output.lineFrameLeft = buffer[offset + 6];
69
+ output.lineFrameRight = buffer[offset + 7];
70
+ output.elementFrameTop = buffer[offset + 8];
71
+ output.elementFrameBottom = buffer[offset + 9];
72
+ output.elementFrameLeft = buffer[offset + 10];
73
+ output.elementFrameRight = buffer[offset + 11];
74
+ }
75
+ }
76
+ return output;
77
+ });
78
+ };
79
+
19
80
  export function createTextRecognitionPlugin(
20
81
  options: TextRecognitionOptions
21
82
  ): TextRecognitionPlugin {
22
83
  const plugin: FrameProcessorPlugin | undefined =
23
- VisionCameraProxy.initFrameProcessorPlugin('scanText', {
84
+ VisionCameraProxy.initFrameProcessorPlugin('vinScannerText', {
24
85
  ...options,
25
86
  });
26
87
  if (!plugin) {
27
88
  throw new Error(LINKING_ERROR);
28
89
  }
29
90
  return {
30
- scanText: (frame: Frame): TextDetection[] => {
91
+ scanText: (
92
+ frame: Frame,
93
+ overrides?: TextRecognitionOptions
94
+ ): TextDetection[] => {
31
95
  'worklet';
32
- const result = plugin.call(frame);
33
- return (Array.isArray(result) ? result : []) as TextDetection[];
96
+ const args =
97
+ overrides && Object.keys(overrides).length > 0
98
+ ? { ...options, ...overrides }
99
+ : options;
100
+ const result = plugin.call(frame, args);
101
+ return normalizeTextDetections(result);
34
102
  },
35
103
  };
36
104
  }
package/src/types.ts CHANGED
@@ -25,6 +25,8 @@ export type BarcodeFormat =
25
25
 
26
26
  export type ScanBarcodeOptions = BarcodeFormat[];
27
27
 
28
+ export type NativeBarcodeFormatMap = Partial<Record<BarcodeFormat, boolean>>;
29
+
28
30
  export type BarcodeDetection = {
29
31
  width?: number;
30
32
  height?: number;
@@ -121,6 +123,12 @@ export type DetectionOptions = {
121
123
  emitDuplicates?: boolean;
122
124
  includePartialVins?: boolean; // Allow detection of incomplete VINs (13-16 chars)
123
125
  minPartialLength?: number; // Minimum length for partial VINs (default: 13)
126
+ /**
127
+ * Maximum frame rate (in FPS) at which the frame processor should execute.
128
+ * Frames that arrive faster than this budget are dropped to keep the
129
+ * worklet responsive when ML Kit takes longer than the camera cadence.
130
+ */
131
+ maxFrameRate?: number;
124
132
  };
125
133
 
126
134
  export type VinScannerOptions = {
@@ -139,11 +147,17 @@ export type CameraTypes = {
139
147
  } & CameraProps;
140
148
 
141
149
  export type BarcodeScannerPlugin = {
142
- scanBarcodes: (frame: Frame) => BarcodeDetection[] | undefined;
150
+ scanBarcodes: (
151
+ frame: Frame,
152
+ overrides?: NativeBarcodeFormatMap
153
+ ) => BarcodeDetection[] | undefined;
143
154
  };
144
155
 
145
156
  export type TextRecognitionPlugin = {
146
- scanText: (frame: Frame) => TextDetection[] | undefined;
157
+ scanText: (
158
+ frame: Frame,
159
+ overrides?: TextRecognitionOptions
160
+ ) => TextDetection[] | undefined;
147
161
  };
148
162
 
149
163
  export type WorkletPayload = {
@@ -14,6 +14,7 @@ import {
14
14
  export function useVinScanner(options?: VinScannerOptions) {
15
15
  const resolvedOptions = useMemo(() => resolveOptions(options), [options]);
16
16
  const lastFingerprintRef = useRef<string | null | undefined>(undefined);
17
+ const lastFrameTimestampRef = useRef(0);
17
18
 
18
19
  const barcodeScanner = useMemo(() => {
19
20
  if (!resolvedOptions.barcode.enabled) {
@@ -43,12 +44,26 @@ export function useVinScanner(options?: VinScannerOptions) {
43
44
  const frameProcessor = useFrameProcessor(
44
45
  (frame: Frame) => {
45
46
  'worklet';
47
+ const now =
48
+ typeof frame?.timestamp === 'number' ? frame.timestamp : Date.now();
49
+ const maxFps = resolvedOptions.detection.maxFrameRate;
50
+ if (maxFps > 0) {
51
+ const minInterval = 1000 / maxFps;
52
+ if (
53
+ lastFrameTimestampRef.current > 0 &&
54
+ now - lastFrameTimestampRef.current < minInterval
55
+ ) {
56
+ return;
57
+ }
58
+ lastFrameTimestampRef.current = now;
59
+ }
60
+
46
61
  const barcodes = barcodeScanner ? barcodeScanner.scanBarcodes(frame) : [];
47
62
  const textBlocks = textScanner ? textScanner.scanText(frame) : [];
48
63
  const payload = {
49
64
  barcodes: barcodes ?? [],
50
65
  textBlocks: textBlocks ?? [],
51
- timestamp: Date.now(),
66
+ timestamp: now,
52
67
  };
53
68
  const candidates = buildVinCandidates(payload, resolvedOptions);
54
69
  const best = pickBestCandidate(candidates, resolvedOptions);
package/src/vinUtils.ts CHANGED
@@ -38,6 +38,7 @@ export type ResolvedVinScannerOptions = {
38
38
  resultMode: VinResultMode;
39
39
  includePartialVins: boolean;
40
40
  minPartialLength: number;
41
+ maxFrameRate: number;
41
42
  };
42
43
  };
43
44
 
@@ -57,6 +58,7 @@ const DEFAULT_RESOLVED_OPTIONS: ResolvedVinScannerOptions = {
57
58
  emitDuplicates: false,
58
59
  includePartialVins: false,
59
60
  minPartialLength: 13,
61
+ maxFrameRate: 30,
60
62
  },
61
63
  };
62
64
 
@@ -101,6 +103,9 @@ export const resolveOptions = (
101
103
  minPartialLength:
102
104
  options?.detection?.minPartialLength ??
103
105
  DEFAULT_RESOLVED_OPTIONS.detection.minPartialLength,
106
+ maxFrameRate:
107
+ options?.detection?.maxFrameRate ??
108
+ DEFAULT_RESOLVED_OPTIONS.detection.maxFrameRate,
104
109
  },
105
110
  };
106
111
  };