fix: Fix physicalDevices DeviceType computation on Android (#2072)

* fix: Fix device type calculation on Android

* fix: Type safety for deviceTypes

* fix: Update docs
This commit is contained in:
Marc Rousavy 2023-10-24 14:27:47 +02:00 committed by GitHub
parent 5b1e5f3c9d
commit 8a5dfd6ac6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 46 additions and 29 deletions

View File

@ -11,17 +11,18 @@ import android.util.Size
import com.facebook.react.bridge.Arguments import com.facebook.react.bridge.Arguments
import com.facebook.react.bridge.ReadableArray import com.facebook.react.bridge.ReadableArray
import com.facebook.react.bridge.ReadableMap import com.facebook.react.bridge.ReadableMap
import com.mrousavy.camera.extensions.bigger
import com.mrousavy.camera.extensions.getPhotoSizes import com.mrousavy.camera.extensions.getPhotoSizes
import com.mrousavy.camera.extensions.getVideoSizes import com.mrousavy.camera.extensions.getVideoSizes
import com.mrousavy.camera.extensions.toJSValue
import com.mrousavy.camera.types.AutoFocusSystem import com.mrousavy.camera.types.AutoFocusSystem
import com.mrousavy.camera.types.DeviceType
import com.mrousavy.camera.types.HardwareLevel import com.mrousavy.camera.types.HardwareLevel
import com.mrousavy.camera.types.LensFacing import com.mrousavy.camera.types.LensFacing
import com.mrousavy.camera.types.Orientation import com.mrousavy.camera.types.Orientation
import com.mrousavy.camera.types.PixelFormat import com.mrousavy.camera.types.PixelFormat
import com.mrousavy.camera.types.VideoStabilizationMode import com.mrousavy.camera.types.VideoStabilizationMode
import kotlin.math.PI import kotlin.math.atan2
import kotlin.math.atan import kotlin.math.sqrt
class CameraDeviceDetails(private val cameraManager: CameraManager, private val cameraId: String) { class CameraDeviceDetails(private val cameraManager: CameraManager, private val cameraId: String) {
private val characteristics = cameraManager.getCameraCharacteristics(cameraId) private val characteristics = cameraManager.getCameraCharacteristics(cameraId)
@ -106,33 +107,29 @@ class CameraDeviceDetails(private val cameraManager: CameraManager, private val
return array return array
} }
// 35mm is 135 film format, a standard in which focal lengths are usually measured private fun getDeviceTypes(): List<DeviceType> {
private val size35mm = Size(36, 24) val deviceTypes = focalLengths.map { focalLength ->
val fov = getFieldOfView(focalLength)
private fun getDeviceTypes(): ReadableArray { return@map when {
// To get valid focal length standards we have to upscale to the 35mm measurement (film standard) fov > 94 -> DeviceType.ULTRA_WIDE_ANGLE
val cropFactor = size35mm.bigger / sensorSize.bigger fov in 60f..94f -> DeviceType.WIDE_ANGLE
fov < 60f -> DeviceType.TELEPHOTO
val deviceTypes = Arguments.createArray()
focalLengths.forEach { focalLength ->
// scale to the 35mm film standard
val l = focalLength * cropFactor
when {
// https://en.wikipedia.org/wiki/Ultra_wide_angle_lens
l < 24f -> deviceTypes.pushString("ultra-wide-angle-camera")
// https://en.wikipedia.org/wiki/Wide-angle_lens
l in 24f..43f -> deviceTypes.pushString("wide-angle-camera")
// https://en.wikipedia.org/wiki/Telephoto_lens
l > 43f -> deviceTypes.pushString("telephoto-camera")
else -> throw Error("Invalid focal length! (${focalLength}mm)") else -> throw Error("Invalid focal length! (${focalLength}mm)")
} }
} }
return deviceTypes return deviceTypes
} }
private fun getFieldOfView(): Double = 2 * atan(sensorSize.bigger / (focalLengths[0] * 2)) * (180 / PI) private fun getFieldOfView(focalLength: Float): Double {
val sensorDiagonal = sqrt((sensorSize.width * sensorSize.width + sensorSize.height * sensorSize.height).toDouble())
val fovRadians = 2.0 * atan2(sensorDiagonal, (2.0 * focalLength))
return Math.toDegrees(fovRadians)
}
private fun getMaxFieldOfView(): Double {
val smallestFocalLength = focalLengths.minOrNull() ?: return 0.0
return getFieldOfView(smallestFocalLength)
}
private fun getVideoSizes(): List<Size> = characteristics.getVideoSizes(cameraId, videoFormat) private fun getVideoSizes(): List<Size> = characteristics.getVideoSizes(cameraId, videoFormat)
private fun getPhotoSizes(): List<Size> = characteristics.getPhotoSizes(ImageFormat.JPEG) private fun getPhotoSizes(): List<Size> = characteristics.getPhotoSizes(ImageFormat.JPEG)
@ -177,7 +174,7 @@ class CameraDeviceDetails(private val cameraManager: CameraManager, private val
map.putInt("minFps", fpsRange.lower) map.putInt("minFps", fpsRange.lower)
map.putInt("maxFps", fpsRange.upper) map.putInt("maxFps", fpsRange.upper)
map.putDouble("maxZoom", maxZoom) map.putDouble("maxZoom", maxZoom)
map.putDouble("fieldOfView", getFieldOfView()) map.putDouble("fieldOfView", getMaxFieldOfView())
map.putBoolean("supportsVideoHDR", supportsVideoHdr) map.putBoolean("supportsVideoHDR", supportsVideoHdr)
map.putBoolean("supportsPhotoHDR", supportsPhotoHdr) map.putBoolean("supportsPhotoHDR", supportsPhotoHdr)
map.putBoolean("supportsDepthCapture", supportsDepthCapture) map.putBoolean("supportsDepthCapture", supportsDepthCapture)
@ -188,9 +185,11 @@ class CameraDeviceDetails(private val cameraManager: CameraManager, private val
} }
fun toMap(): ReadableMap { fun toMap(): ReadableMap {
val deviceTypes = getDeviceTypes()
val map = Arguments.createMap() val map = Arguments.createMap()
map.putString("id", cameraId) map.putString("id", cameraId)
map.putArray("physicalDevices", getDeviceTypes()) map.putArray("physicalDevices", deviceTypes.toJSValue())
map.putString("position", lensFacing.unionValue) map.putString("position", lensFacing.unionValue)
map.putString("name", name) map.putString("name", name)
map.putBoolean("hasFlash", hasFlash) map.putBoolean("hasFlash", hasFlash)

View File

@ -0,0 +1,11 @@
package com.mrousavy.camera.extensions
import com.facebook.react.bridge.Arguments
import com.facebook.react.bridge.ReadableArray
import com.mrousavy.camera.types.JSUnionValue
fun List<JSUnionValue>.toJSValue(): ReadableArray {
val arguments = Arguments.createArray()
this.forEach { arguments.pushString(it.unionValue) }
return arguments
}

View File

@ -0,0 +1,7 @@
package com.mrousavy.camera.types
enum class DeviceType(override val unionValue: String) : JSUnionValue {
ULTRA_WIDE_ANGLE("ultra-wide-angle-camera"),
WIDE_ANGLE("wide-angle-camera"),
TELEPHOTO("telephoto-camera")
}

View File

@ -13,9 +13,9 @@ export type CameraPosition = 'front' | 'back' | 'external'
/** /**
* Indentifiers for a physical camera (one that actually exists on the back/front of the device) * Indentifiers for a physical camera (one that actually exists on the back/front of the device)
* *
* * `"ultra-wide-angle-camera"`: A built-in camera with a shorter focal length than that of a wide-angle camera. (focal length between below 24mm) * * `"ultra-wide-angle-camera"`: A built-in camera with a shorter focal length than that of a wide-angle camera. (FOV of 94° or higher)
* * `"wide-angle-camera"`: A built-in wide-angle camera. (focal length between 24mm and 43mm) * * `"wide-angle-camera"`: A built-in wide-angle camera. (FOV between 60° and 94°)
* * `"telephoto-camera"`: A built-in camera device with a longer focal length than a wide-angle camera. (focal length between above 85mm) * * `"telephoto-camera"`: A built-in camera device with a longer focal length than a wide-angle camera. (FOV of 60° or lower)
* *
* Some Camera devices consist of multiple physical devices. They can be interpreted as _logical devices_, for example: * Some Camera devices consist of multiple physical devices. They can be interpreted as _logical devices_, for example:
* *