Closed jzxchiang1 closed 2 years ago
Do I have to install a library like react-native-orientation-locker
for this rotation feature to work properly?
Do I have to install a library like react-native-orientation-locker for this rotation feature to work properly?
No.
Does your screen even rotate? If your react view actually rotates, then the camera should do too. How does this behave on iOS?
Yeah sorry, it was a false alarm. I'm seeing the rotation now.
Is there a way to take a "rotated" photo in landscape position but have the UI remain in portrait mode? I tried locking the screen to portrait mode, but that also makes it so that the vision-camera doesn't detect landscape mode and thus takes the photo without rotation.
In other words, have vision-camera detect rotation based on device orientation instead of UI orientation.
Or do I need to handle that myself with a 3rd party image rotation library? Thanks a lot!
I resolved it by using react-native-orientation-locker
and listening to the device orientation.
Note that device orientation doesn't work for iOS if the user has locked the orientation from their Control Center. See my comment on that issue for how to resolve it.
EDIT: Confirmed that this library doesn't support camera photo rotation if the UI doesn't also rotate.
Implemented in #715! Hope this works for you :) If you appreciate the work I do in my free time, please consider sponsoring me on GitHub, it would mean a lot to me. 🖤
@mrousavy @jzxchiang1 To get around the orientation lock issue, we can copy the react-native-camera
's code that checks the gravity vector to guesstimate the orientation https://github.com/react-native-camera/react-native-camera/blob/master/ios/RN/RNSensorOrientationChecker.m
Alternatively if you don't wanna touch this library's code you can also use react-native-sensors
with the hook below
import { useLayoutEffect, useRef } from 'react';
import { Platform } from 'react-native';
import { gravity } from 'react-native-sensors';
type Rotation = 'top' | 'down' | 'right' | 'left';
export const useDeviceRotationSensor = (
callback: (rotation: Rotation, degree: number) => void,
) => {
const callbackRef = useRef(callback);
callbackRef.current = callback;
useLayoutEffect(() => {
// We use gravity sensor here because react-native-orientation
// can't detect landscape orientation when the device's orientation is locked
const subscription = gravity.subscribe(({ x, y }) => {
const radian = Math.atan2(y, x);
const degree = (radian * 180) / Math.PI;
let rotation: Rotation = 'left';
if (degree > -135) rotation = 'top';
if (degree > -45) rotation = 'right';
if (degree > 45) rotation = 'down';
if (degree > 135) rotation = 'left';
if (Platform.OS === 'android') {
rotation = 'right';
if (degree > -135) rotation = 'down';
if (degree > -45) rotation = 'left';
if (degree > 45) rotation = 'top';
if (degree > 135) rotation = 'right';
}
callbackRef.current(rotation, degree);
});
return () => subscription.unsubscribe();
}, []);
};
In your component
useDeviceRotationSensor((rotation) => {
// These still work when the device orientation is unlocked
setCameraOrientation('landscapeRight');
if (rotation === 'top') setCameraOrientation('portrait');
if (rotation === 'right') setCameraOrientation('landscapeLeft');
if (rotation === 'down') setCameraOrientation('portraitUpsideDown');
if (rotation === 'left') setCameraOrientation('landscapeRight');
});
That's weird. This should be detected automatically by VisionCamera
I see there’s this updateOrientation method https://github.com/mrousavy/react-native-vision-camera/blob/8f327267d3189f357a46b0e9217c3aa7b9a8d800/ios/CameraView%2BOrientation.swift#L39, but it doesn’t really do what the RNSensorOrientationChecker does in RNCamera. It needs to tap into the Gravity Sensor.
wo @thomasttvo I was about to look at the old code from react-native-camera to make this feature works. Going to try it asap
Thanks @thomasttvo, I was able to fix it with react-native-sensors
@mrousavy , when taking picture on Android in portrait mode, the orientation is 0 plus width is less than height. However, when taking picture on iOS on portrait mode it returns orientation of 6 and in metadata, width value is higher than height value. I think iOS metadata is wrong and needs to be fixed.
I also have the same issue, my photos on iOS have orientation 6.
@mrousavy @jzxchiang1 To get around the orientation lock issue, we can copy the
react-native-camera
's code that checks the gravity vector to guesstimate the orientation https://github.com/react-native-camera/react-native-camera/blob/master/ios/RN/RNSensorOrientationChecker.mAlternatively if you don't wanna touch this library's code you can also use
react-native-sensors
with the hook belowimport { useLayoutEffect, useRef } from 'react'; import { Platform } from 'react-native'; import { gravity } from 'react-native-sensors'; type Rotation = 'top' | 'down' | 'right' | 'left'; export const useDeviceRotationSensor = ( callback: (rotation: Rotation, degree: number) => void, ) => { const callbackRef = useRef(callback); callbackRef.current = callback; useLayoutEffect(() => { // We use gravity sensor here because react-native-orientation // can't detect landscape orientation when the device's orientation is locked const subscription = gravity.subscribe(({ x, y }) => { const radian = Math.atan2(y, x); const degree = (radian * 180) / Math.PI; let rotation: Rotation = 'left'; if (degree > -135) rotation = 'top'; if (degree > -45) rotation = 'right'; if (degree > 45) rotation = 'down'; if (degree > 135) rotation = 'left'; if (Platform.OS === 'android') { rotation = 'right'; if (degree > -135) rotation = 'down'; if (degree > -45) rotation = 'left'; if (degree > 45) rotation = 'top'; if (degree > 135) rotation = 'right'; } callbackRef.current(rotation, degree); }); return () => subscription.unsubscribe(); }, []); };
In your component
useDeviceRotationSensor((rotation) => { // These still work when the device orientation is unlocked setCameraOrientation('landscapeRight'); if (rotation === 'top') setCameraOrientation('portrait'); if (rotation === 'right') setCameraOrientation('landscapeLeft'); if (rotation === 'down') setCameraOrientation('portraitUpsideDown'); if (rotation === 'left') setCameraOrientation('landscapeRight'); });
This works but the camera scene momentarily freezes while the vision camera changes orientation.
Hey - I'm tracking Orientation in this feature request/issue now: https://github.com/mrousavy/react-native-vision-camera/issues/1891 ✨
Make sure to upvote or sponsor to support this feature, and leave a comment if you have any additional thoughts/ideas.
@mrousavy @jzxchiang1 To get around the orientation lock issue, we can copy the
react-native-camera
's code that checks the gravity vector to guesstimate the orientation https://github.com/react-native-camera/react-native-camera/blob/master/ios/RN/RNSensorOrientationChecker.m Alternatively if you don't wanna touch this library's code you can also usereact-native-sensors
with the hook belowimport { useLayoutEffect, useRef } from 'react'; import { Platform } from 'react-native'; import { gravity } from 'react-native-sensors'; type Rotation = 'top' | 'down' | 'right' | 'left'; export const useDeviceRotationSensor = ( callback: (rotation: Rotation, degree: number) => void, ) => { const callbackRef = useRef(callback); callbackRef.current = callback; useLayoutEffect(() => { // We use gravity sensor here because react-native-orientation // can't detect landscape orientation when the device's orientation is locked const subscription = gravity.subscribe(({ x, y }) => { const radian = Math.atan2(y, x); const degree = (radian * 180) / Math.PI; let rotation: Rotation = 'left'; if (degree > -135) rotation = 'top'; if (degree > -45) rotation = 'right'; if (degree > 45) rotation = 'down'; if (degree > 135) rotation = 'left'; if (Platform.OS === 'android') { rotation = 'right'; if (degree > -135) rotation = 'down'; if (degree > -45) rotation = 'left'; if (degree > 45) rotation = 'top'; if (degree > 135) rotation = 'right'; } callbackRef.current(rotation, degree); }); return () => subscription.unsubscribe(); }, []); };
In your component
useDeviceRotationSensor((rotation) => { // These still work when the device orientation is unlocked setCameraOrientation('landscapeRight'); if (rotation === 'top') setCameraOrientation('portrait'); if (rotation === 'right') setCameraOrientation('landscapeLeft'); if (rotation === 'down') setCameraOrientation('portraitUpsideDown'); if (rotation === 'left') setCameraOrientation('landscapeRight'); });
This works but the camera scene momentarily freezes while the vision camera changes orientation.
Hi, the problem is because every 100ms the effect is reading the orientation of the phone, so, every 100ms the camera orientation is changing.
Just create another state like "currentRotation" that saves the current orientation of the change, and only change it when it really changes, for example, from top to left.
This solved the freeze problem, also I had a problem that the app was crashing, because the camera orientation was changing every 100ms.
const [currentRotation, setCurrentRotation] = useState('top');
.
.
.
useDeviceRotationSensor(rotation => {
if (currentRotation !== rotation) {
setCurrentRotation(rotation);
}
});
.
.
.
useEffect(() => {
if (currentRotation === 'top' || currentRotation === 'down')
setCameraOrientation('portrait');
if (currentRotation === 'right') setCameraOrientation('landscape-left');
if (currentRotation === 'left') setCameraOrientation('landscape-right');
}, [currentRotation]);
This is not working for me on Ipad with camera V3
This is not working for me on Ipad with camera V3
yes same question here
// const [orientation, setOrientation] = useState('portrait');
useEffect(() => { setUpdateIntervalForType(SensorTypes.accelerometer, 800); // defaults to 100ms const subscription = accelerometer.subscribe( ({x, y, z}) => { const absX = Math.abs(x); const absY = Math.abs(y);
// Determine the orientation based on accelerometer data
if (absX > absY) {
if (x > 0) {
setOrientation('landscape-left');
} else {
setOrientation('landscape-right');
}
} else {
setOrientation('portrait');
}
}, (error) => { console.log('The sensor is not available', error); }, );
return () => { setTimeout(() => { // If it's the last subscription to accelerometer it will stop polling in the native API subscription.unsubscribe(); }, 1000); };
// const [orientation, setOrientation] = useState('portrait');
useEffect(() => { setUpdateIntervalForType(SensorTypes.accelerometer, 800); // defaults to 100ms const subscription = accelerometer.subscribe( ({x, y, z}) => { const absX = Math.abs(x); const absY = Math.abs(y);
// Determine the orientation based on accelerometer data if (absX > absY) { if (x > 0) { setOrientation('landscape-left'); } else { setOrientation('landscape-right'); } } else { setOrientation('portrait'); }
}, (error) => { console.log('The sensor is not available', error); }, );
return () => { setTimeout(() => { // If it's the last subscription to accelerometer it will stop polling in the native API subscription.unsubscribe(); }, 1000); };
this work for me
from where is this setCameraOrientation(...)
comes from?
@mrousavy @jzxchiang1 To get around the orientation lock issue, we can copy the
react-native-camera
's code that checks the gravity vector to guesstimate the orientation https://github.com/react-native-camera/react-native-camera/blob/master/ios/RN/RNSensorOrientationChecker.m Alternatively if you don't wanna touch this library's code you can also usereact-native-sensors
with the hook belowimport { useLayoutEffect, useRef } from 'react'; import { Platform } from 'react-native'; import { gravity } from 'react-native-sensors'; type Rotation = 'top' | 'down' | 'right' | 'left'; export const useDeviceRotationSensor = ( callback: (rotation: Rotation, degree: number) => void, ) => { const callbackRef = useRef(callback); callbackRef.current = callback; useLayoutEffect(() => { // We use gravity sensor here because react-native-orientation // can't detect landscape orientation when the device's orientation is locked const subscription = gravity.subscribe(({ x, y }) => { const radian = Math.atan2(y, x); const degree = (radian * 180) / Math.PI; let rotation: Rotation = 'left'; if (degree > -135) rotation = 'top'; if (degree > -45) rotation = 'right'; if (degree > 45) rotation = 'down'; if (degree > 135) rotation = 'left'; if (Platform.OS === 'android') { rotation = 'right'; if (degree > -135) rotation = 'down'; if (degree > -45) rotation = 'left'; if (degree > 45) rotation = 'top'; if (degree > 135) rotation = 'right'; } callbackRef.current(rotation, degree); }); return () => subscription.unsubscribe(); }, []); };
In your component
useDeviceRotationSensor((rotation) => { // These still work when the device orientation is unlocked setCameraOrientation('landscapeRight'); if (rotation === 'top') setCameraOrientation('portrait'); if (rotation === 'right') setCameraOrientation('landscapeLeft'); if (rotation === 'down') setCameraOrientation('portraitUpsideDown'); if (rotation === 'left') setCameraOrientation('landscapeRight'); });
This works but the camera scene momentarily freezes while the vision camera changes orientation.
Hi, the problem is because every 100ms the effect is reading the orientation of the phone, so, every 100ms the camera orientation is changing.
Just create another state like "currentRotation" that saves the current orientation of the change, and only change it when it really changes, for example, from top to left.
This solved the freeze problem, also I had a problem that the app was crashing, because the camera orientation was changing every 100ms.
const [currentRotation, setCurrentRotation] = useState('top'); . . . useDeviceRotationSensor(rotation => { if (currentRotation !== rotation) { setCurrentRotation(rotation); } }); . . . useEffect(() => { if (currentRotation === 'top' || currentRotation === 'down') setCameraOrientation('portrait'); if (currentRotation === 'right') setCameraOrientation('landscape-left'); if (currentRotation === 'left') setCameraOrientation('landscape-right'); }, [currentRotation]);
Sorry, is just my state to manage the current orientation
const [cameraOrientation, setCameraOrientation] = useState('portrait');
With this state I rotate the image taken from the camera, based on portrait or landscape, with something like this when is in landscape mode:
const rotatedImage = await manipulateAsync( picture.uri, [{rotate: 270}, {resize: {width: 960, height: 540}}], {compress: 0.85}, );
from where is this
setCameraOrientation(...)
comes from?@mrousavy @jzxchiang1 To get around the orientation lock issue, we can copy the
react-native-camera
's code that checks the gravity vector to guesstimate the orientation https://github.com/react-native-camera/react-native-camera/blob/master/ios/RN/RNSensorOrientationChecker.m Alternatively if you don't wanna touch this library's code you can also usereact-native-sensors
with the hook belowimport { useLayoutEffect, useRef } from 'react'; import { Platform } from 'react-native'; import { gravity } from 'react-native-sensors'; type Rotation = 'top' | 'down' | 'right' | 'left'; export const useDeviceRotationSensor = ( callback: (rotation: Rotation, degree: number) => void, ) => { const callbackRef = useRef(callback); callbackRef.current = callback; useLayoutEffect(() => { // We use gravity sensor here because react-native-orientation // can't detect landscape orientation when the device's orientation is locked const subscription = gravity.subscribe(({ x, y }) => { const radian = Math.atan2(y, x); const degree = (radian * 180) / Math.PI; let rotation: Rotation = 'left'; if (degree > -135) rotation = 'top'; if (degree > -45) rotation = 'right'; if (degree > 45) rotation = 'down'; if (degree > 135) rotation = 'left'; if (Platform.OS === 'android') { rotation = 'right'; if (degree > -135) rotation = 'down'; if (degree > -45) rotation = 'left'; if (degree > 45) rotation = 'top'; if (degree > 135) rotation = 'right'; } callbackRef.current(rotation, degree); }); return () => subscription.unsubscribe(); }, []); };
In your component
useDeviceRotationSensor((rotation) => { // These still work when the device orientation is unlocked setCameraOrientation('landscapeRight'); if (rotation === 'top') setCameraOrientation('portrait'); if (rotation === 'right') setCameraOrientation('landscapeLeft'); if (rotation === 'down') setCameraOrientation('portraitUpsideDown'); if (rotation === 'left') setCameraOrientation('landscapeRight'); });
This works but the camera scene momentarily freezes while the vision camera changes orientation.
Hi, the problem is because every 100ms the effect is reading the orientation of the phone, so, every 100ms the camera orientation is changing. Just create another state like "currentRotation" that saves the current orientation of the change, and only change it when it really changes, for example, from top to left. This solved the freeze problem, also I had a problem that the app was crashing, because the camera orientation was changing every 100ms.
const [currentRotation, setCurrentRotation] = useState('top'); . . . useDeviceRotationSensor(rotation => { if (currentRotation !== rotation) { setCurrentRotation(rotation); } }); . . . useEffect(() => { if (currentRotation === 'top' || currentRotation === 'down') setCameraOrientation('portrait'); if (currentRotation === 'right') setCameraOrientation('landscape-left'); if (currentRotation === 'left') setCameraOrientation('landscape-right'); }, [currentRotation]);
Question
When testing on both an iOS device and an Android simulator with v2.8.3 of this library, I noticed the
metadata
fortakePhoto
always hasOrientation: 6
.Even when I physically rotate my iPhone (or click the rotate right/left buttons for the Pixel emulator), the metadata always has an Orientation value of 6, which is the default 90 degree clockwise rotation of landscape mode, i.e. the upright portrait mode. My end goal is to take photos in landscape mode, and have the photos also be stored in "landscape mode".
How do I take advantage of the rotation feature that was available starting from v2.5.0? How does this library listen for orientation changes? I checked my AndroidManifest.xml and XCode settings, and I don't lock the device to portrait only... I don't even know how to do that...
Couldn't find any examples in the repo. Thanks!
What I tried
No response
VisionCamera Version
2.8.3
Additional information