margelo / margelo/react-native-vision-camera
🐛 CameraOrientation.fromDegrees() has LEFT/RIGHT swapped on Android — doesn't round-trip its own .degrees getter
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 9.6k
- Forks
- 1.4k
- Avg merge
- 1d 28m
- Merged PRs (30d)
- 4
Description
Prerequisites
- I have read and followed every applicable step in the Nitro Modules Troubleshooting guide.
- I have read the VisionCamera Troubleshooting guide.
- I have searched existing issues and found nothing matching.
- I am on the latest version of react-native-vision-camera, or have a specific reason I cannot upgrade.
Reproduction
https://github.com/Melon-Technologies/vc-camera-orientation-fromdegrees-repro
Steps to reproduce
- Clone https://github.com/Melon-Technologies/vc-camera-orientation-fromdegrees-repro
kotlinc camera_orientation_fromdegrees_test.kt -include-runtime -d test.jarkotlin -classpath test.jar Camera_orientation_fromdegrees_testKt- Observe:
LEFT.degrees=270 -> fromDegrees(270)=RIGHTandRIGHT.degrees=90 -> fromDegrees(90)=LEFT
What did you expect to happen?
CameraOrientation.Companion.fromDegrees(degrees) should be the inverse of CameraOrientation.degrees — i.e. fromDegrees(o.degrees) == o for every CameraOrientation value.
What actually happened?
It only round-trips for UP and DOWN. For LEFT and RIGHT it returns the wrong value:
val CameraOrientation.degrees: Int
get() = when (this) {
UP -> 0; DOWN -> 180; LEFT -> 270; RIGHT -> 90
}
fun CameraOrientation.Companion.fromDegrees(degrees: Int): CameraOrientation {
val normalizedDegrees = normalizeDegrees(degrees)
return when (normalizedDegrees) {
in 45..135 -> CameraOrientation.LEFT // should be RIGHT (RIGHT.degrees == 90, inside this bucket)
in 135..225 -> CameraOrientation.DOWN
in 225..315 -> CameraOrientation.RIGHT // should be LEFT (LEFT.degrees == 270, inside this bucket)
else -> CameraOrientation.UP
}
}
fromDegrees(90) returns LEFT instead of RIGHT; fromDegrees(270) returns RIGHT instead of LEFT. The LEFT/RIGHT buckets are swapped relative to .degrees' own mapping.
Why it matters: ImageProxy.orientation calls fromDegrees(imageInfo.rotationDegrees) to label every incoming camera frame:
val ImageProxy.orientation: CameraOrientation
get() {
val degrees = imageInfo.rotationDegrees
return CameraOrientation.fromDegrees(degrees)
}
A front-facing sensor held in portrait commonly reports rotationDegrees = 270. Pre-fix, fromDegrees(270) returns RIGHT instead of the self-consistent LEFT — a 180-degree-equivalent mislabeling. That wrong orientation value feeds react-native-vision-camera-resizer's rotationDegrees push constant downstream.
Observed live: on a real Android device (Samsung Galaxy A54 5G, Android 15), a detected face's bounding box was drawn over an unrelated part of the frame instead of the actual face, while the identical JS/native pipeline on iOS (iPhone 12, iOS 16.2) drew it correctly — which is what pointed at this Android-only orientation mapping rather than the detector model itself.
Fix
Swap the LEFT/RIGHT buckets to match .degrees:
fun CameraOrientation.Companion.fromDegrees(degrees: Int): CameraOrientation {
val normalizedDegrees = normalizeDegrees(degrees)
return when (normalizedDegrees) {
- in 45..135 -> CameraOrientation.LEFT
+ in 45..135 -> CameraOrientation.RIGHT
in 135..225 -> CameraOrientation.DOWN
- in 225..315 -> CameraOrientation.RIGHT
+ in 225..315 -> CameraOrientation.LEFT
else -> CameraOrientation.UP
}
}
We've been running this as a local patch-package patch to react-native-vision-camera@5.1.1 in production.
Affected platforms
Android (device)
Device(s) affected
Observed on Samsung Galaxy A54 5G (Android 15). Root cause confirmed by direct source inspection and a pure JVM round-trip test — the bug is deterministic enum-mapping logic independent of specific hardware.
VisionCamera version
5.1.1
React Native version
0.86
React Native architecture
New Architecture (Fabric / bridgeless)
Features being used
- Preview
- Photo capture
- Video capture
- Frame Processors (worklets)
- Skia Frame Processors
- Code/Barcode Scanner
- Location metadata
- Multi-cam
- Depth data
- HDR / custom dynamic range
- Custom format / FPS / resolution
Relevant logs / stack trace
This is a logic/correctness bug, not a crash — there is no stack trace. See the reproduction repo's console output:
CameraOrientation.fromDegrees round-trip test (issue: LEFT/RIGHT swapped)
BUGGY fromDegrees (pristine upstream code):
UP.degrees=0 -> fromDegreesBuggy(0)=UP OK
DOWN.degrees=180 -> fromDegreesBuggy(180)=DOWN OK
LEFT.degrees=270 -> fromDegreesBuggy(270)=RIGHT MISMATCH (bug reproduced)
RIGHT.degrees=90 -> fromDegreesBuggy(90)=LEFT MISMATCH (bug reproduced)
-> all orientations round-trip correctly: NO -- bug confirmed
FIXED fromDegrees (LEFT/RIGHT buckets swapped):
UP.degrees=0 -> fromDegreesFixed(0)=UP OK
DOWN.degrees=180 -> fromDegreesFixed(180)=DOWN OK
LEFT.degrees=270 -> fromDegreesFixed(270)=LEFT OK
RIGHT.degrees=90 -> fromDegreesFixed(90)=RIGHT OK
-> all orientations round-trip correctly: yes, bug is fixed
RESULT: PASS -- bug reproduced on unpatched code, confirmed fixed on patched code.
Additional context
No response
Submission
- The reproduction I linked is either (preferred) a PR against this repo that adds a failing harness test following the harness-tests README, or (fallback) a public repo that reproduces the bug on a fresh clone. I understand the issue will be closed without one.
- I pasted logs as text (not screenshots).
- I wrote this report in my own words. I did not paste AI-generated descriptions of the bug.
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start at CameraOrientation.degrees and CameraOrientation.Companion.fromDegrees, then check how ImageProxy.orientation uses the conversion. Run kotlinc camera_orientation_fromdegrees_test.kt -include-runtime -d test.jar and the resulting Kotlin command; done means every orientation round-trips to itself, including LEFT and RIGHT.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- android, kotlin, react-native
- Domain
- mobile-dev
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 88/100