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.ReadableArray
import com.facebook.react.bridge.ReadableMap
import com.mrousavy.camera.extensions.bigger
import com.mrousavy.camera.extensions.getPhotoSizes
import com.mrousavy.camera.extensions.getVideoSizes
import com.mrousavy.camera.extensions.toJSValue
import com.mrousavy.camera.types.AutoFocusSystem
import com.mrousavy.camera.types.DeviceType
import com.mrousavy.camera.types.HardwareLevel
import com.mrousavy.camera.types.LensFacing
import com.mrousavy.camera.types.Orientation
import com.mrousavy.camera.types.PixelFormat
import com.mrousavy.camera.types.VideoStabilizationMode
import kotlin.math.PI
import kotlin.math.atan
import kotlin.math.atan2
import kotlin.math.sqrt
class CameraDeviceDetails(private val cameraManager: CameraManager, private val cameraId: String) {
private val characteristics = cameraManager.getCameraCharacteristics(cameraId)
@ -106,33 +107,29 @@ class CameraDeviceDetails(private val cameraManager: CameraManager, private val
return array
}
// 35mm is 135 film format, a standard in which focal lengths are usually measured
private val size35mm = Size(36, 24)
private fun getDeviceTypes(): ReadableArray {
// To get valid focal length standards we have to upscale to the 35mm measurement (film standard)
val cropFactor = size35mm.bigger / sensorSize.bigger
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")
private fun getDeviceTypes(): List<DeviceType> {
val deviceTypes = focalLengths.map { focalLength ->
val fov = getFieldOfView(focalLength)
return@map when {
fov > 94 -> DeviceType.ULTRA_WIDE_ANGLE
fov in 60f..94f -> DeviceType.WIDE_ANGLE
fov < 60f -> DeviceType.TELEPHOTO
else -> throw Error("Invalid focal length! (${focalLength}mm)")
}
}
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 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("maxFps", fpsRange.upper)
map.putDouble("maxZoom", maxZoom)
map.putDouble("fieldOfView", getFieldOfView())
map.putDouble("fieldOfView", getMaxFieldOfView())
map.putBoolean("supportsVideoHDR", supportsVideoHdr)
map.putBoolean("supportsPhotoHDR", supportsPhotoHdr)
map.putBoolean("supportsDepthCapture", supportsDepthCapture)
@ -188,9 +185,11 @@ class CameraDeviceDetails(private val cameraManager: CameraManager, private val
}
fun toMap(): ReadableMap {
val deviceTypes = getDeviceTypes()
val map = Arguments.createMap()
map.putString("id", cameraId)
map.putArray("physicalDevices", getDeviceTypes())
map.putArray("physicalDevices", deviceTypes.toJSValue())
map.putString("position", lensFacing.unionValue)
map.putString("name", name)
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)
*
* * `"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)
* * `"wide-angle-camera"`: A built-in wide-angle camera. (focal length between 24mm and 43mm)
* * `"telephoto-camera"`: A built-in camera device with a longer focal length than a wide-angle camera. (focal length between above 85mm)
* * `"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. (FOV between 60° and 94°)
* * `"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:
*