Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🐛 [Android] Getting Wrong Orientation of Picture when coming back from Gallery screen #3340

Open
3 of 5 tasks
shaheerarshad01 opened this issue Dec 24, 2024 · 0 comments
Open
3 of 5 tasks
Labels
🐛 bug Something isn't working

Comments

@shaheerarshad01
Copy link

What's happening?

react-native-vision-camera@4.6.3.webm

When the camera is opened in landscape mode and a picture is taken, the image orientation is correctly set to landscape. However, if the user views the image by clicking the gallery button, navigates to the gallery screen, and then returns to the camera screen (via the cross button) to take another picture in landscape mode, the orientation of the captured image changes incorrectly from landscape to portrait.

Steps to Reproduce:

Open the camera in landscape mode.
Capture a picture in landscape orientation.
Tap the gallery button to view the captured image on the gallery screen.
Return to the camera screen by clicking the cross button in the gallery screen.
Take another picture in landscape mode.

Observed Behavior:
The orientation of the second image changes to portrait instead of remaining in landscape mode.

Expected Behavior:
The orientation of all captured images should remain consistent with the device's orientation at the time of capture, i.e., landscape mode should result in landscape-oriented images.

Reproduceable Code

import { actions } from '@';
import { useIsFocused } from '@react-navigation/native';
import { getFlashMode, getOrientationDiff } from '@selectors';
import { GlobalState, InspectionSpot, PictureOrientation } from '@types';
import { showError } from '@ui';
import { useAppState } from '@utils';
import { debounce } from 'lodash';
import React, { memo, useCallback, useRef, useEffect, useState } from 'react';
import { GestureResponderEvent, StyleSheet } from 'react-native';
import {
  Camera,
  CameraRuntimeError,
  OutputOrientation,
  TakePhotoOptions,
  Templates,
  useCameraDevice,
  useCameraFormat,
} from 'react-native-vision-camera';
import { useDispatch, useSelector } from 'react-redux';
import { Action } from 'redux';
import { ThunkDispatch } from 'redux-thunk';
import { answerListRef } from '../answers/AnswerList';
import { logTime, logTimeEnd } from '../timing';
import CameraOverlay from './CameraOverlay';
import FocusIndicator, { HIDDEN_FOCUS_POINT } from './FocusIndicator';
import PhotoCaptureIndicator from './PhotoCaptureIndicator';
import ZoomView from './ZoomView';

type Props = {
  spot: InspectionSpot;
  guidanceText: string;
  pictureOrientation: PictureOrientation;
};

const CameraView = ({ spot, pictureOrientation, guidanceText }: Props) => {
  const device = useCameraDevice('back');
  const flashMode = useSelector(getFlashMode);
  const rotation = useSelector(getOrientationDiff);
  const cameraRef = useRef<Camera>(null);
  const dispatch: ThunkDispatch<GlobalState, void, Action> = useDispatch();
  const [scale, setScale] = useState(1);
  const [isInitialized, setIsInitialized] = useState(false);
  const [isTakingPicture, setIsTakingPicture] = useState(false);
  const [focusSpot, setFocusSpot] = useState(HIDDEN_FOCUS_POINT);
  const isFocused = useIsFocused();
  const appState = useAppState();
  const format = useCameraFormat(device, Templates.Instagram);
  const [isActive, setIsActive] = useState(false);
  const [preview] = useState<OutputOrientation>('preview');
  useEffect(() => {
    setScale(1);
  }, []);

  useEffect(() => {
    if (isFocused && appState === 'active') {
      setTimeout(() => {
        setIsActive(true);
      }, 500);
    } else {
      setIsActive(false);
    }
  }, [isFocused, appState]);

  const handleInitialized = useCallback(() => {
    setIsInitialized(true);
  }, []);

  const handleError = useCallback((error: CameraRuntimeError) => {
    console.error(error);
  }, []);

  const takePicture = useCallback(async () => {
    try {
      const options: TakePhotoOptions = {
        flash: flashMode,
        enableShutterSound: false,
      };
      setIsTakingPicture(true);
      logTime('Take Picture');
      const photo = await cameraRef.current?.takePhoto(options);
      logTimeEnd('Take Picture');

      setIsTakingPicture(false);
      if (photo) {
        await dispatch(actions.answerAddPicture(`file://${photo.path}`, rotation, spot));
        setTimeout(() => {
          answerListRef?.current?.scrollToEnd();
        }, 100);
        setTimeout(() => {
          answerListRef?.current?.scrollToEnd();
        }, 1000);
      }
    } catch (e) {
      showError((e as Error).message);
    } finally {
      setIsTakingPicture(false);
    }
  }, [flashMode, rotation, dispatch, spot]);

  const debouncedHide = useCallback(
    debounce(() => {
      setFocusSpot(HIDDEN_FOCUS_POINT);
    }, 3 * 1000),
    [],
  );

  const focus = async (event: GestureResponderEvent) => {
    const { pageX, pageY } = event.nativeEvent;
    if (!device?.supportsFocus) {
      return;
    }
    const newFocusSpot = { top: pageY - 32, left: pageX - 32 };
    setFocusSpot(newFocusSpot);
    debouncedHide();
    try {
      await cameraRef.current?.focus({ x: pageX, y: pageY });
    } catch {}
  };

  const canTakePicture = device && isInitialized && isFocused && !isTakingPicture;

  if (!device) {
    logTime('Camera Initialization');
  } else {
    logTimeEnd('Camera Initialization');
  }

  return (
    <>
      {device && (
        <ZoomView setScale={setScale} onTap={focus}>
          <Camera
            ref={cameraRef}
            style={StyleSheet.absoluteFill}
            device={device}
            format={format}
            isActive={isActive}
            photo
            zoom={scale}
            lowLightBoost={device.supportsLowLightBoost}
            onInitialized={handleInitialized}
            onError={handleError}
            photoQualityBalance={'speed'}
            exposure={0}
            outputOrientation={preview}
          />

          <FocusIndicator top={focusSpot.top} left={focusSpot.left} />
        </ZoomView>
      )}
      <PhotoCaptureIndicator show={isTakingPicture} />
      <CameraOverlay
        spot={spot}
        disableSnapButton={!canTakePicture}
        pictureOrientation={pictureOrientation}
        guidanceText={guidanceText}
        onTakePicture={() => takePicture()}
      />
    </>
  );
};

CameraView.displayName = 'CameraView';

export default memo(CameraView);

Relevant log output

Running "Loss Control +" with {"rootTag":11}

Camera Device

{
  "formats": [],
  "sensorOrientation": "landscape-left",
  "hardwareLevel": "full",
  "maxZoom": 10,
  "minZoom": 1,
  "maxExposure": 9,
  "supportsLowLightBoost": false,
  "neutralZoom": 1,
  "physicalDevices": [
    "wide-angle-camera"
  ],
  "supportsFocus": true,
  "supportsRawCapture": false,
  "isMultiCam": false,
  "minFocusDistance": 5,
  "minExposure": -9,
  "name": "0 (BACK) androidx.camera.camera2",
  "hasFlash": true,
  "hasTorch": true,
  "position": "back",
  "id": "0"
}

Device

Redmi Note 13 (Android)

VisionCamera Version

4.6.3

Can you reproduce this issue in the VisionCamera Example app?

I didn't try (⚠️ your issue might get ignored & closed if you don't try this)

Additional information

@shaheerarshad01 shaheerarshad01 added the 🐛 bug Something isn't working label Dec 24, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
🐛 bug Something isn't working
Projects
None yet
Development

No branches or pull requests

1 participant