react-native-yolo 0.0.5 → 0.0.8

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 (46) hide show
  1. package/Yolo.podspec +3 -0
  2. package/android/src/main/java/com/yolo/HybridYolo.kt +12 -235
  3. package/android/src/main/java/com/yolo/HybridYoloModel.kt +259 -0
  4. package/android/src/main/java/com/yolo/utils/BitmapOrientationFixer.kt +8 -1
  5. package/ios/HybridYolo.swift +52 -10
  6. package/ios/HybridYoloModel.swift +250 -0
  7. package/ios/Loader/YoloModelLoader.swift +139 -0
  8. package/ios/Utils/BitmapOrientationFixer.swift +64 -0
  9. package/ios/Utils/ContextProvider.swift +45 -0
  10. package/ios/Utils/FrameJpegConverter.swift +41 -0
  11. package/ios/Utils/FrameValidator.swift +40 -0
  12. package/ios/Utils/Nv12JpegEncoder.swift +58 -0
  13. package/ios/Utils/Yuv420ToNv12Converter.swift +71 -0
  14. package/lib/commonjs/index.js +4 -3
  15. package/lib/commonjs/index.js.map +1 -1
  16. package/lib/module/index.js +4 -3
  17. package/lib/module/index.js.map +1 -1
  18. package/lib/typescript/src/index.d.ts +12 -3
  19. package/lib/typescript/src/index.d.ts.map +1 -1
  20. package/lib/typescript/src/specs/yolo.nitro.d.ts +10 -6
  21. package/lib/typescript/src/specs/yolo.nitro.d.ts.map +1 -1
  22. package/nitrogen/generated/android/Yolo+autolinking.cmake +2 -0
  23. package/nitrogen/generated/android/YoloOnLoad.cpp +2 -0
  24. package/nitrogen/generated/android/c++/JHybridYoloModelSpec.cpp +78 -0
  25. package/nitrogen/generated/android/c++/JHybridYoloModelSpec.hpp +64 -0
  26. package/nitrogen/generated/android/c++/JHybridYoloSpec.cpp +9 -32
  27. package/nitrogen/generated/android/c++/JHybridYoloSpec.hpp +1 -3
  28. package/nitrogen/generated/android/kotlin/com/margelo/nitro/yolo/HybridYoloModelSpec.kt +59 -0
  29. package/nitrogen/generated/android/kotlin/com/margelo/nitro/yolo/HybridYoloSpec.kt +1 -9
  30. package/nitrogen/generated/ios/Yolo-Swift-Cxx-Bridge.cpp +17 -0
  31. package/nitrogen/generated/ios/Yolo-Swift-Cxx-Bridge.hpp +48 -31
  32. package/nitrogen/generated/ios/Yolo-Swift-Cxx-Umbrella.hpp +5 -0
  33. package/nitrogen/generated/ios/c++/HybridYoloModelSpecSwift.cpp +11 -0
  34. package/nitrogen/generated/ios/c++/HybridYoloModelSpecSwift.hpp +97 -0
  35. package/nitrogen/generated/ios/c++/HybridYoloSpecSwift.hpp +6 -24
  36. package/nitrogen/generated/ios/swift/HybridYoloModelSpec.swift +57 -0
  37. package/nitrogen/generated/ios/swift/HybridYoloModelSpec_cxx.swift +160 -0
  38. package/nitrogen/generated/ios/swift/HybridYoloSpec.swift +1 -3
  39. package/nitrogen/generated/ios/swift/HybridYoloSpec_cxx.swift +8 -38
  40. package/nitrogen/generated/shared/c++/HybridYoloModelSpec.cpp +22 -0
  41. package/nitrogen/generated/shared/c++/HybridYoloModelSpec.hpp +69 -0
  42. package/nitrogen/generated/shared/c++/HybridYoloSpec.cpp +0 -2
  43. package/nitrogen/generated/shared/c++/HybridYoloSpec.hpp +5 -8
  44. package/package.json +1 -1
  45. package/src/index.ts +14 -4
  46. package/src/specs/yolo.nitro.ts +12 -3
package/Yolo.podspec CHANGED
@@ -27,5 +27,8 @@ Pod::Spec.new do |s|
27
27
 
28
28
  s.dependency 'React-jsi'
29
29
  s.dependency 'React-callinvoker'
