From cad5240420d19c866f502d74023df3d4bb6638c8 Mon Sep 17 00:00:00 2001 From: Marc Rousavy Date: Fri, 24 Nov 2023 18:20:56 +0100 Subject: [PATCH] fix: Move `minExposure`/`maxExposure` from format into device (#2211) * fix: Move `minExposure`/`maxExposure` into `device` * Update docs * chore: Remove unneeded dependency * chore: Update code --- docs/docs/guides/EXPOSURE.mdx | 8 ++++---- .../mrousavy/camera/core/CameraDeviceDetails.kt | 4 ++-- .../mrousavy/camera/types/CameraDeviceFormat.kt | 4 ---- .../ios/Core/CameraSession+Configuration.swift | 6 +++--- .../AVCaptureDevice+toDictionary.swift | 6 ++++-- package/ios/Types/CameraDeviceFormat.swift | 15 +++------------ package/src/CameraDevice.ts | 16 ++++++++-------- package/src/CameraProps.ts | 2 +- 8 files changed, 25 insertions(+), 36 deletions(-) diff --git a/docs/docs/guides/EXPOSURE.mdx b/docs/docs/guides/EXPOSURE.mdx index 206d0c3..1b7f3f1 100644 --- a/docs/docs/guides/EXPOSURE.mdx +++ b/docs/docs/guides/EXPOSURE.mdx @@ -12,7 +12,7 @@ To adjust the exposure of the Camera, you can use the Camera's [`exposure`](/doc ``` -Values for the `exposure` prop range from [`format.minExposure`](/docs/api/interfaces/CameraDeviceFormat#maxExposure) to [`format.maxExposure`](/docs/api/interfaces/CameraDeviceFormat#minExposure), inclusively. By default (`undefined`), it is set to neutral auto exposure. +Values for the `exposure` prop range from [`device.minExposure`](/docs/api/interfaces/CameraDevice#minexposure) to [`device.maxExposure`](/docs/api/interfaces/CameraDevice#maxexposure), inclusively. By default (`undefined`), it is set to neutral auto exposure. Instead of manually adjusting ISO and Exposure-Duration, this acts as an "exposure compensation bias", meaning the Camera will still continuously automatically adjust exposure as it goes, but premultiplies the given exposure value to it's ISO and Exposure Duration settings. @@ -44,12 +44,12 @@ Just like [`zoom`](zooming), this property can be animated using Reanimated. // 2. map slider to [minExposure, 0, maxExposure] const exposureValue = useDerivedValue(() => { - if (format == null) return 0 + if (device == null) return 0 return interpolate(exposureSlider.value, [-1, 0, 1], - [format.minExposure, 0, format.maxExposure]) - }, [exposureSlider, format]) + [device.minExposure, 0, device.maxExposure]) + }, [exposureSlider, device]) // 3. pass it as an animated prop const animatedProps = useAnimatedProps(() => ({ 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 4f584af..1c26608 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 @@ -177,8 +177,6 @@ class CameraDeviceDetails(private val cameraManager: CameraManager, private val map.putInt("videoWidth", videoSize.width) map.putInt("minISO", isoRange.lower) map.putInt("maxISO", isoRange.upper) - map.putDouble("minExposure", exposureRange.lower.toDouble() / exposureStep.toDouble()) - map.putDouble("maxExposure", exposureRange.upper.toDouble() / exposureStep.toDouble()) map.putInt("minFps", fpsRange.lower) map.putInt("maxFps", fpsRange.upper) map.putDouble("maxZoom", maxZoom) @@ -209,6 +207,8 @@ class CameraDeviceDetails(private val cameraManager: CameraManager, private val map.putDouble("minZoom", minZoom) map.putDouble("maxZoom", maxZoom) map.putDouble("neutralZoom", 1.0) // Zoom is always relative to 1.0 on Android + map.putDouble("minExposure", exposureRange.lower.toDouble() / exposureStep.toDouble()) + map.putDouble("maxExposure", exposureRange.upper.toDouble() / exposureStep.toDouble()) map.putString("hardwareLevel", hardwareLevel.unionValue) map.putString("sensorOrientation", Orientation.fromRotationDegrees(sensorOrientation).unionValue) map.putArray("formats", getFormats()) diff --git a/package/android/src/main/java/com/mrousavy/camera/types/CameraDeviceFormat.kt b/package/android/src/main/java/com/mrousavy/camera/types/CameraDeviceFormat.kt index 163262a..ade2acd 100644 --- a/package/android/src/main/java/com/mrousavy/camera/types/CameraDeviceFormat.kt +++ b/package/android/src/main/java/com/mrousavy/camera/types/CameraDeviceFormat.kt @@ -13,8 +13,6 @@ data class CameraDeviceFormat( val maxFps: Double, val minISO: Double, val maxISO: Double, - val minExposure: Double, - val maxExposure: Double, val fieldOfView: Double, val maxZoom: Double, val videoStabilizationModes: List, @@ -48,8 +46,6 @@ data class CameraDeviceFormat( value.getDouble("maxFps"), value.getDouble("minISO"), value.getDouble("maxISO"), - value.getDouble("minExposure"), - value.getDouble("maxExposure"), value.getDouble("fieldOfView"), value.getDouble("maxZoom"), videoStabilizationModes, diff --git a/package/ios/Core/CameraSession+Configuration.swift b/package/ios/Core/CameraSession+Configuration.swift index 296504f..3f837b9 100644 --- a/package/ios/Core/CameraSession+Configuration.swift +++ b/package/ios/Core/CameraSession+Configuration.swift @@ -187,14 +187,14 @@ extension CameraSession { ReactLogger.log(level: .info, message: "Configuring Format (\(targetFormat))...") - let currentFormat = CameraDeviceFormat(fromFormat: device.activeFormat, forDevice: device) + let currentFormat = CameraDeviceFormat(fromFormat: device.activeFormat) if currentFormat == targetFormat { ReactLogger.log(level: .info, message: "Already selected active format, no need to configure.") return } // Find matching format (JS Dictionary -> strongly typed Swift class) - let format = device.formats.first { targetFormat.isEqualTo(format: $0, device: device) } + let format = device.formats.first { targetFormat.isEqualTo(format: $0) } guard let format else { throw CameraError.format(.invalidFormat) } @@ -295,7 +295,7 @@ extension CameraSession { return } - let clamped = max(min(exposure, device.maxExposureTargetBias), device.minExposureTargetBias) + let clamped = min(max(exposure, device.minExposureTargetBias), device.maxExposureTargetBias) device.setExposureTargetBias(clamped) } diff --git a/package/ios/Extensions/AVCaptureDevice+toDictionary.swift b/package/ios/Extensions/AVCaptureDevice+toDictionary.swift index a84ebdf..69e94d8 100644 --- a/package/ios/Extensions/AVCaptureDevice+toDictionary.swift +++ b/package/ios/Extensions/AVCaptureDevice+toDictionary.swift @@ -10,7 +10,7 @@ import AVFoundation extension AVCaptureDevice { func toDictionary() -> [String: Any] { - let formats = formats.map { CameraDeviceFormat(fromFormat: $0, forDevice: self) } + let formats = formats.map { CameraDeviceFormat(fromFormat: $0) } return [ "id": uniqueID, @@ -20,8 +20,10 @@ extension AVCaptureDevice { "hasFlash": hasFlash, "hasTorch": hasTorch, "minZoom": minAvailableVideoZoomFactor, - "neutralZoom": neutralZoomFactor, "maxZoom": maxAvailableVideoZoomFactor, + "neutralZoom": neutralZoomFactor, + "minExposure": minExposureTargetBias, + "maxExposure": maxExposureTargetBias, "isMultiCam": isMultiCam, "supportsRawCapture": false, // TODO: supportsRawCapture "supportsLowLightBoost": isLowLightBoostSupported, diff --git a/package/ios/Types/CameraDeviceFormat.swift b/package/ios/Types/CameraDeviceFormat.swift index 5a2009c..af5096a 100644 --- a/package/ios/Types/CameraDeviceFormat.swift +++ b/package/ios/Types/CameraDeviceFormat.swift @@ -22,9 +22,6 @@ struct CameraDeviceFormat: Equatable, CustomStringConvertible { let minFps: Double let maxFps: Double - let minExposure: Float - let maxExposure: Float - let minISO: Float let maxISO: Float @@ -41,15 +38,13 @@ struct CameraDeviceFormat: Equatable, CustomStringConvertible { let supportsDepthCapture: Bool - init(fromFormat format: AVCaptureDevice.Format, forDevice device: AVCaptureDevice) { + init(fromFormat format: AVCaptureDevice.Format) { videoWidth = Int(format.videoDimensions.width) videoHeight = Int(format.videoDimensions.height) photoWidth = Int(format.photoDimensions.width) photoHeight = Int(format.photoDimensions.height) minFps = format.minFps maxFps = format.maxFps - minExposure = device.minExposureTargetBias - maxExposure = device.maxExposureTargetBias minISO = format.minISO maxISO = format.maxISO fieldOfView = format.videoFieldOfView @@ -72,8 +67,6 @@ struct CameraDeviceFormat: Equatable, CustomStringConvertible { maxFps = jsValue["maxFps"] as! Double minISO = jsValue["minISO"] as! Float maxISO = jsValue["maxISO"] as! Float - minExposure = jsValue["minExposure"] as! Float - maxExposure = jsValue["maxExposure"] as! Float fieldOfView = jsValue["fieldOfView"] as! Float maxZoom = jsValue["maxZoom"] as! Double let jsVideoStabilizationModes = jsValue["videoStabilizationModes"] as! [String] @@ -88,8 +81,8 @@ struct CameraDeviceFormat: Equatable, CustomStringConvertible { // swiftlint:enable force_cast } - func isEqualTo(format other: AVCaptureDevice.Format, device otherDevice: AVCaptureDevice) -> Bool { - let other = CameraDeviceFormat(fromFormat: other, forDevice: otherDevice) + func isEqualTo(format other: AVCaptureDevice.Format) -> Bool { + let other = CameraDeviceFormat(fromFormat: other) return self == other } @@ -103,8 +96,6 @@ struct CameraDeviceFormat: Equatable, CustomStringConvertible { "videoWidth": videoWidth, "minISO": minISO, "maxISO": maxISO, - "minExposure": minExposure, - "maxExposure": maxExposure, "fieldOfView": fieldOfView, "maxZoom": maxZoom, "supportsVideoHdr": supportsVideoHdr, diff --git a/package/src/CameraDevice.ts b/package/src/CameraDevice.ts index 9501a9a..62eeb30 100644 --- a/package/src/CameraDevice.ts +++ b/package/src/CameraDevice.ts @@ -80,14 +80,6 @@ export interface CameraDeviceFormat { * Minimum supported ISO value */ minISO: number - /** - * The minimum Exposure-Bias value this format supports. When setting the `exposure` to this value, the image is almost completely dark (under-exposed). - */ - minExposure: number - /** - * The maximum Exposure-Bias value this format supports. When setting the `exposure` to this value, the image is almost completely bright (over-exposed). - */ - maxExposure: number /** * The video field of view in degrees */ @@ -204,6 +196,14 @@ export interface CameraDevice { * })) */ neutralZoom: number + /** + * The minimum Exposure-Bias value this format supports. When setting the `exposure` to this value, the image is almost completely dark (under-exposed). + */ + minExposure: number + /** + * The maximum Exposure-Bias value this format supports. When setting the `exposure` to this value, the image is almost completely bright (over-exposed). + */ + maxExposure: number /** * All available formats for this camera device. Use this to find the best format for your use case and set it to the Camera's {@linkcode CameraProps.format | Camera's .format} property. * diff --git a/package/src/CameraProps.ts b/package/src/CameraProps.ts index b6edda8..73bb8eb 100644 --- a/package/src/CameraProps.ts +++ b/package/src/CameraProps.ts @@ -116,7 +116,7 @@ export interface CameraProps extends ViewProps { * * The Camera will still continue to auto-adjust exposure and focus, but will premultiply the exposure setting with the provided value here. * - * This values ranges from {@linkcode CameraDeviceFormat.minExposure format.minExposure} to {@linkcode CameraDeviceFormat.maxExposure format.maxExposure}. + * This values ranges from {@linkcode CameraDevice.minExposure device.minExposure} to {@linkcode CameraDevice.maxExposure device.maxExposure}. * * The value between min- and max supported exposure is considered the default, neutral value. */