fix: Represent neutralZoom in factor instead of percentage (#179)

* Use factor instead of percent for `neutralZoom`

* fix zoom calculation

* Update CameraPage.tsx
This commit is contained in:
Marc Rousavy 2021-06-07 10:46:53 +02:00 committed by GitHub
parent 8aec647acd
commit 555474be7d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 37 additions and 51 deletions

View File

@ -173,7 +173,7 @@ class CameraViewModule(reactContext: ReactApplicationContext) : ReactContextBase
map.putDouble("minZoom", 1.0) map.putDouble("minZoom", 1.0)
map.putDouble("maxZoom", maxScalerZoom.toDouble()) map.putDouble("maxZoom", maxScalerZoom.toDouble())
} }
map.putDouble("neutralZoom", characteristics.neutralZoomPercent.toDouble()) map.putDouble("neutralZoom", 1.0)
// TODO: Optimize? // TODO: Optimize?
val maxImageOutputSize = cameraConfig.outputFormats val maxImageOutputSize = cameraConfig.outputFormats

View File

@ -56,25 +56,3 @@ fun CameraCharacteristics.getFieldOfView(): Double {
return 2 * atan(sensorSize.bigger / (focalLengths[0] * 2)) * (180 / PI) return 2 * atan(sensorSize.bigger / (focalLengths[0] * 2)) * (180 / PI)
} }
fun CameraCharacteristics.supportsFps(fps: Int): Boolean {
return this.get(CameraCharacteristics.CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES)!!
.any { it.upper >= fps && it.lower <= fps }
}
/**
* Get the value at which the Zoom is at neutral state (wide-angle camera zoom 0) (in percent, between 0.0-1.0)
*
* * On single-camera physical devices this value will always be 0
* * On devices with multiple cameras, e.g. triple-camera, this value will be a value between 0.0 and 1.0, where the field-of-view and zoom looks "neutral"
*/
val CameraCharacteristics.neutralZoomPercent: Float
get() {
val zoomRange = if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R)
this.get(CameraCharacteristics.CONTROL_ZOOM_RATIO_RANGE)
else null
return if (zoomRange != null)
((1.0f - zoomRange.lower) / (zoomRange.upper - zoomRange.lower))
else
0.0f
}

View File

