Skip to content

Commit b21a967

Browse files
fix(android): resolve multiple Android-specific bugs
- Fix validateImage returning 'issues' instead of 'errors' to match iOS/TypeScript - Fix fiveCrop 'array already consumed' by removing duplicate putArray calls - Fix extractVideoFrames returning 0 frameCount by tracking count separately - Add backwards-compatible error handling in App.tsx for validation results - Add safety guard for regions array in cutout result handling
1 parent 4059491 commit b21a967

7 files changed

Lines changed: 28 additions & 23 deletions

File tree

android/src/main/java/com/visionutils/ImageAnalyzerAndroid.kt

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -164,44 +164,44 @@ object ImageAnalyzerAndroid {
164164
* Validate image against criteria
165165
*/
166166
fun validate(bitmap: Bitmap, options: ReadableMap): WritableMap {
167-
val issues = Arguments.createArray()
167+
val errors = Arguments.createArray()
168168
var isValid = true
169169

170170
val width = bitmap.width
171171
val height = bitmap.height
172172

173173
// Check minimum dimensions
174174
if (options.hasKey("minWidth") && width < options.getInt("minWidth")) {
175-
issues.pushString("Width ${width} is less than minimum ${options.getInt("minWidth")}")
175+
errors.pushString("Width ${width} is less than minimum ${options.getInt("minWidth")}")
176176
isValid = false
177177
}
178178

179179
if (options.hasKey("minHeight") && height < options.getInt("minHeight")) {
180-
issues.pushString("Height ${height} is less than minimum ${options.getInt("minHeight")}")
180+
errors.pushString("Height ${height} is less than minimum ${options.getInt("minHeight")}")
181181
isValid = false
182182
}
183183

184184
// Check maximum dimensions
185185
if (options.hasKey("maxWidth") && width > options.getInt("maxWidth")) {
186-
issues.pushString("Width ${width} exceeds maximum ${options.getInt("maxWidth")}")
186+
errors.pushString("Width ${width} exceeds maximum ${options.getInt("maxWidth")}")
187187
isValid = false
188188
}
189189

190190
if (options.hasKey("maxHeight") && height > options.getInt("maxHeight")) {
191-
issues.pushString("Height ${height} exceeds maximum ${options.getInt("maxHeight")}")
191+
errors.pushString("Height ${height} exceeds maximum ${options.getInt("maxHeight")}")
192192
isValid = false
193193
}
194194

195195
// Check aspect ratio
196196
val aspectRatio = width.toDouble() / height.toDouble()
197197

198198
if (options.hasKey("minAspectRatio") && aspectRatio < options.getDouble("minAspectRatio")) {
199-
issues.pushString("Aspect ratio $aspectRatio is less than minimum ${options.getDouble("minAspectRatio")}")
199+
errors.pushString("Aspect ratio $aspectRatio is less than minimum ${options.getDouble("minAspectRatio")}")
200200
isValid = false
201201
}
202202

203203
if (options.hasKey("maxAspectRatio") && aspectRatio > options.getDouble("maxAspectRatio")) {
204-
issues.pushString("Aspect ratio $aspectRatio exceeds maximum ${options.getDouble("maxAspectRatio")}")
204+
errors.pushString("Aspect ratio $aspectRatio exceeds maximum ${options.getDouble("maxAspectRatio")}")
205205
isValid = false
206206
}
207207

@@ -214,7 +214,7 @@ object ImageAnalyzerAndroid {
214214
0.01
215215

216216
if (kotlin.math.abs(aspectRatio - required) > tolerance) {
217-
issues.pushString("Aspect ratio $aspectRatio does not match required $required (tolerance: $tolerance)")
217+
errors.pushString("Aspect ratio $aspectRatio does not match required $required (tolerance: $tolerance)")
218218
isValid = false
219219
}
220220
}
@@ -223,13 +223,13 @@ object ImageAnalyzerAndroid {
223223
val channels = if (bitmap.hasAlpha()) 4 else 3
224224

225225
if (options.hasKey("requiredChannels") && channels != options.getInt("requiredChannels")) {
226-
issues.pushString("Image has $channels channels but $${options.getInt("requiredChannels")} required")
226+
errors.pushString("Image has $channels channels but $${options.getInt("requiredChannels")} required")
227227
isValid = false
228228
}
229229

230230
return Arguments.createMap().apply {
231231
putBoolean("isValid", isValid)
232-
putArray("issues", issues)
232+
putArray("errors", errors)
233233
putInt("width", width)
234234
putInt("height", height)
235235
putInt("channels", channels)

android/src/main/java/com/visionutils/MultiCropAndroid.kt

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -56,25 +56,23 @@ object MultiCropAndroid {
5656
// Process each crop
5757
val results = Arguments.createArray()
5858
val parsedOptions = GetPixelDataOptions.fromMap(pixelOptions)
59+
var cropCount = 0
5960

6061
for (crop in crops) {
6162
val result = PixelProcessor.process(crop, parsedOptions)
6263
results.pushMap(result.toWritableMap())
6364
crop.recycle()
65+
cropCount++
6466
}
6567

6668
val totalTimeMs = (System.nanoTime() - startTimeNs) / 1_000_000.0
6769

6870
return Arguments.createMap().apply {
69-
// Keep original keys for backward compatibility
70-
putArray("results", results)
71-
putInt("cropCount", 5)
71+
// Use "crops" as the primary key (matching iOS)
72+
putArray("crops", results)
73+
putInt("count", cropCount)
7274
putInt("cropWidth", cropWidth)
7375
putInt("cropHeight", cropHeight)
74-
75-
// Align with iOS shape and sample app expectations
76-
putArray("crops", results)
77-
putInt("count", results.size())
7876
putDouble("totalTimeMs", totalTimeMs)
7977
}
8078
}

android/src/main/java/com/visionutils/VideoFrameExtractorAndroid.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ object VideoFrameExtractorAndroid {
9898

9999
// Extract frames
100100
val frames = WritableNativeArray()
101+
var frameCount = 0
101102

102103
for (timestamp in timestamps) {
103104
val frameData = WritableNativeMap()
@@ -147,12 +148,13 @@ object VideoFrameExtractorAndroid {
147148
}
148149

149150
frames.pushMap(frameData)
151+
frameCount++
150152
}
151153

152154
val processingTime = System.currentTimeMillis() - startTime
153155

154156
result.putArray("frames", frames)
155-
result.putInt("frameCount", frames.size())
157+
result.putInt("frameCount", frameCount)
156158
result.putDouble("videoDuration", durationSeconds)
157159
result.putInt("videoWidth", videoWidth)
158160
result.putInt("videoHeight", videoHeight)

example/ios/Podfile.lock

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2537,7 +2537,7 @@ PODS:
25372537
- React-utils (= 0.83.0)
25382538
- SocketRocket
25392539
- SocketRocket (0.7.1)
2540-
- VisionUtils (1.2.0):
2540+
- VisionUtils (1.2.2):
25412541
- boost
25422542
- DoubleConversion
25432543
- fast_float
@@ -2894,7 +2894,7 @@ SPEC CHECKSUMS:
28942894
ReactCodegen: 10a61330b137caaad6f7fbe7f5d0e7a40d621700
28952895
ReactCommon: c6cd81778336e767e27fe63c6707dd6b735fff5c
28962896
SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748
2897-
VisionUtils: ff638083c89b25483f93bdee9583eb12ece5ce39
2897+
VisionUtils: 2b383d68eeb5d97a9470d076031be9ab7b01001e
28982898
Yoga: 6ca93c8c13f56baeec55eb608577619b17a4d64e
28992899

29002900
PODFILE CHECKSUM: 17994baec44471422dbe5d477936ca2916e073af

example/src/App.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -345,10 +345,11 @@ const App: React.FC = () => {
345345
const height =
346346
validation.metadata?.height ??
347347
(validation as unknown as { height: number }).height;
348+
const errors = validation.errors || (validation as any).issues || [];
348349
Alert.alert(
349350
'Image Validation',
350351
`Valid: ${validation.isValid}\nErrors: ${
351-
validation.errors.length > 0 ? validation.errors.join(', ') : 'None'
352+
errors.length > 0 ? errors.join(', ') : 'None'
352353
}\nSize: ${width}x${height}`
353354
);
354355
} catch (err: unknown) {
@@ -473,7 +474,7 @@ const App: React.FC = () => {
473474
if (result.base64) {
474475
setProcessedImageUri(`data:image/png;base64,${result.base64}`);
475476
}
476-
const regionsInfo = result.regions
477+
const regionsInfo = (result.regions || [])
477478
.map(
478479
(
479480
r: { x: number; y: number; width: number; height: number },

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "react-native-vision-utils",
3-
"version": "1.2.1",
3+
"version": "1.2.2",
44
"description": "High-performance React Native SDK for image pre-processing for ML/AI pipelines. Native implementations for pixel extraction, tensor conversion, quantization, augmentation, and model-specific preprocessing (YOLO, MobileNet, etc.)",
55
"main": "./lib/module/index.js",
66
"types": "./lib/typescript/src/index.d.ts",

src/index.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,10 @@ function validateSource(source: GetPixelDataOptions['source']): void {
233233
throw new VisionUtilsException('INVALID_SOURCE', 'Source is required');
234234
}
235235

236+
if (!source.type) {
237+
throw new VisionUtilsException('INVALID_SOURCE', 'Source type is required');
238+
}
239+
236240
const validTypes = ['url', 'file', 'base64', 'asset', 'photoLibrary'];
237241
if (!validTypes.includes(source.type)) {
238242
throw new VisionUtilsException(

0 commit comments

Comments
 (0)