30
+ s.dependency 'VisionCamera'
31
+ s.dependency 'TensorFlowLiteSwift'
32
+
30
33
  install_modules_dependencies(s)
31
34
  end
@@ -1,265 +1,42 @@
1
1
  package com.yolo
2
2
 
3
- import android.net.Uri
3
+ import android.util.Base64
4
4
  import android.util.Log
5
- import com.margelo.nitro.NitroModules
6
- import com.margelo.nitro.yolo.HybridYoloSpec
7
- import java.io.File
8
- import java.io.RandomAccessFile
9
- import java.net.URL
10
- import java.nio.MappedByteBuffer
11
- import java.nio.channels.FileChannel
12
- import org.tensorflow.lite.Interpreter
13
- import org.tensorflow.lite.support.common.FileUtil
14
- import yolo.com.loader.YoloModelLoader
5
+
15
6
  import com.margelo.nitro.camera.HybridFrameSpec
7
+ import com.margelo.nitro.yolo.HybridYoloModelSpec
8
+ import com.margelo.nitro.yolo.HybridYoloSpec
16
9
 
17
- import android.util.Base64
18
10
  import com.yolo.utils.FrameJpegConverter
19
11
  import com.yolo.utils.FrameValidator
20
12
 
21
- import com.margelo.nitro.yolo.Detection
22
- import com.margelo.nitro.yolo.BoundingBox
23
-
24
- import java.nio.ByteBuffer
25
- import java.nio.ByteOrder
26
- import org.tensorflow.lite.DataType
27
- import kotlin.math.roundToInt
28
-
29
13
 