@ -95,18 +95,23 @@ export const CameraPage: NavigationFunctionComponent = ({ componentId }) => {
}, [formats, fps, enableHdr]); }, [formats, fps, enableHdr]);
//#region Animated Zoom //#region Animated Zoom
const formatMaxZoom = format?.maxZoom ?? 1; // This just maps the zoom factor to a percentage value.
const maxZoomFactor = Math.min(formatMaxZoom, MAX_ZOOM_FACTOR); // so e.g. for [min, neutr., max] values [1, 2, 128] this would result in [0, 0.0081, 1]
const neutralZoom = device?.neutralZoom ?? 0; const minZoomFactor = device?.minZoom ?? 1;
const neutralZoomScaled = (neutralZoom / maxZoomFactor) * formatMaxZoom; const neutralZoomFactor = device?.neutralZoom ?? 1;
const maxZoomScaled = (1 / formatMaxZoom) * maxZoomFactor; const maxZoomFactor = device?.maxZoom ?? 1;
const maxZoomFactorClamped = Math.min(maxZoomFactor, MAX_ZOOM_FACTOR);
const cameraAnimatedProps = useAnimatedProps( const neutralZoomOut = (neutralZoomFactor - minZoomFactor) / (maxZoomFactor - minZoomFactor);
() => ({ const neutralZoomIn = (neutralZoomOut / maxZoomFactorClamped) * maxZoomFactor;
zoom: interpolate(zoom.value, [0, neutralZoomScaled, 1], [0, neutralZoom, maxZoomScaled], Extrapolate.CLAMP), const maxZoomOut = maxZoomFactorClamped / maxZoomFactor;
}),
[maxZoomScaled, neutralZoom, neutralZoomScaled, zoom], const cameraAnimatedProps = useAnimatedProps(() => {
); const z = interpolate(zoom.value, [0, neutralZoomIn, 1], [0, neutralZoomOut, maxZoomOut], Extrapolate.CLAMP);
return {
zoom: isNaN(z) ? 0 : z,
};
}, [maxZoomOut, neutralZoomOut, neutralZoomIn, zoom]);
//#endregion //#endregion
//#region Callbacks //#region Callbacks
@ -155,8 +160,8 @@ export const CameraPage: NavigationFunctionComponent = ({ componentId }) => {
//#region Effects //#region Effects
useEffect(() => { useEffect(() => {
// Run everytime the neutralZoomScaled value changes. (reset zoom when device changes) // Run everytime the neutralZoomScaled value changes. (reset zoom when device changes)
zoom.value = neutralZoomScaled; zoom.value = neutralZoomIn;
}, [neutralZoomScaled, zoom]); }, [neutralZoomIn, zoom]);
//#endregion //#endregion
//#region Pinch to Zoom Gesture //#region Pinch to Zoom Gesture

View File

@ -68,7 +68,6 @@ extension CameraView {
guard let videoDevice = AVCaptureDevice(uniqueID: cameraId) else { guard let videoDevice = AVCaptureDevice(uniqueID: cameraId) else {
return invokeOnError(.device(.invalid)) return invokeOnError(.device(.invalid))
} }
zoom = NSNumber(value: Double(videoDevice.neutralZoomPercent))
videoDeviceInput = try AVCaptureDeviceInput(device: videoDevice) videoDeviceInput = try AVCaptureDeviceInput(device: videoDevice)
guard captureSession.canAddInput(videoDeviceInput!) else { guard captureSession.canAddInput(videoDeviceInput!) else {
return invokeOnError(.parameter(.unsupportedInput(inputDescriptor: "video-input"))) return invokeOnError(.parameter(.unsupportedInput(inputDescriptor: "video-input")))

View File

@ -96,8 +96,8 @@ final class CameraViewManager: RCTViewManager {
"hasFlash": $0.hasFlash, "hasFlash": $0.hasFlash,
"hasTorch": $0.hasTorch, "hasTorch": $0.hasTorch,
"minZoom": $0.minAvailableVideoZoomFactor, "minZoom": $0.minAvailableVideoZoomFactor,
"neutralZoom": $0.neutralZoomFactor,
"maxZoom": $0.maxAvailableVideoZoomFactor, "maxZoom": $0.maxAvailableVideoZoomFactor,
"neutralZoom": $0.neutralZoomPercent,
"isMultiCam": $0.isMultiCam, "isMultiCam": $0.isMultiCam,
"supportsDepthCapture": false, // TODO: supportsDepthCapture "supportsDepthCapture": false, // TODO: supportsDepthCapture
"supportsRawCapture": false, // TODO: supportsRawCapture "supportsRawCapture": false, // TODO: supportsRawCapture

View File

@ -9,6 +9,12 @@
import AVFoundation import AVFoundation
extension AVCaptureDevice { extension AVCaptureDevice {
/**
Get the value at which the Zoom factor is neutral.
For normal wide-angle devices, this is always going to be 1.0, since this is the default scale.
For devices with an ultra-wide-angle camera, this value is going to be the value where the wide-angle device will switch over.
*/
var neutralZoomFactor: CGFloat { var neutralZoomFactor: CGFloat {
if #available(iOS 13.0, *) { if #available(iOS 13.0, *) {
if let indexOfWideAngle = self.constituentDevices.firstIndex(where: { $0.deviceType == .builtInWideAngleCamera }) { if let indexOfWideAngle = self.constituentDevices.firstIndex(where: { $0.deviceType == .builtInWideAngleCamera }) {
@ -19,14 +25,4 @@ extension AVCaptureDevice {
} }
return 1.0 return 1.0
} }
/**
Get the value at which the Zoom value is neutral, in percent (0.0-1.0)
* On single-camera physical devices, this value will always be 0.0
* On devices with multiple cameras, e.g. triple-camera, this value will be a value between 0.0 and 1.0, where the field-of-view and zoom looks "neutral"
*/
var neutralZoomPercent: CGFloat {
return (neutralZoomFactor - minAvailableVideoZoomFactor) / (maxAvailableVideoZoomFactor - minAvailableVideoZoomFactor)
}
} }

View File

@ -228,12 +228,20 @@ export interface CameraDevice {
*/ */
maxZoom: number; maxZoom: number;
/** /**
* The zoom percentage (`0.0`-`1.0`) where the camera is "neutral". * The zoom factor where the camera is "neutral".
* *
* * For single-physical cameras this property is always `0.0`. * * For single-physical cameras this property is always `1.0`.
* * For multi cameras this property is a value between `0.0` and `1.0`, where the camera is in wide-angle mode and hasn't switched to the ultra-wide (`0.5`x zoom) or telephoto camera yet. * * For multi cameras this property is a value between `minZoom` and `maxZoom`, where the camera is in _wide-angle_ mode and hasn't switched to the _ultra-wide-angle_ (`0.5`x zoom) or telephoto camera yet.
* *
* Use this value as an initial value for the zoom property if you implement custom zoom. (e.g. reanimated shared value should be initially set to this value) * Use this value as an initial value for the zoom property if you implement custom zoom. (e.g. reanimated shared value should be initially set to this value)
* @example
* const device = ...
*
* const neutralZoomPercent = (device.neutralZoom - device.minZoom) / (device.maxZoom - device.minZoom)
* const zoomFactor = useSharedValue(neutralZoomPercent) // <-- initial value so it doesn't start at ultra-wide
* const cameraProps = useAnimatedProps(() => ({
* zoom: zoomFactor.value
* }))
*/ */
neutralZoom: number; neutralZoom: number;
/** /**