@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/README.md CHANGED
@@ -102,8 +102,15 @@ type VinScannerEvent = {
102
102
  | `options.detection.preferBarcode` | boolean | Prefer barcode hits when selecting the best VIN | `true` |
103
103
  | `options.detection.validateChecksum` | boolean | Run VIN checksum validation | `true` |
104
104
  | `options.detection.emitDuplicates` | boolean | Emit unchanged payloads every frame | `false` for `'best'`, `true` for `'all'` |
105
+ | `options.detection.maxFrameRate` | number | Max FPS budget for frame processing (drops surplus frames to avoid blocking) | `30` |
105
106
  | `options.onResult` | `(result, event) => void` | Convenience callback when using `useVinScanner`; receives either the best candidate, all candidates, or `null` plus the raw event | `undefined` |
106
107
 
108
+ ### Advanced frame-processor controls
109
+
110
+ - **Per-frame plugin overrides:** both barcode and text frame processor plugins accept per-frame arguments, so you can dynamically change ML Kit barcode formats or text recognition language without reinitializing the plugin. Call `barcodeScanner.scanBarcodes(frame, { 'pdf-417': true })` or `textScanner.scanText(frame, { language: 'japanese' })` inside your worklet to override the resolved defaults for a single frame.
111
+ - **Shared bounding boxes:** native plugins now stream bounding box coordinates via zero-copy shared arrays, minimizing JSI serialization. The hook translates these buffers into the familiar `BoundingBox` structures before running VIN heuristics, so no API change is required.
112
+ - **Orientation-safe processing:** the native plugins forward VisionCamera’s frame orientation metadata directly into ML Kit as recommended in the [VisionCamera orientation guide](https://react-native-vision-camera.com/docs/guides/orientation), ensuring portrait VIN scans stay upright.
113
+
107
114
  ### Hook-only usage
108
115
 
109
116
  If you prefer to configure `react-native-vision-camera` yourself, grab the frame processor from the hook:
@@ -1,8 +1,6 @@
1
1
  package com.visioncamerabarcodescanner
2
2
 
3
3
  import android.media.Image
4
- import com.facebook.react.bridge.WritableNativeArray
5
- import com.facebook.react.bridge.WritableNativeMap
6
4
  import com.google.android.gms.tasks.Task
7
5
  import com.google.android.gms.tasks.Tasks
8
6
  import com.google.mlkit.vision.barcode.BarcodeScannerOptions
@@ -25,69 +23,101 @@ import com.google.mlkit.vision.barcode.common.Barcode.FORMAT_EAN_13
25
23
  import com.google.mlkit.vision.common.InputImage
26
24
  import com.mrousavy.camera.frameprocessors.Frame
27
25
  import com.mrousavy.camera.frameprocessors.FrameProcessorPlugin
26
+ import com.mrousavy.camera.frameprocessors.SharedArray
28
27
  import com.mrousavy.camera.frameprocessors.VisionCameraProxy
28
+ import java.nio.ByteOrder
29
+ import java.util.HashMap
29
30
 
30
- class VisionCameraBarcodeScannerModule(proxy : VisionCameraProxy, options: Map<String, Any>?): FrameProcessorPlugin() {
31
+ private const val BOX_STRIDE = 6
31
32
 
32
- override fun callback(frame: Frame, arguments: Map<String, Any>?): Any {
33
- try {
34
- val optionsBuilder = BarcodeScannerOptions.Builder()
35
- if (arguments?.get("code-128").toString().toBoolean()) optionsBuilder.setBarcodeFormats(FORMAT_CODE_128)
36
- else if (arguments?.get("code-39").toString().toBoolean()) optionsBuilder.setBarcodeFormats(FORMAT_CODE_39)
37
- else if (arguments?.get("code-93").toString().toBoolean()) optionsBuilder.setBarcodeFormats(FORMAT_CODE_93)
38
- else if (arguments?.get("codabar").toString().toBoolean()) optionsBuilder.setBarcodeFormats(FORMAT_CODABAR)
39
- else if (arguments?.get("ean-13").toString().toBoolean()) optionsBuilder.setBarcodeFormats(FORMAT_EAN_13)
40
- else if (arguments?.get("ean-8").toString().toBoolean()) optionsBuilder.setBarcodeFormats(FORMAT_EAN_8)
41
- else if (arguments?.get("itf").toString().toBoolean()) optionsBuilder.setBarcodeFormats(FORMAT_ITF)
42
- else if (arguments?.get("upc-e").toString().toBoolean()) optionsBuilder.setBarcodeFormats(FORMAT_UPC_E)
43
- else if (arguments?.get("upc-a").toString().toBoolean()) optionsBuilder.setBarcodeFormats(FORMAT_UPC_A)
44
- else if (arguments?.get("qr").toString().toBoolean()) optionsBuilder.setBarcodeFormats(FORMAT_QR_CODE)
45
- else if (arguments?.get("pdf-417").toString().toBoolean()) optionsBuilder.setBarcodeFormats(FORMAT_PDF417)
46
- else if (arguments?.get("aztec").toString().toBoolean()) optionsBuilder.setBarcodeFormats(FORMAT_AZTEC)
47
- else if (arguments?.get("data-matrix").toString().toBoolean()) optionsBuilder.setBarcodeFormats(FORMAT_DATA_MATRIX)
48
- else if (arguments?.get("all").toString().toBoolean()) optionsBuilder.setBarcodeFormats(FORMAT_ALL_FORMATS)
49
- else optionsBuilder.setBarcodeFormats(FORMAT_ALL_FORMATS)
33
+ class VisionCameraBarcodeScannerModule(
34
+ private val proxy : VisionCameraProxy,
35
+ options: Map<String, Any>?
36
+ ): FrameProcessorPlugin() {
37
+
38
+ private val initializerOptions: Map<String, Any> = options ?: emptyMap()
39
+
40
+ private fun mergedOptions(overrides: Map<String, Any>?): Map<String, Any> {
41
+ if (overrides == null || overrides.isEmpty()) {
42
+ return initializerOptions
43
+ }
44
+ val merged = HashMap(initializerOptions)
45
+ merged.putAll(overrides)
46
+ return merged
47
+ }
48
+
49
+ private fun buildScannerOptions(effective: Map<String, Any>): BarcodeScannerOptions {
50
+ val builder = BarcodeScannerOptions.Builder()
51
+ when {
52
+ effective["code-128"].toString().toBoolean() -> builder.setBarcodeFormats(FORMAT_CODE_128)
53
+ effective["code-39"].toString().toBoolean() -> builder.setBarcodeFormats(FORMAT_CODE_39)
54
+ effective["code-93"].toString().toBoolean() -> builder.setBarcodeFormats(FORMAT_CODE_93)
55
+ effective["codabar"].toString().toBoolean() -> builder.setBarcodeFormats(FORMAT_CODABAR)
56
+ effective["ean-13"].toString().toBoolean() -> builder.setBarcodeFormats(FORMAT_EAN_13)
57
+ effective["ean-8"].toString().toBoolean() -> builder.setBarcodeFormats(FORMAT_EAN_8)
58
+ effective["itf"].toString().toBoolean() -> builder.setBarcodeFormats(FORMAT_ITF)
59
+ effective["upc-e"].toString().toBoolean() -> builder.setBarcodeFormats(FORMAT_UPC_E)
60
+ effective["upc-a"].toString().toBoolean() -> builder.setBarcodeFormats(FORMAT_UPC_A)
61
+ effective["qr"].toString().toBoolean() -> builder.setBarcodeFormats(FORMAT_QR_CODE)
62
+ effective["pdf-417"].toString().toBoolean() -> builder.setBarcodeFormats(FORMAT_PDF417)
63
+ effective["aztec"].toString().toBoolean() -> builder.setBarcodeFormats(FORMAT_AZTEC)
64
+ effective["data-matrix"].toString().toBoolean() -> builder.setBarcodeFormats(FORMAT_DATA_MATRIX)
65
+ effective["all"].toString().toBoolean() -> builder.setBarcodeFormats(FORMAT_ALL_FORMATS)
66
+ else -> builder.setBarcodeFormats(FORMAT_ALL_FORMATS)
67
+ }
68
+ return builder.build()
69
+ }
50
70
 
51
- val scanner = BarcodeScanning.getClient(optionsBuilder.build())
71
+ override fun callback(frame: Frame, arguments: Map<String, Any>?): Any {
72
+ return try {
73
+ val options = mergedOptions(arguments)
74
+ val scanner = BarcodeScanning.getClient(buildScannerOptions(options))
52
75
  val mediaImage: Image = frame.image
53
76
  val image = InputImage.fromMediaImage(mediaImage, frame.imageProxy.imageInfo.rotationDegrees)
54
77
  val task: Task<List<Barcode>> = scanner.process(image)
55
78
  val barcodes: List<Barcode> = Tasks.await(task)
56
- val array = WritableNativeArray()
57
- for (barcode in barcodes) {
58
- val map = WritableNativeMap()
59
- val bounds = barcode.boundingBox
60
- if (bounds != null) {
61
- map.putInt("width",bounds.width())
62
- map.putInt("height",bounds.height())
63
- map.putInt("top",bounds.top)
64
- map.putInt("bottom",bounds.bottom)
65
- map.putInt("left",bounds.left)
66
- map.putInt("right",bounds.right)
67
- }
68
- val rawValue = barcode.rawValue
69
- map.putString("rawValue",rawValue)
70
- val valueType = barcode.valueType
71
- when (valueType) {
72
- Barcode.TYPE_WIFI -> {
73
- val ssid = barcode.wifi!!.ssid
74
- map.putString("ssid",ssid)
75
- val password = barcode.wifi!!.password
76
- map.putString("password",password)
77
- }
78
- Barcode.TYPE_URL -> {
79
- val title = barcode.url!!.title
80
- map.putString("title",title)
81
- val url = barcode.url!!.url
82
- map.putString("url",url)
79
+
80
+ val detections = ArrayList<Map<String, Any?>>()
81
+ val sharedArray =
82
+ if (barcodes.isNotEmpty()) SharedArray(proxy, barcodes.size * BOX_STRIDE * java.lang.Float.BYTES)
83
+ else null
84
+ val floatBuffer = sharedArray?.byteBuffer
85
+ ?.order(ByteOrder.nativeOrder())
86
+ ?.asFloatBuffer()
87
+
88
+ barcodes.forEachIndexed { index, barcode ->
89
+ val detection = HashMap<String, Any?>()
90
+ detection["rawValue"] = barcode.rawValue
91
+ detection["displayValue"] = barcode.displayValue
92
+ detection["format"] = barcode.format
93
+ detection["boxIndex"] = index
94
+ floatBuffer?.let { buffer ->
95
+ val bounds = barcode.boundingBox
96
+ val floatIndex = index * BOX_STRIDE
97
+ if (bounds != null) {
98
+ buffer.put(floatIndex, bounds.top.toFloat())
99
+ buffer.put(floatIndex + 1, bounds.bottom.toFloat())
100
+ buffer.put(floatIndex + 2, bounds.left.toFloat())
101
+ buffer.put(floatIndex + 3, bounds.right.toFloat())
102
+ buffer.put(floatIndex + 4, bounds.width().toFloat())
103
+ buffer.put(floatIndex + 5, bounds.height().toFloat())
104
+ } else {
105
+ for (offset in 0 until BOX_STRIDE) {
106
+ buffer.put(floatIndex + offset, -1f)
107
+ }
83
108
  }
84
109
  }
85
- array.pushMap(map)
110
+ detections.add(detection)
111
+ }
112
+
113
+ val response = HashMap<String, Any?>()
114
+ response["detections"] = detections
115
+ if (sharedArray != null) {
116
+ response["boxes"] = sharedArray
86
117
  }
87
- return array.toArrayList()
118
+ response
88
119
  } catch (e: Exception) {
89
- throw Exception("Error processing barcode scanner: $e ")
120
+ throw Exception("Error processing barcode scanner: $e")
90
121
  }
91
122
  }
92
-
93
123
  }
@@ -10,10 +10,10 @@ import com.visioncameratextrecognition.VisionCameraTextRecognitionModule
10
10
  class VisionCameraBarcodeScannerPackage : ReactPackage {
11
11
  companion object {
12
12
  init {
13
- FrameProcessorPluginRegistry.addFrameProcessorPlugin("scanBarcodes") {proxy,options ->
13
+ FrameProcessorPluginRegistry.addFrameProcessorPlugin("vinScannerBarcode") {proxy,options ->
14
14
  VisionCameraBarcodeScannerModule(proxy,options)
15
15
  }
16
- FrameProcessorPluginRegistry.addFrameProcessorPlugin("scanText") { proxy, options ->
16
+ FrameProcessorPluginRegistry.addFrameProcessorPlugin("vinScannerText") { proxy, options ->
17
17
  VisionCameraTextRecognitionModule(proxy, options)
18
18
  }
19
19
  }
@@ -1,8 +1,6 @@
1
1
  package com.visioncameratextrecognition
2
2
 
3
3
  import android.media.Image
4
- import com.facebook.react.bridge.WritableNativeArray
5
- import com.facebook.react.bridge.WritableNativeMap
6
4
  import com.google.android.gms.tasks.Task
7
5
  import com.google.android.gms.tasks.Tasks
8
6
  import com.google.mlkit.vision.common.InputImage
@@ -14,114 +12,149 @@ import com.google.mlkit.vision.text.devanagari.DevanagariTextRecognizerOptions
14
12
  import com.google.mlkit.vision.text.japanese.JapaneseTextRecognizerOptions
15
13
  import com.google.mlkit.vision.text.korean.KoreanTextRecognizerOptions
16
14
  import com.google.mlkit.vision.text.latin.TextRecognizerOptions
17
- import com.mrousavy.camera.frameprocessor.Frame
18
- import com.mrousavy.camera.frameprocessor.FrameProcessorPlugin
19
- import com.mrousavy.camera.frameprocessor.VisionCameraProxy
20
- import com.mrousavy.camera.types.Orientation
15
+ import com.mrousavy.camera.frameprocessors.Frame
16
+ import com.mrousavy.camera.frameprocessors.FrameProcessorPlugin
17
+ import com.mrousavy.camera.frameprocessors.SharedArray
18
+ import com.mrousavy.camera.frameprocessors.VisionCameraProxy
19
+ import java.nio.ByteOrder
20
+ import java.util.HashMap
21
21
  import java.util.ArrayList
22
22
 
23
+ private const val TEXT_BOX_STRIDE = 12
23
24
 
24
- class VisionCameraTextRecognitionModule(proxy : VisionCameraProxy, options: Map<String, Any>?): FrameProcessorPlugin() {
25
- private var recognizer: TextRecognizer = TextRecognition.getClient(TextRecognizerOptions.DEFAULT_OPTIONS)
26
- private var language = options?.get("language").toString()
25
+ class VisionCameraTextRecognitionModule(
26
+ private val proxy : VisionCameraProxy,
27
+ options: Map<String, Any>?
28
+ ): FrameProcessorPlugin() {
29
+ private var language = options?.get("language").toString().ifEmpty { "latin" }
30
+ private val recognizers = mutableMapOf<String, TextRecognizer>()
27
31
 
28
- init {
29
- recognizer = when (language) {
30
- "latin" -> TextRecognition.getClient(TextRecognizerOptions.DEFAULT_OPTIONS)
31
- "chinese" -> TextRecognition.getClient(ChineseTextRecognizerOptions.Builder().build())
32
- "devanagari" -> TextRecognition.getClient(DevanagariTextRecognizerOptions.Builder().build())
33
- "japanese" -> TextRecognition.getClient(JapaneseTextRecognizerOptions.Builder().build())
34
- "korean" -> TextRecognition.getClient(KoreanTextRecognizerOptions.Builder().build())
35
- else -> TextRecognition.getClient(TextRecognizerOptions.DEFAULT_OPTIONS)
32
+ private fun recognizerFor(languageCode: String): TextRecognizer {
33
+ return recognizers.getOrPut(languageCode) {
34
+ when (languageCode) {
35
+ "latin" -> TextRecognition.getClient(TextRecognizerOptions.DEFAULT_OPTIONS)
36
+ "chinese" -> TextRecognition.getClient(ChineseTextRecognizerOptions.Builder().build())
37
+ "devanagari" -> TextRecognition.getClient(DevanagariTextRecognizerOptions.Builder().build())
38
+ "japanese" -> TextRecognition.getClient(JapaneseTextRecognizerOptions.Builder().build())
39
+ "korean" -> TextRecognition.getClient(KoreanTextRecognizerOptions.Builder().build())
40
+ else -> TextRecognition.getClient(TextRecognizerOptions.DEFAULT_OPTIONS)
41
+ }
36
42
  }
37
43
  }
38
- override fun callback(frame: Frame, arguments: Map<String, Any>?): ArrayList<Any> {
44
+
45
+ override fun callback(frame: Frame, arguments: Map<String, Any>?): Any {
39
46
  try {
40
47
  val mediaImage: Image = frame.image
41
- val orientation : Orientation = frame.orientation
42
- val array = WritableNativeArray()
43
- val image = InputImage.fromMediaImage(mediaImage, orientation.toDegrees())
48
+ val rotationDegrees = frame.imageProxy.imageInfo.rotationDegrees
49
+ val requestedLanguage = arguments?.get("language")?.toString()?.ifEmpty { null }
50
+ val effectiveLanguage = requestedLanguage ?: language
51
+ val recognizer = recognizerFor(effectiveLanguage)
52
+ val image = InputImage.fromMediaImage(mediaImage, rotationDegrees)
44
53
  val task: Task<Text> = recognizer.process(image)
45
54
  val result: Text? = Tasks.await(task)
46
- val resultText = result?.text
47
- for (block in result?.textBlocks!!) {
48
- val map = WritableNativeMap()
49
- map.putString("resultText",resultText)
50
- val blockText = block.text
51
- map.putString("blockText",blockText)
52
- val blockCornerPoints = block.cornerPoints?.size
53
- if (blockCornerPoints != null) {
54
- map.putInt("size",blockCornerPoints)
55
- }
56
- val blockFrameBottom = block.boundingBox?.bottom
57
- val blockFrameTop = block.boundingBox?.top
58
- val blockFrameLeft = block.boundingBox?.left
59
- val blockFrameRight = block.boundingBox?.right
60
- if (blockFrameBottom != null) {
61
- map.putInt("blockFrameBottom",blockFrameBottom)
62
- }
63
- if (blockFrameLeft != null) {
64
- map.putInt("blockFrameLeft",blockFrameLeft)
65
- }
66
- if (blockFrameTop != null) {
67
- map.putInt("blockFrameTop",blockFrameTop)
55
+ val resultText = result?.text
56
+ val detections = ArrayList<Map<String, Any?>>()
57
+ val boxValues = ArrayList<FloatArray>()
58
+
59
+ result?.textBlocks?.forEach { block ->
60
+ val blockBounds = block.boundingBox
61
+ if (block.lines.isEmpty()) {
62
+ val detection = HashMap<String, Any?>()
63
+ detection["resultText"] = resultText
64
+ detection["blockText"] = block.text
65
+ detection["boxIndex"] = boxValues.size
66
+ detections.add(detection)
67
+ boxValues.add(
68
+ floatArrayOf(
69
+ blockBounds?.top?.toFloat() ?: -1f,
70
+ blockBounds?.bottom?.toFloat() ?: -1f,
71
+ blockBounds?.left?.toFloat() ?: -1f,
72
+ blockBounds?.right?.toFloat() ?: -1f,
73
+ -1f,
74
+ -1f,
75
+ -1f,
76
+ -1f,
77
+ -1f,
78
+ -1f,
79
+ -1f,
80
+ -1f,
81
+ )
82
+ )
83
+ }
84
+ block.lines.forEach { line ->
85
+ if (line.elements.isEmpty()) {
86
+ val detection = HashMap<String, Any?>()
87
+ detection["resultText"] = resultText
88
+ detection["blockText"] = block.text
89
+ detection["lineText"] = line.text
90
+ detection["boxIndex"] = boxValues.size
91
+ detections.add(detection)
92
+ boxValues.add(
93
+ floatArrayOf(
94
+ blockBounds?.top?.toFloat() ?: -1f,
95
+ blockBounds?.bottom?.toFloat() ?: -1f,
96
+ blockBounds?.left?.toFloat() ?: -1f,
97
+ blockBounds?.right?.toFloat() ?: -1f,
98
+ line.boundingBox?.top?.toFloat() ?: -1f,
99
+ line.boundingBox?.bottom?.toFloat() ?: -1f,
100
+ line.boundingBox?.left?.toFloat() ?: -1f,
101
+ line.boundingBox?.right?.toFloat() ?: -1f,
102
+ -1f,
103
+ -1f,
104
+ -1f,
105
+ -1f,
106
+ )
107
+ )
68
108
  }
69
- if (blockFrameRight != null) {
70
- map.putInt("blockFrameRight",blockFrameRight)
109
+ line.elements.forEach { element ->
110
+ val detection = HashMap<String, Any?>()
111
+ detection["resultText"] = resultText
112
+ detection["blockText"] = block.text
113
+ detection["lineText"] = line.text
114
+ detection["elementText"] = element.text
115
+ detection["boxIndex"] = boxValues.size
116
+ detections.add(detection)
117
+ boxValues.add(
118
+ floatArrayOf(
119
+ blockBounds?.top?.toFloat() ?: -1f,
120
+ blockBounds?.bottom?.toFloat() ?: -1f,
121
+ blockBounds?.left?.toFloat() ?: -1f,
122
+ blockBounds?.right?.toFloat() ?: -1f,
123
+ line.boundingBox?.top?.toFloat() ?: -1f,
124
+ line.boundingBox?.bottom?.toFloat() ?: -1f,
125
+ line.boundingBox?.left?.toFloat() ?: -1f,
126
+ line.boundingBox?.right?.toFloat() ?: -1f,
127
+ element.boundingBox?.top?.toFloat() ?: -1f,
128
+ element.boundingBox?.bottom?.toFloat() ?: -1f,
129
+ element.boundingBox?.left?.toFloat() ?: -1f,
130
+ element.boundingBox?.right?.toFloat() ?: -1f,
131
+ )
132
+ )
71
133
  }
72
- for (line in block.lines) {
73
- val lineText = line.text
74
- map.putString("lineText",lineText)
75
- val lineCornerPoints = line.cornerPoints?.size
76
- if (lineCornerPoints != null) {
77
- map.putInt("size",lineCornerPoints)
78
- }
79
- val lineFrameBottom = line.boundingBox?.bottom
80
- val lineFrameTop = line.boundingBox?.top
81
- val lineFrameLeft = line.boundingBox?.left
82
- val lineFrameRight = line.boundingBox?.right
83
- if (lineFrameBottom != null) {
84
- map.putInt("lineFrameBottom",lineFrameBottom)
85
- }
86
- if (lineFrameLeft != null) {
87
- map.putInt("lineFrameLeft",lineFrameLeft)
88
- }
89
- if (lineFrameTop != null) {
90
- map.putInt("lineFrameTop",lineFrameTop)
91
- }
92
- if (lineFrameRight != null) {
93
- map.putInt("lineFrameRight",lineFrameRight)
94
- }
95
- for (element in line.elements) {
96
- val elementText = element.text
97
- map.putString("elementText",elementText)
98
-
99
- val elementCornerPoints = line.cornerPoints?.size
100
- if (elementCornerPoints != null) {
101
- map.putInt("size",elementCornerPoints)
102
- }
103
- val elementFrameBottom = line.boundingBox?.bottom
104
- val elementFrameTop = line.boundingBox?.top
105
- val elementFrameLeft = line.boundingBox?.left
106
- val elementFrameRight = line.boundingBox?.right
107
- if (elementFrameBottom != null) {
108
- map.putInt("elementFrameBottom",elementFrameBottom)
109
- }
110
- if (elementFrameLeft != null) {
111
- map.putInt("elementFrameLeft",elementFrameLeft)
112
- }
113
- if (elementFrameTop != null) {
114
- map.putInt("elementFrameTop",elementFrameTop)
115
- }
116
- if (elementFrameRight != null) {
117
- map.putInt("elementFrameRight",elementFrameRight)
118
- }
119
- }
120
- array.pushMap(map)
134
+ }
135
+ }
121
136
 
137
+ val sharedArray =
138
+ if (boxValues.isNotEmpty()) SharedArray(proxy, boxValues.size * TEXT_BOX_STRIDE * java.lang.Float.BYTES)
139
+ else null
140
+ val floatBuffer = sharedArray?.byteBuffer
141
+ ?.order(ByteOrder.nativeOrder())
142
+ ?.asFloatBuffer()
143
+ floatBuffer?.let { buffer ->
144
+ boxValues.forEachIndexed { index, values ->
145
+ val floatIndex = index * TEXT_BOX_STRIDE
146
+ for (offset in 0 until TEXT_BOX_STRIDE) {
147
+ buffer.put(floatIndex + offset, values[offset])
122
148
  }
123
149
  }
124
- return array.toArrayList()
150
+ }
151
+
152
+ val response = HashMap<String, Any?>()
153
+ response["detections"] = detections
154
+ if (sharedArray != null) {
155
+ response["boxes"] = sharedArray
156
+ }
157
+ return response
125
158
  } catch (e: Exception) {
126
159
  throw Exception("Error processing text recognition: $e ")
127
160
  }
@@ -3,12 +3,14 @@
3
3
  #import <VisionCamera/FrameProcessorPlugin.h>
4
4
  #import <VisionCamera/FrameProcessorPluginRegistry.h>
5
5
  #import <VisionCamera/Frame.h>
6
+ #import <VisionCamera/SharedArray.h>
6
7
  #import <React/RCTBridgeModule.h>
7
8
  @import MLKitVision;
8
9
 
9
10
  @interface VisionCameraBarcodeScannerPlugin : FrameProcessorPlugin
10
11
 
11
12
  @property(nonatomic, strong) NSDictionary *configuration;
13
+ @property(nonatomic, strong) VisionCameraProxyHolder *proxyHolder;
12
14
 
13
15
  @end
14
16
 
@@ -28,13 +30,18 @@
28
30
  self = [super initWithProxy:proxy withOptions:options];
29
31
  if (self) {
30
32
  _configuration = options ?: @{};
33
+ _proxyHolder = proxy;
31
34
  }
32
35
  return self;
33
36
  }
34
37
 
35
38
  - (id _Nullable)callback:(Frame* _Nonnull)frame
36
39
  withArguments:(NSDictionary* _Nullable)arguments {
37
- NSDictionary *config = arguments ?: self.configuration ?: @{};
40
+ NSMutableDictionary *config = [self.configuration mutableCopy];
41
+ if (!config) {
42
+ config = [NSMutableDictionary dictionary];
43
+ }
44
+ [config addEntriesFromDictionary:arguments ?: @{}];
38
45
  MLKBarcodeFormat formatMask = [self formatMaskFromConfig:config];
39
46
  options = [[MLKBarcodeScannerOptions alloc] initWithFormats:formatMask];
40
47
 
@@ -42,9 +49,12 @@
42
49
 
43
50
  CMSampleBufferRef buffer = frame.buffer;
44
51
  UIImageOrientation orientation = frame.orientation;
52
+ // VisionCamera already normalizes orientation per https://react-native-vision-camera.com/docs/guides/orientation,
53
+ // so ML Kit just needs the frame’s orientation metadata instead of rotating pixels manually.
45
54
  MLKVisionImage *image = [[MLKVisionImage alloc] initWithBuffer:buffer];
46
55
  image.orientation = orientation;
47
- NSMutableArray *data = [NSMutableArray array];
56
+ NSMutableArray *detections = [NSMutableArray array];
57
+ __block NSDictionary *resultPayload = @{};
48
58
  dispatch_group_t dispatchGroup = dispatch_group_create();
49
59
  dispatch_group_enter(dispatchGroup);
50
60
  dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
@@ -52,54 +62,66 @@
52
62
  completion:^(NSArray<MLKBarcode *> *_Nullable barcodes,
53
63
  NSError *_Nullable error) {
54
64
  if (error != nil) {
55
- RCTResponseErrorBlock error;
65
+ dispatch_group_leave(dispatchGroup);
56
66
  return;
57
67
  }
58
- if (barcodes.count > 0) {
59
- for (MLKBarcode *barcode in barcodes) {
60
- NSMutableDictionary *obj = [[NSMutableDictionary alloc] init];
61
-
62
- NSString *displayValue = barcode.displayValue;
63
- obj[@"displayValue"] = displayValue;
64
- NSString *rawValue = barcode.rawValue;
65
- obj[@"rawValue"] = rawValue;
66
- obj[@"format"] = [self stringFromFormat:barcode.format];
67
- obj[@"left"] = @(CGRectGetMinX(barcode.frame));
68
- obj[@"top"] = @(CGRectGetMinY(barcode.frame));
69
- obj[@"right"] = @(CGRectGetMaxX(barcode.frame));
70
- obj[@"bottom"] = @(CGRectGetMaxY(barcode.frame));
71
- obj[@"width"] = @(barcode.frame.size.width);
72
- obj[@"height"] = @(barcode.frame.size.height);
73
-
74
- MLKBarcodeValueType valueType = barcode.valueType;
75
-
76
- switch (valueType) {
77
- case MLKBarcodeValueTypeWiFi:
78
- obj[@"ssid"] = barcode.wifi.ssid;
79
- obj[@"password"] = barcode.wifi.password;
80
- break;
81
- case MLKBarcodeValueTypeURL:
82
- obj[@"url"] = barcode.URL.url;
83
- obj[@"title"] = barcode.URL.title;
84
- break;
85
- default:
86
- break;
87
- }
88
- [data addObject:obj];
68
+ SharedArray *sharedBoxes = nil;
69
+ float *boxData = nil;
70
+ const NSUInteger count = barcodes.count;
71
+ if (count > 0 && self.proxyHolder != nil) {
72
+ sharedBoxes = [[SharedArray alloc] initWithProxy:self.proxyHolder
73
+ allocateWithSize:count * sizeof(float) * 6];
74
+ boxData = (float *)sharedBoxes.data;
75
+ }
76
+ [barcodes enumerateObjectsUsingBlock:^(MLKBarcode * _Nonnull barcode, NSUInteger idx, BOOL * _Nonnull stop) {
77
+ NSMutableDictionary *obj = [[NSMutableDictionary alloc] init];
78
+ obj[@"displayValue"] = barcode.displayValue ?: (id)kCFNull;
79
+ obj[@"rawValue"] = barcode.rawValue ?: (id)kCFNull;
80
+ obj[@"format"] = [self stringFromFormat:barcode.format];
81
+ obj[@"boxIndex"] = @(idx);
82
+
83
+ if (boxData != nil) {
84
+ CGRect frameRect = barcode.frame;
85
+ const NSUInteger baseIndex = idx * 6;
86
+ boxData[baseIndex] = CGRectGetMinY(frameRect);
87
+ boxData[baseIndex + 1] = CGRectGetMaxY(frameRect);
88
+ boxData[baseIndex + 2] = CGRectGetMinX(frameRect);
89
+ boxData[baseIndex + 3] = CGRectGetMaxX(frameRect);
90
+ boxData[baseIndex + 4] = CGRectGetWidth(frameRect);
91
+ boxData[baseIndex + 5] = CGRectGetHeight(frameRect);
92
+ }
89
93
 
94
+ MLKBarcodeValueType valueType = barcode.valueType;
95
+ switch (valueType) {
96
+ case MLKBarcodeValueTypeWiFi:
97
+ obj[@"ssid"] = barcode.wifi.ssid ?: (id)kCFNull;
98
+ obj[@"password"] = barcode.wifi.password ?: (id)kCFNull;
99
+ break;
100
+ case MLKBarcodeValueTypeURL:
101
+ obj[@"url"] = barcode.URL.url ?: (id)kCFNull;
102
+ obj[@"title"] = barcode.URL.title ?: (id)kCFNull;
103
+ break;
104
+ default:
105
+ break;
90
106
  }
107
+ [detections addObject:obj];
108
+ }];
91
109
 
110
+ NSMutableDictionary *payload = [NSMutableDictionary dictionary];
111
+ payload[@"detections"] = detections;
112
+ if (sharedBoxes != nil) {
113
+ payload[@"boxes"] = sharedBoxes;
92
114
  }
93
-
115
+ resultPayload = payload;
94
116
  dispatch_group_leave(dispatchGroup);
95
117
  }];
96
118
  });
97
119
 
98
120
  dispatch_group_wait(dispatchGroup, DISPATCH_TIME_FOREVER);
99
- return data;
121
+ return resultPayload;
100
122
  }
101
123
 
102
- VISION_EXPORT_FRAME_PROCESSOR(VisionCameraBarcodeScannerPlugin, scanBarcodes)
124
+ VISION_EXPORT_FRAME_PROCESSOR(VisionCameraBarcodeScannerPlugin, vinScannerBarcode)
103
125
 
104
126
  @end
105
127