30
14
  class HybridYolo : HybridYoloSpec() {
31
15
  companion object {
32
16
  private const val TAG = "YOLO_TAG"
33
17
  }
34
- private var interpreter: Interpreter? = null
35
-
36
- private var inputBuffer: ByteBuffer? = null
37
- private var inputWidth = 0
38
- private var inputHeight = 0
39
- private var inputDataType: DataType? = null
40
-
41
- private val modelLoader = YoloModelLoader()
42
-
43
- override fun sum(num1: Double, num2: Double): Double {
44
- return num1 + num2
45
- }
46
-
47
- override fun loadModel(modelPath: String) {
48
- val context =
49
- NitroModules.applicationContext ?: throw IllegalStateException("Context is null")
50
-
51
- Log.d(TAG, "Trying to load: $modelPath")
52
-
53
- try {
54
- val modelBuffer = modelLoader.load(modelPath)
55
-
56
- interpreter?.close()
57
- interpreter = Interpreter(modelBuffer)
58
-
59
- val inputTensor = interpreter!!.getInputTensor(0)
60
- val shape = inputTensor.shape()
61
-
62
- inputHeight = shape[1]
63
- inputWidth = shape[2]
64
- inputDataType = inputTensor.dataType()
65
- inputBuffer = modelLoader.makeInputBuffer(interpreter!!)
66
-
67
- val outputTensor = interpreter!!.getOutputTensor(0)
68
- Log.d(TAG, "✅ YOLO model loaded")
69
- Log.d(TAG, "📥 Input shape: ${inputTensor.shape().contentToString()}")
70
- Log.d(TAG, "📤 Output shape: ${outputTensor.shape().contentToString()}")
71
- Log.d(TAG, "📥 Input type: ${inputTensor.dataType()}")
72
- Log.d(TAG, "📤 Output type: ${outputTensor.dataType()}")
73
-
74
- Log.d(TAG, "Input shape=${shape.contentToString()} type=$inputDataType")
75
- Log.d(TAG, "✅ Model loaded successfully!")
76
- } catch (e: Exception) {
77
- Log.e(TAG, "❌ Failed to load model: ${e.message}", e)
78
- }
18
+ override fun loadModel(modelPath: String): HybridYoloModelSpec {
19
+ Log.d(TAG, "Trying to load model object: $modelPath")
20
+ return HybridYoloModel(modelPath)
79
21
  }
80
22
  override fun frameToBase64(frame: HybridFrameSpec): String {
23
+ Log.d(TAG, "Trying to convert frame to base64")
81
24
  return try {
25
+ Log.d(TAG, "frameToBase64: frame: $frame")
82
26
  if (!FrameValidator.isValidYuv(frame)) return ""
27
+ Log.d(TAG, "frameToBase64: frame is valid YUV")
83
28
 
84
29
  val jpegBytes = FrameJpegConverter.toJpegBytes(
85
30
  frame = frame,
86
31
  quality = 80
87
32
  )
33
+ Log.d(TAG, "frameToBase64: jpegBytes size: ${jpegBytes.size}")
88
34
 
89
35
  Base64.encodeToString(jpegBytes, Base64.NO_WRAP)
36
+
90
37
  } catch (e: Exception) {
91
38
  Log.e(TAG, "❌ frameToBase64 failed", e)
92
39
  ""
93
40
  }
94
41
  }
95
-
96
- override fun detect(frame: HybridFrameSpec): Array<Detection> {
97
- val localInterpreter = interpreter ?: run {
98
- Log.e(TAG, "❌ Model is not loaded. Please call loadModel() first.")
99
- return emptyArray()
100
- }
101
-
102
- if (!FrameValidator.isValidYuv(frame)) {
103
- Log.e(TAG, "❌ Invalid frame provided for detection.")
104
- return emptyArray()
105
- }
106
-
107
- val input = inputBuffer ?: return emptyArray()
108
-
109
- fillInputFromYuvFrame(
110
- frame = frame,
111
- input = input,
112
- dstWidth = inputWidth,
113
- dstHeight = inputHeight,
114
- dataType = inputDataType ?: DataType.FLOAT32
115
- )
116
-
117
- val output = Array(1) { Array(300) { FloatArray(6) } }
118
-
119
- localInterpreter.run(input, output)
120
-
121
- val detections = parseNmsOutput(output, confidenceThreshold = 0.5f)
122
- if (detections.isEmpty()) {
123
- val best = output[0].maxByOrNull { it[4] }
124
- Log.d(TAG, "No detections. Best row=${best?.contentToString()}")
125
- } else {
126
- Log.d(TAG, "✅ Detections: ${detections.size}")
127
- }
128
-
129
-
130
- return detections.toTypedArray()
131
- }
132
-
133
- private fun parseNmsOutput(
134
- output: Array<Array<FloatArray>>,
135
- confidenceThreshold: Float = 0.5f
136
- ): List<Detection> {
137
- val detections = mutableListOf<Detection>()
138
-
139
- val batchMatrix = output[0]
140
-
141
- for (i in batchMatrix.indices) {
142
- val row = batchMatrix[i]
143
-
144
- val score = row[4]
145
- if (score < confidenceThreshold) continue
146
-
147
- val x1 = row[0].toDouble()
148
- val y1 = row[1].toDouble()
149
- val x2 = row[2].toDouble()
150
- val y2 = row[3].toDouble()
151
- val classId = row[5].toDouble()
152
-
153
- detections.add(
154
- Detection(
155
- boundingBox = BoundingBox(
156
- x1 = x1,
157
- y1 = y1,
158
- x2 = x2,
159
- y2 = y2
160
- ),
161
- score = score.toDouble(),
162
- classId = classId
163
- )
164
- )
165
- }
166
-
167
- return detections
168
- }
169
-
170
-
171
-
172
- private fun fillInputFromYuvFrame(
173
- frame: HybridFrameSpec,
174
- input: ByteBuffer,
175
- dstWidth: Int,
176
- dstHeight: Int,
177
- dataType: DataType
178
- ) {
179
- val srcWidth = frame.width.toInt()
180
- val srcHeight = frame.height.toInt()
181
-
182
- val planes = frame.getPlanes()
183
-
184
- val yPlane = planes[0]
185
- val uPlane = planes[1]
186
- val vPlane = planes[2]
187
-
188
- val yBytes = yPlane.getPixelBuffer().toByteArray()
189
- val uBytes = uPlane.getPixelBuffer().toByteArray()
190
- val vBytes = vPlane.getPixelBuffer().toByteArray()
191
-
192
- val yRowStride = yPlane.bytesPerRow.roundToInt()
193
- val uRowStride = uPlane.bytesPerRow.roundToInt()
194
- val vRowStride = vPlane.bytesPerRow.roundToInt()
195
-
196
- input.rewind()
197
-
198
- for (dy in 0 until dstHeight) {
199
- // val sy = dy * srcHeight / dstHeight
200
-
201
- for (dx in 0 until dstWidth) {
202
- val srcX = dy * srcWidth / dstHeight
203
- val srcY = srcHeight - 1 - (dx * srcHeight / dstWidth)
204
-
205
- val yIndex = srcY * yRowStride + srcX
206
-
207
- val uvX = srcX / 2
208
- val uvY = srcY / 2
209
-
210
- val uIndex = uvY * uRowStride + uvX * 2
211
- val vIndex = uvY * vRowStride + uvX * 2
212
-
213
- if (
214
- yIndex >= yBytes.size ||
215
- uIndex >= uBytes.size ||
216
- vIndex >= vBytes.size
217
- ) {
218
- continue
219
- }
220
-
221
- val y = yBytes[yIndex].toInt() and 0xFF
222
- val u = uBytes[uIndex].toInt() and 0xFF
223
- val v = vBytes[vIndex].toInt() and 0xFF
224
-
225
- val rFloat = y + 1.402f * (v - 128)
226
- val gFloat = y - 0.344136f * (u - 128) - 0.714136f * (v - 128)
227
- val bFloat = y + 1.772f * (u - 128)
228
-
229
- val r = rFloat.roundToInt().coerceIn(0, 255)
230
- val g = gFloat.roundToInt().coerceIn(0, 255)
231
- val b = bFloat.roundToInt().coerceIn(0, 255)
232
-
233
- when (dataType) {
234
- DataType.FLOAT32 -> {
235
- input.putFloat(r / 255f)
236
- input.putFloat(g / 255f)
237
- input.putFloat(b / 255f)
238
- }
239
-
240
- DataType.UINT8 -> {
241
- input.put(r.toByte())
242
- input.put(g.toByte())
243
- input.put(b.toByte())
244
- }
245
-
246
- else -> error("Unsupported input type: $dataType")
247
- }
248
- }
249
- }
250
- input.rewind()
251
- }
252
-
253
- private fun yuvToRgb(y: Int, u: Int, v: Int): IntArray {
254
- val yf = y.toFloat()
255
- val uf = u.toFloat() - 128f
256
- val vf = v.toFloat() - 128f
257
-
258
- val r = (yf + 1.402f * vf).roundToInt().coerceIn(0, 255)
259
- val g = (yf - 0.344136f * uf - 0.714136f * vf).roundToInt().coerceIn(0, 255)
260
- val b = (yf + 1.772f * uf).roundToInt().coerceIn(0, 255)
261
-
262
- return intArrayOf(r, g, b)
263
- }
264
-
265
42
  }
@@ -0,0 +1,259 @@
1
+ package com.yolo
2
+
3
+ import android.util.Log
4
+ import com.margelo.nitro.yolo.HybridYoloModelSpec
5
+ import com.margelo.nitro.yolo.Detection
6
+ import com.margelo.nitro.yolo.BoundingBox
7
+ import com.margelo.nitro.camera.HybridFrameSpec
8
+ import com.yolo.utils.FrameValidator
9
+ import java.nio.ByteBuffer
10
+ import org.tensorflow.lite.DataType
11
+ import org.tensorflow.lite.Interpreter
12
+ import yolo.com.loader.YoloModelLoader
13
+ import kotlin.math.roundToInt
14
+ import com.margelo.nitro.camera.CameraOrientation
15
+
16
+ class HybridYoloModel(
17
+ modelPath: String
18
+ ) : HybridYoloModelSpec() {
19
+
20
+ companion object {
21
+ private const val TAG = "YOLO_MODEL_TAG"
22
+ }
23
+
24
+ private val modelLoader = YoloModelLoader()
25
+
26
+ private var interpreter: Interpreter? = null
27
+ private var inputBuffer: ByteBuffer? = null
28
+ private var inputWidth = 0
29
+ private var inputHeight = 0
30
+ private var inputDataType: DataType? = null
31
+
32
+ private val lock = Any()
33
+
34
+ init {
35
+ load(modelPath)
36
+ }
37
+
38
+ private fun load(modelPath: String) {
39
+ try {
40
+ val modelBuffer = modelLoader.load(modelPath)
41
+
42
+ interpreter = Interpreter(modelBuffer)
43
+
44
+ val localInterpreter = interpreter!!
45
+
46
+ val inputTensor = localInterpreter.getInputTensor(0)
47
+ val outputTensor = localInterpreter.getOutputTensor(0)
48
+
49
+ val shape = inputTensor.shape()
50
+
51
+ inputHeight = shape[1]
52
+ inputWidth = shape[2]
53
+ inputDataType = inputTensor.dataType()
54
+ inputBuffer = modelLoader.makeInputBuffer(localInterpreter)
55
+
56
+ Log.d(TAG, "✅ YOLO model instance loaded")
57
+ Log.d(TAG, "📥 Input shape: ${inputTensor.shape().contentToString()}")
58
+ Log.d(TAG, "📤 Output shape: ${outputTensor.shape().contentToString()}")
59
+ Log.d(TAG, "📥 Input type: ${inputTensor.dataType()}")
60
+ Log.d(TAG, "📤 Output type: ${outputTensor.dataType()}")
61
+ } catch (e: Exception) {
62
+ Log.e(TAG, "❌ Failed to load model instance", e)
63
+ }
64
+ }
65
+
66
+ override fun detect(frame: HybridFrameSpec): Array<Detection> {
67
+ val localInterpreter = interpreter ?: run {
68
+ Log.e(TAG, "❌ This model instance is not loaded.")
69
+ return emptyArray()
70
+ }
71
+
72
+ if (!FrameValidator.isValidYuv(frame)) {
73
+ Log.e(TAG, "❌ Invalid frame provided for detection.")
74
+ return emptyArray()
75
+ }
76
+
77
+ val input = inputBuffer ?: return emptyArray()
78
+
79
+ synchronized(lock) {
80
+ input.rewind()
81
+
82
+ fillInputFromYuvFrame(
83
+ frame = frame,
84
+ input = input,
85
+ dstWidth = inputWidth,
86
+ dstHeight = inputHeight,
87
+ dataType = inputDataType ?: DataType.FLOAT32
88
+ )
89
+
90
+ val output = Array(1) { Array(300) { FloatArray(6) } }
91
+
92
+ localInterpreter.run(input, output)
93
+
94
+ val detections = parseNmsOutput(output, confidenceThreshold = 0.5f)
95
+
96
+ return detections.toTypedArray()
97
+ }
98
+ }
99
+
100
+ override fun close() {
101
+ interpreter?.close()
102
+ interpreter = null
103
+ inputBuffer = null
104
+ Log.d(TAG, "🧹 YOLO model disposed")
105
+ }
106
+
107
+ private fun parseNmsOutput(
108
+ output: Array<Array<FloatArray>>,
109
+ confidenceThreshold: Float = 0.5f
110
+ ): List<Detection> {
111
+ val detections = mutableListOf<Detection>()
112
+
113
+ for (row in output[0]) {
114
+ val score = row[4]
115
+ if (score < confidenceThreshold) continue
116
+
117
+ detections.add(
118
+ Detection(
119
+ boundingBox = BoundingBox(
120
+ x1 = row[0].toDouble(),
121
+ y1 = row[1].toDouble(),
122
+ x2 = row[2].toDouble(),
123
+ y2 = row[3].toDouble()
124
+ ),
125
+ score = score.toDouble(),
126
+ classId = row[5].toDouble()
127
+ )
128
+ )
129
+ }
130
+
131
+ return detections
132
+ }
133
+
134
+ private fun fillInputFromYuvFrame(
135
+ frame: HybridFrameSpec,
136
+ input: ByteBuffer,
137
+ dstWidth: Int,
138
+ dstHeight: Int,
139
+ dataType: DataType
140
+ ) {
141
+ val srcWidth = frame.width.toInt()
142
+ val srcHeight = frame.height.toInt()
143
+
144
+ val planes = frame.getPlanes()
145
+
146
+ val yPlane = planes[0]
147
+ val uPlane = planes[1]
148
+ val vPlane = planes[2]
149
+
150
+ val yBytes = yPlane.getPixelBuffer().toByteArray()
151
+ val uBytes = uPlane.getPixelBuffer().toByteArray()
152
+ val vBytes = vPlane.getPixelBuffer().toByteArray()
153
+
154
+ val yRowStride = yPlane.bytesPerRow.roundToInt()
155
+ val uRowStride = uPlane.bytesPerRow.roundToInt()
156
+ val vRowStride = vPlane.bytesPerRow.roundToInt()
157
+
158
+ input.rewind()
159
+
160
+ for (dy in 0 until dstHeight) {
161
+ for (dx in 0 until dstWidth) {
162
+ val (srcX, srcY) = mapModelPixelToFramePixel(
163
+ dx = dx,
164
+ dy = dy,
165
+ dstWidth = dstWidth,
166
+ dstHeight = dstHeight,
167
+ srcWidth = srcWidth,
168
+ srcHeight = srcHeight,
169
+ orientation = frame.orientation
170
+ )
171
+
172
+ val yIndex = srcY * yRowStride + srcX
173
+
174
+ val uvX = srcX / 2
175
+ val uvY = srcY / 2
176
+
177
+ val uIndex = uvY * uRowStride + uvX * 2
178
+ val vIndex = uvY * vRowStride + uvX * 2
179
+
180
+ if (
181
+ yIndex >= yBytes.size ||
182
+ uIndex >= uBytes.size ||
183
+ vIndex >= vBytes.size
184
+ ) {
185
+ continue
186
+ }
187
+
188
+ val y = yBytes[yIndex].toInt() and 0xFF
189
+ val u = uBytes[uIndex].toInt() and 0xFF
190
+ val v = vBytes[vIndex].toInt() and 0xFF
191
+
192
+ val rFloat = y + 1.402f * (v - 128)
193
+ val gFloat = y - 0.344136f * (u - 128) - 0.714136f * (v - 128)
194
+ val bFloat = y + 1.772f * (u - 128)
195
+
196
+ val r = rFloat.roundToInt().coerceIn(0, 255)
197
+ val g = gFloat.roundToInt().coerceIn(0, 255)
198
+ val b = bFloat.roundToInt().coerceIn(0, 255)
199
+
200
+ when (dataType) {
201
+ DataType.FLOAT32 -> {
202
+ input.putFloat(r / 255f)
203
+ input.putFloat(g / 255f)
204
+ input.putFloat(b / 255f)
205
+ }
206
+
207
+ DataType.UINT8 -> {
208
+ input.put(r.toByte())
209
+ input.put(g.toByte())
210
+ input.put(b.toByte())
211
+ }
212
+
213
+ else -> error("Unsupported input type: $dataType")
214
+ }
215
+ }
216
+ }
217
+
218
+ input.rewind()
219
+ }
220
+
221
+ private fun mapModelPixelToFramePixel(
222
+ dx: Int,
223
+ dy: Int,
224
+ dstWidth: Int,
225
+ dstHeight: Int,
226
+ srcWidth: Int,
227
+ srcHeight: Int,
228
+ orientation: CameraOrientation
229
+ ): Pair<Int, Int> {
230
+ val nx = dx.toFloat() / dstWidth
231
+ val ny = dy.toFloat() / dstHeight
232
+
233
+ return when (orientation) {
234
+ CameraOrientation.UP -> {
235
+ val srcX = (nx * srcWidth).toInt()
236
+ val srcY = (ny * srcHeight).toInt()
237
+ srcX.coerceIn(0, srcWidth - 1) to srcY.coerceIn(0, srcHeight - 1)
238
+ }
239
+
240
+ CameraOrientation.DOWN -> {
241
+ val srcX = ((1f - nx) * srcWidth).toInt()
242
+ val srcY = ((1f - ny) * srcHeight).toInt()
243
+ srcX.coerceIn(0, srcWidth - 1) to srcY.coerceIn(0, srcHeight - 1)
244
+ }
245
+
246
+ CameraOrientation.LEFT -> {
247
+ val srcX = (ny * srcWidth).toInt()
248
+ val srcY = ((1f - nx) * srcHeight).toInt()
249
+ srcX.coerceIn(0, srcWidth - 1) to srcY.coerceIn(0, srcHeight - 1)
250
+ }
251
+
252
+ CameraOrientation.RIGHT -> {
253
+ val srcX = ((1f - ny) * srcWidth).toInt()
254
+ val srcY = (nx * srcHeight).toInt()
255
+ srcX.coerceIn(0, srcWidth - 1) to srcY.coerceIn(0, srcHeight - 1)
256
+ }
257
+ }
258
+ }
259
+ }
@@ -4,6 +4,7 @@ import android.graphics.Bitmap
4
4
  import android.graphics.BitmapFactory
5
5
  import android.graphics.Matrix
6
6
  import com.margelo.nitro.camera.HybridFrameSpec
7
+ import com.margelo.nitro.camera.CameraOrientation
7
8
  import java.io.ByteArrayOutputStream
8
9
 
9
10
 
@@ -20,7 +21,13 @@ object BitmapOrientationFixer {
20
21
  jpegBytes.size
21
22
  ) ?: return jpegBytes
22
23
 
23
- val rotationDegrees = 90f //TODO: Get the actual rotation degrees from the frame metadata if available
24
+ val rotationDegrees = when (frame.orientation) {
25
+ CameraOrientation.LEFT -> 90f
26
+ CameraOrientation.RIGHT -> 270f
27
+ CameraOrientation.UP -> 0f
28
+ CameraOrientation.DOWN -> 180f
29
+ else -> 0f
30
+ }
24
31
 
25
32
  val matrix = Matrix().apply {
26
33
  postRotate(rotationDegrees)
@@ -1,14 +1,56 @@
1
- //
2
- // HybridYolo.swift
3
- // Pods
4
- //
5
- // Created by Khaoula-Ghalimi on 22/06/2026.
6
- //
7
-
8
1
  import Foundation
2
+ import NitroModules
3
+ import VisionCamera
9
4
 
10
- class HybridYolo: HybridYoloSpec {
11
- func sum(num1: Double, num2: Double) throws -> Double {
12
- return num1 + num2
5
+ public class HybridYolo: HybridYoloSpec {
6
+ private static let tag = "YOLO_TAG"
7
+
8
+ // Initialisateur obligatoire pour les modules Nitro
9
+ public required init() {
10
+ super.init()
11
+ }
12
+
13
+ /**
14
+ * Charge l'objet modèle YOLO spécifié par son chemin.
15
+ * Implémente la méthode obligatoire du protocole Nitro TypeScript.
16
+ */
17
+ public override func loadModel(modelPath: String) throws -> any HybridYoloModelSpec {
18
+ NSLog("[%@]: Trying to load model object: %@", HybridYolo.tag, modelPath)
19
+
20
+ // Initialise et retourne votre sous-classe de modèle Nitro (assurez-vous qu'elle s'appelle bien HybridYoloModel)
21
+ return try HybridYoloModel(modelPath: modelPath)
22
+ }
23
+
24
+ /**
25
+ * Valide un Frame de la caméra, le convertit en JPEG permanent (NV12 -> JPEG -> Fix Rotation),
26
+ * puis l'encode instantanément en chaîne de caractères Base64 standard.
27
+ */
28
+ public override func frameToBase64(frame: any HybridFrameSpec) throws -> String {
29
+ NSLog("[%@]: Trying to convert frame to base64", HybridYolo.tag)
30
+
31
+ do {
32
+ // 1. Validation du buffer vidéo via notre validateur adapté à iOS (NV12 à 2 plans minimum)
33
+ guard FrameValidator.isValidYuv(frame: frame) else {
34
+ return ""
35
+ }
36
+ NSLog("[%@]: frameToBase64: frame is valid YUV", HybridYolo.tag)
37
+
38
+ // 2. Traitement complet de l'image (Conversion NV12 -> Encodage GPU JPEG -> Rendu de sécurité)
39
+ let jpegBytes = FrameJpegConverter.toJpegBytes(frame: frame, quality: 80)
40
+
41
+ guard !jpegBytes.isEmpty else {
42
+ NSLog("[%@]: ❌ frameToBase64 failed: converted JPEG bytes array is empty", HybridYolo.tag)
43
+ return ""
44
+ }
45
+ NSLog("[%@]: frameToBase64: jpegBytes size: %d bytes", HybridYolo.tag, jpegBytes.count)
46
+
47
+ // 3. Encapsulation dans un conteneur Data pour un encodage Base64 matériel (sans sauts de ligne, équivalent de NO_WRAP)
48
+ let data = Data(jpegBytes)
49
+ return data.base64EncodedString(options: [])
50
+
51
+ } catch {
52
+ NSLog("[%@]: ❌ frameToBase64 failed: %@", HybridYolo.tag, error.localizedDescription)
53
+ return ""
54
+ }
13
55
  }
14
56
  }