From 8a5dfd6ac64e26afd9fc23d062ab64c3f38d8fe0 Mon Sep 17 00:00:00 2001 From: Marc Rousavy Date: Tue, 24 Oct 2023 14:27:47 +0200 Subject: [PATCH] fix: Fix `physicalDevices` DeviceType computation on Android (#2072) * fix: Fix device type calculation on Android * fix: Type safety for deviceTypes * fix: Update docs --- .../camera/core/CameraDeviceDetails.kt | 51 +++++++++---------- .../camera/extensions/List+toJSValue.kt | 11 ++++ .../com/mrousavy/camera/types/DeviceType.kt | 7 +++ package/src/CameraDevice.ts | 6 +-- 4 files changed, 46 insertions(+), 29 deletions(-) create mode 100644 package/android/src/main/java/com/mrousavy/camera/extensions/List+toJSValue.kt create mode 100644 package/android/src/main/java/com/mrousavy/camera/types/DeviceType.kt diff --git a/package/android/src/main/java/com/mrousavy/camera/core/CameraDeviceDetails.kt b/package/android/src/main/java/com/mrousavy/camera/core/CameraDeviceDetails.kt index 183d0af..1233ec8 100644 --- a/package/android/src/main/java/com/mrousavy/camera/core/CameraDeviceDetails.kt +++ b/package/android/src/main/java/com/mrousavy/camera/core/CameraDeviceDetails.kt @@ -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 { + 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 = characteristics.getVideoSizes(cameraId, videoFormat) private fun getPhotoSizes(): List = 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) diff --git a/package/android/src/main/java/com/mrousavy/camera/extensions/List+toJSValue.kt b/package/android/src/main/java/com/mrousavy/camera/extensions/List+toJSValue.kt new file mode 100644 index 0000000..ba78a96 --- /dev/null +++ b/package/android/src/main/java/com/mrousavy/camera/extensions/List+toJSValue.kt @@ -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.toJSValue(): ReadableArray { + val arguments = Arguments.createArray() + this.forEach { arguments.pushString(it.unionValue) } + return arguments +} diff --git a/package/android/src/main/java/com/mrousavy/camera/types/DeviceType.kt b/package/android/src/main/java/com/mrousavy/camera/types/DeviceType.kt new file mode 100644 index 0000000..e32e459 --- /dev/null +++ b/package/android/src/main/java/com/mrousavy/camera/types/DeviceType.kt @@ -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") +} diff --git a/package/src/CameraDevice.ts b/package/src/CameraDevice.ts index 4b12bf1..a3e7d8e 100644 --- a/package/src/CameraDevice.ts +++ b/package/src/CameraDevice.ts @@ -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: *