mrousavy / react-native-vision-camera

📸 A powerful, high-performance React Native Camera library.
https://react-native-vision-camera.com
MIT License
7.58k stars 1.1k forks source link

How do we change the camera orientation❓ #624

Closed jzxchiang1 closed 2 years ago

jzxchiang1 commented 2 years ago

Question

When testing on both an iOS device and an Android simulator with v2.8.3 of this library, I noticed the metadata for takePhoto always has Orientation: 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

jzxchiang1 commented 2 years ago

Do I have to install a library like react-native-orientation-locker for this rotation feature to work properly?

mrousavy commented 2 years ago

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?

jzxchiang1 commented 2 years ago

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!

jzxchiang1 commented 2 years ago

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.

mrousavy commented 2 years ago

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. 🖤

thomasttvo commented 2 years ago

@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');
  });
mrousavy commented 2 years ago

That's weird. This should be detected automatically by VisionCamera

thomasttvo commented 2 years ago

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.

PABourdais commented 2 years ago

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

PABourdais commented 2 years ago

Thanks @thomasttvo, I was able to fix it with react-native-sensors

yestay90 commented 1 year ago

@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.

andreiwow2 commented 1 year ago

I also have the same issue, my photos on iOS have orientation 6.

ajp8164 commented 1 year ago

@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');
  });

This works but the camera scene momentarily freezes while the vision camera changes orientation.

mrousavy commented 1 year ago

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.

ingfdoaguirre commented 8 months ago

@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');
  });

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]);
matheusqfql commented 7 months ago

This is not working for me on Ipad with camera V3

IamHungry113 commented 7 months ago

This is not working for me on Ipad with camera V3

yes same question here

nabzmohd commented 6 months ago

// 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); };

nabzmohd commented 6 months ago

// 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

sugaith commented 2 months ago

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 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');
  });

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]);
ingfdoaguirre commented 2 months ago

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 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');
  });

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]);