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:
parent
8aec647acd
commit
555474be7d
@ -173,7 +173,7 @@ class CameraViewModule(reactContext: ReactApplicationContext) : ReactContextBase
|
||||
map.putDouble("minZoom", 1.0)
|
||||
map.putDouble("maxZoom", maxScalerZoom.toDouble())
|
||||
}
|
||||
map.putDouble("neutralZoom", characteristics.neutralZoomPercent.toDouble())
|
||||
map.putDouble("neutralZoom", 1.0)
|
||||
|
||||
// TODO: Optimize?
|
||||
val maxImageOutputSize = cameraConfig.outputFormats
|
||||
|
@ -56,25 +56,3 @@ fun CameraCharacteristics.getFieldOfView(): Double {
|
||||
|
||||
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
|
||||
}
|
||||
|
@ -95,18 +95,23 @@ export const CameraPage: NavigationFunctionComponent = ({ componentId }) => {
|
||||
}, [formats, fps, enableHdr]);
|
||||
|
||||
//#region Animated Zoom
|
||||
const formatMaxZoom = format?.maxZoom ?? 1;
|
||||
const maxZoomFactor = Math.min(formatMaxZoom, MAX_ZOOM_FACTOR);
|
||||
const neutralZoom = device?.neutralZoom ?? 0;
|
||||
const neutralZoomScaled = (neutralZoom / maxZoomFactor) * formatMaxZoom;
|
||||
const maxZoomScaled = (1 / formatMaxZoom) * maxZoomFactor;
|
||||
// This just maps the zoom factor to a percentage value.
|
||||
// so e.g. for [min, neutr., max] values [1, 2, 128] this would result in [0, 0.0081, 1]
|
||||
const minZoomFactor = device?.minZoom ?? 1;
|
||||
const neutralZoomFactor = device?.neutralZoom ?? 1;
|
||||
const maxZoomFactor = device?.maxZoom ?? 1;
|
||||
const maxZoomFactorClamped = Math.min(maxZoomFactor, MAX_ZOOM_FACTOR);
|
||||
|
||||
const cameraAnimatedProps = useAnimatedProps(
|
||||
() => ({
|
||||
zoom: interpolate(zoom.value, [0, neutralZoomScaled, 1], [0, neutralZoom, maxZoomScaled], Extrapolate.CLAMP),
|
||||
}),
|
||||
[maxZoomScaled, neutralZoom, neutralZoomScaled, zoom],
|
||||
);
|
||||
const neutralZoomOut = (neutralZoomFactor - minZoomFactor) / (maxZoomFactor - minZoomFactor);
|
||||
const neutralZoomIn = (neutralZoomOut / maxZoomFactorClamped) * maxZoomFactor;
|
||||
const maxZoomOut = maxZoomFactorClamped / maxZoomFactor;
|
||||
|
||||
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
|
||||
|
||||
//#region Callbacks
|
||||
@ -155,8 +160,8 @@ export const CameraPage: NavigationFunctionComponent = ({ componentId }) => {
|
||||
//#region Effects
|
||||
useEffect(() => {
|
||||
// Run everytime the neutralZoomScaled value changes. (reset zoom when device changes)
|
||||
zoom.value = neutralZoomScaled;
|
||||
}, [neutralZoomScaled, zoom]);
|
||||
zoom.value = neutralZoomIn;
|
||||
}, [neutralZoomIn, zoom]);
|
||||
//#endregion
|
||||
|
||||
//#region Pinch to Zoom Gesture
|
||||
|
@ -68,7 +68,6 @@ extension CameraView {
|
||||
guard let videoDevice = AVCaptureDevice(uniqueID: cameraId) else {
|
||||
return invokeOnError(.device(.invalid))
|
||||
}
|
||||
zoom = NSNumber(value: Double(videoDevice.neutralZoomPercent))
|
||||
videoDeviceInput = try AVCaptureDeviceInput(device: videoDevice)
|
||||
guard captureSession.canAddInput(videoDeviceInput!) else {
|
||||
return invokeOnError(.parameter(.unsupportedInput(inputDescriptor: "video-input")))
|
||||
|
@ -96,8 +96,8 @@ final class CameraViewManager: RCTViewManager {
|
||||
"hasFlash": $0.hasFlash,
|
||||
"hasTorch": $0.hasTorch,
|
||||
"minZoom": $0.minAvailableVideoZoomFactor,
|
||||
"neutralZoom": $0.neutralZoomFactor,
|
||||
"maxZoom": $0.maxAvailableVideoZoomFactor,
|
||||
"neutralZoom": $0.neutralZoomPercent,
|
||||
"isMultiCam": $0.isMultiCam,
|
||||
"supportsDepthCapture": false, // TODO: supportsDepthCapture
|
||||
"supportsRawCapture": false, // TODO: supportsRawCapture
|
||||
|
@ -9,6 +9,12 @@
|
||||
import AVFoundation
|
||||
|
||||
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 {
|
||||
if #available(iOS 13.0, *) {
|
||||
if let indexOfWideAngle = self.constituentDevices.firstIndex(where: { $0.deviceType == .builtInWideAngleCamera }) {
|
||||
@ -19,14 +25,4 @@ extension AVCaptureDevice {
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
@ -228,12 +228,20 @@ export interface CameraDevice {
|
||||
*/
|
||||
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 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 single-physical cameras this property is always `1.0`.
|
||||
* * 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)
|
||||
* @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;
|
||||
/**
|
||||
|
Loading…
Reference in New Issue
Block a user