react-native-nitro-pose-exercises 1.1.1 → 1.1.3

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.
package/README.md CHANGED
@@ -127,7 +127,7 @@ module.exports = {
127
127
  The iOS podspec needs the Vision and AVFoundation system frameworks:
128
128
 
129
129
  ```ruby
130
- s.frameworks = 'Vision', 'AVFoundation'
130
+ s.frameworks = ["AVFoundation", "Vision"]
131
131
  ```
132
132
 
133
133
  No CocoaPods dependencies required — Vision is built into iOS.
@@ -28,4 +28,11 @@ find_package(react-native-vision-camera REQUIRED)
28
28
  target_link_libraries(
29
29
  ${PACKAGE_NAME}
30
30
  react-native-vision-camera::VisionCamera
31
+ )
32
+
33
+ find_library(android-lib android)
34
+
35
+ target_link_libraries(
36
+ ${PACKAGE_NAME}
37
+ ${android-lib}
31
38
  )
@@ -0,0 +1,37 @@
1
+ #include <jni.h>
2
+ #include <android/hardware_buffer_jni.h>
3
+
4
+ extern "C"
5
+ JNIEXPORT jobject JNICALL
6
+ Java_com_margelo_nitro_nitroposeexercises_FrameHelper_hardwareBufferToBitmap(
7
+ JNIEnv *env,
8
+ jclass clazz,
9
+ jlong pointer
10
+ ) {
11
+ AHardwareBuffer *buffer = reinterpret_cast<AHardwareBuffer *>(pointer);
12
+ if (!buffer) return nullptr;
13
+
14
+ // Convert AHardwareBuffer* to Java HardwareBuffer
15
+ jobject hardwareBuffer = AHardwareBuffer_toHardwareBuffer(env, buffer);
16
+ if (!hardwareBuffer) return nullptr;
17
+
18
+ // Call Bitmap.wrapHardwareBuffer(hardwareBuffer, null) to create a hardware-backed Bitmap
19
+ jclass bitmapClass = env->FindClass("android/graphics/Bitmap");
20
+ jmethodID wrapMethod = env->GetStaticMethodID(
21
+ bitmapClass,
22
+ "wrapHardwareBuffer",
23
+ "(Landroid/hardware/HardwareBuffer;Landroid/graphics/ColorSpace;)Landroid/graphics/Bitmap;"
24
+ );
25
+ jobject hwBitmap = env->CallStaticObjectMethod(bitmapClass, wrapMethod, hardwareBuffer, nullptr);
26
+ if (!hwBitmap) return nullptr;
27
+
28
+ // Copy to a software ARGB_8888 Bitmap (ML Kit needs software bitmap)
29
+ jclass configClass = env->FindClass("android/graphics/Bitmap$Config");
30
+ jfieldID argbField = env->GetStaticFieldID(configClass, "ARGB_8888", "Landroid/graphics/Bitmap$Config;");
31
+ jobject argbConfig = env->GetStaticObjectField(configClass, argbField);
32
+
33
+ jmethodID copyMethod = env->GetMethodID(bitmapClass, "copy", "(Landroid/graphics/Bitmap$Config;Z)Landroid/graphics/Bitmap;");
34
+ jobject softBitmap = env->CallObjectMethod(hwBitmap, copyMethod, argbConfig, JNI_FALSE);
35
+
36
+ return softBitmap;
37
+ }
@@ -0,0 +1,12 @@
1
+ package com.margelo.nitro.nitroposeexercises
2
+
3
+ import android.graphics.Bitmap
4
+
5
+ object FrameHelper {
6
+ init {
7
+ System.loadLibrary("nitroposeexercises")
8
+ }
9
+
10
+ @JvmStatic
11
+ external fun hardwareBufferToBitmap(pointer: Long): Bitmap?
12
+ }
@@ -13,7 +13,6 @@ import com.google.mlkit.vision.pose.defaults.PoseDetectorOptions
13
13
  import com.margelo.nitro.NitroModules
14
14
  import com.margelo.nitro.core.Promise
15
15
  import com.margelo.nitro.camera.HybridFrameSpec
16
- import com.margelo.nitro.camera.NativeFrame
17
16
  import kotlin.math.acos
18
17
  import kotlin.math.max
19
18
  import kotlin.math.min
@@ -191,25 +190,21 @@ class HybridPoseExercise : HybridNitroPoseExercisesSpec() {
191
190
  // Frame Processing (ML Kit — async with cached results)
192
191
  // ═══════════════════════════════════════════════════════════
193
192
 
194
- override fun processFrame(frame: HybridFrameSpec) {
193
+ override fun processFrame(frame: HybridFrameSpec) {
195
194
  if (_status != SessionStatus.ACTIVE && _status != SessionStatus.COUNTDOWN) return
196
195
  if (!isInitialized || poseDetector == null) return
197
196
 
198
- // Frame throttle
199
197
  frameCount++
200
198
  if (frameCount % processEveryNFrames != 0) return
201
199
 
202
- // Get the ImageProxy from VisionCamera frame
203
- val nativeFrame = frame as? NativeFrame ?: return
204
- val imageProxy = nativeFrame.image ?: return
205
-
206
200
  try {
207
- // ML Kit takes InputImage directly from ImageProxy — no color conversion needed
208
- @androidx.camera.core.ExperimentalGetImage
209
- val mediaImage: Image = imageProxy.image ?: return
210
- val inputImage = InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees)
201
+ val nativeBuffer = frame.getNativeBuffer()
202
+ val bitmap = FrameHelper.hardwareBufferToBitmap(nativeBuffer.pointer) ?: return
203
+
204
+ val inputImage = InputImage.fromBitmap(bitmap, 0)
205
+ val imageWidth = bitmap.width.toDouble()
206
+ val imageHeight = bitmap.height.toDouble()
211
207
 
212
- // ML Kit is async — fire detection, cache result, use cached landmarks for current frame
213
208
  poseDetector!!.process(inputImage)
214
209
  .addOnSuccessListener { pose ->
215
210
  val poseLandmarks = pose.allPoseLandmarks
@@ -220,17 +215,12 @@ class HybridPoseExercise : HybridNitroPoseExercisesSpec() {
220
215
  onPoseRegained?.invoke()
221
216
  }
222
217
 
223
- // Build landmark array mapped to MediaPipe indices (34 slots)
224
218
  val landmarkArray = Array(34) { Landmark(x = 0.0, y = 0.0, z = 0.0, visibility = 0.0) }
225
219
 
226
- val imageWidth = inputImage.width.toDouble()
227
- val imageHeight = inputImage.height.toDouble()
228
-
229
220
  for (poseLandmark in poseLandmarks) {
230
221
  val mediaPipeIndex = mlKitToMediaPipeMap[poseLandmark.landmarkType] ?: continue
231
222
  if (mediaPipeIndex >= 34) continue
232
223
 
233
- // Normalize coordinates to 0-1 range
234
224
  landmarkArray[mediaPipeIndex] = Landmark(
235
225
  x = poseLandmark.position.x.toDouble() / imageWidth,
236
226
  y = poseLandmark.position.y.toDouble() / imageHeight,
@@ -251,12 +241,15 @@ class HybridPoseExercise : HybridNitroPoseExercisesSpec() {
251
241
  cachedLandmarks = emptyArray()
252
242
  }
253
243
  }
244
+
245
+ bitmap.recycle()
254
246
  }
255
247
  .addOnFailureListener { e ->
256
248
  println("[PoseExercise] ML Kit error: ${e.message}")
249
+ bitmap.recycle()
257
250
  }
258
251
 
259
- // Use cached landmarks for exercise logic (from previous frame's detection)
252
+ // Use cached landmarks from previous frame
260
253
  val currentLandmarks: Array<Landmark>
261
254
  synchronized(landmarkLock) {
262
255
  currentLandmarks = cachedLandmarks.copyOf()
@@ -1,4 +1,4 @@
1
- // ios/HybridPoseExercise.swift
1
+ // ios/NitroPoseExercises.swift
2
2
 
3
3
  import Foundation
4
4
  import NitroModules
@@ -6,7 +6,7 @@ import VisionCamera
6
6
  import AVFoundation
7
7
  import Vision
8
8
 
9
- class HybridPoseExercise: HybridNitroPoseExercisesSpec {
9
+ class NitroPoseExercises: HybridNitroPoseExercisesSpec {
10
10
 
11
11
  // ─── Vision Framework ───────────────────────────────────────
12
12
  private var isInitialized = false
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "react-native-nitro-pose-exercises",
3
- "version": "1.1.1",
4
- "description": "Real-time on-device exercise tracking for React Native. Rep counting, form validation, and skeleton overlay powered by MediaPipe Pose Landmarker and VisionCamera v5 via Nitro Modules.",
3
+ "version": "1.1.3",
4
+ "description": "Real-time on-device exercise tracking for React Native. Rep counting, form validation, and skeleton overlay powered by Apple Vision (iOS) and Google ML Kit (Android) with VisionCamera v5 via Nitro Modules.",
5
5
  "main": "./lib/module/index.js",
6
6
  "types": "./lib/typescript/src/index.d.ts",
7
7
  "exports": {