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

❓ How to prevent the flip/mirror? #966

Open
3 of 4 tasks
pierroo opened this issue Apr 5, 2022 · 25 comments
Open
3 of 4 tasks

❓ How to prevent the flip/mirror? #966

pierroo opened this issue Apr 5, 2022 · 25 comments
Labels
💭 question Further information is requested

Comments

@pierroo
Copy link

pierroo commented Apr 5, 2022

Question

It feels weird when in selfie mode the resulting image is mirrored / flipped.
When you check the main app (snapchat / tiktok), they don't do the flip, to not distract / confuse the user.
Is there any way to cancel the flip?

What I tried

No response

VisionCamera Version

^2.13.1

Additional information

@pierroo pierroo added the 💭 question Further information is requested label Apr 5, 2022
@mrousavy
Copy link
Owner

mrousavy commented Apr 6, 2022

Hey, what do you mean with "they don't do the flip"?

A selfie with VisionCamera looks exactly like a selfie on Snapchat, no?

@pierroo
Copy link
Author

pierroo commented Apr 6, 2022

Actually no; just using the vision camera example, we can clearly see that from the time we press the button capture, and move to the mediapage, the mediapage displays a "mirrored" image of the one I saw in real time from the camera page.

This flip is also mentioned here (#130) , although my issue is not the same.
I just want to get rid of this flipping function, users feel naturally "less attractive" when they are being displayed in mirrored image since they are not used to see this when opening their camera.

(just try taking a photo with your hands raised at the left of your face, from camerapage to media page you will see your hands at the other side of the photo)

@mrousavy
Copy link
Owner

mrousavy commented Apr 6, 2022

can you please share a screen recording? Are you talking about Android or iOS?

@pierroo
Copy link
Author

pierroo commented Apr 6, 2022

It's happening on android, haven't tested iOS yet.
Will share a screenshot in the next few hours if needed

@pierroo
Copy link
Author

pierroo commented Apr 6, 2022

There it is, first is camerapage screenshot, second is mediapage; we can clearly see everything is mirrored from mediapage, thus from the photo generated.
Same happens with video unfortunately

IMG_20220406_124003
IMG_20220406_124036

Edit: as mentioned above, it only happens in selfie mode, of course.

@siddharth-kt
Copy link

siddharth-kt commented Apr 10, 2022

Hi @pierroo , did you got any solution ?

On recording video from front camera.
Output video comes mirrored.

@pierroo
Copy link
Author

pierroo commented Apr 10, 2022

Unfortunately none, @mrousavy if you happen to pass by did you have the chance to look into it ?

@mrousavy
Copy link
Owner

No, I haven't. Does this happen on iOS as well?

@siddharth-kt
Copy link

@mrousavy i am using vision-camera for android only. So for me issue is on android.

@pierroo
Copy link
Author

pierroo commented Apr 11, 2022

No, I haven't. Does this happen on iOS as well?

Surprisingly no, it does not.
Only happens on android.

@mrousavy
Copy link
Owner

Okay yea I think I recall it being an Android issue / CameraX limitation...

@pierroo
Copy link
Author

pierroo commented Apr 11, 2022

Thank you for your additional input.
Does that mean it's a lost cause for now, or is there some ongoing investigation about this issue?
So we can know where to stand and whether or not we should adjust our release timeline :)
Thank you!

@mrousavy
Copy link
Owner

I currently don't have enough free time to fix this issue, but I am also pretty sure that CameraX does not support this out of the box so this comes with a performance penalty.

@amaljosea
Copy link

amaljosea commented Apr 14, 2022

I am also able to reproduce this issue on android.

Some observations:

  • Not reproducible in iOS
  • Seems like working as expected in simulators on the PC
  • Only reproducible when using the front camera

Recording of the issue (Please download and play):

IMG_8311.MOV

Example:

Questions:

Version:

    "react-native-vision-camera": "^2.12.1",

Code:

(Note: Some additional functionalities are also there in the code)

import React, { useRef, useState } from 'react';
import {
  Image,
  Text,
  TouchableOpacity,
  SafeAreaView,
  View,
  Dimensions,
  BackHandler,
} from 'react-native';
import { Camera, useCameraDevices } from 'react-native-vision-camera';
import { useIsFocused, useFocusEffect } from '@react-navigation/native';
import useCameraPermission from 'hooks/useCameraPermission';
import arrow from 'assets/buttons/arrow.png';
import { useMainContext } from 'contexts/MainContext';
import { cameraSides } from 'constants';
import { storeImageToDocuments } from 'utils/imageStorage';
import ReadingsImage from 'components/ReadingsImage';
import BackButton from '../BackButton';
import DateRow from '../Tracker/DateRow';
import FilePickerButton from './FilePickerButton';
import CameraSwitchButton from './CameraSwitchButton';
import OverlaySwitchButton from './OverlaySwitchButton';
import CameraPermissionButton from './CameraPermissionButton';
import TimerButton from './TimerButton';

const windowWidth = Dimensions.get('window').width;
const windowHeight = Dimensions.get('window').height;

const VisonCamera = ({
  label,
  overlayUrl,
  imageUrl,
  setImageUrl,
  date,
  setDate,
  onNextClick,
  isDateEditable,
  onSkip,
}) => {
  const [count, setCount] = useState(0);
  const [isCapturing, setIsCapturing] = useState(false);
  const isFocused = useIsFocused();
  const { p } = useCameraPermission();
  const camera = useRef(null);

  useFocusEffect(
    React.useCallback(() => {
      const onBackPress = () => {
        if (imageUrl) {
          setImageUrl(null);
          return true;
        }
        return false;
      };

      BackHandler.addEventListener('hardwareBackPress', onBackPress);

      return () => BackHandler.removeEventListener('hardwareBackPress', onBackPress);
    }, [imageUrl, setImageUrl])
  );

  const {
    dispatch,
    state: { cameraSide, isCameraTimerOn, isCameraOverlayShown },
  } = useMainContext();

  const takePhoto = async () => {
    const photo = await camera.current.takePhoto();

    const { imageUrl: tempImageUrl } = await storeImageToDocuments({
      src: photo.path,
    });
    setImageUrl(tempImageUrl);
    setIsCapturing(false);
  };

  const handlePhotoClick = () => {
    if (isCapturing) {
      return;
    }
    setIsCapturing(true);
    if (!isCameraTimerOn) {
      takePhoto();
    } else {
      let c = 3;
      const a = setInterval(() => {
        if (c < 1) {
          clearInterval(a);
          setCount(0);
          takePhoto();
        }
        // eslint-disable-next-line no-plusplus
        setCount(c--);
      }, 1000);
    }
  };

  const devices = useCameraDevices();
  const device = devices[cameraSide];

  return (
    <SafeAreaView
      style={{
        flex: 1,
        backgroundColor: 'black',
        justifyContent: 'center',
        alignItems: 'center',
        position: 'relative',
      }}
    >
      <View
        style={{
          position: 'absolute',
          top: 0,
          width: '100%',
          paddingTop: 40,
          paddingLeft: 20,
          zIndex: 99,
        }}
      >
        <BackButton
          color="white"
          onPress={
            imageUrl
              ? () => {
                  setImageUrl(null);
                }
              : undefined
          }
        />
        <Text
          style={{
            marginTop: 10,
            fontSize: 25,
            color: 'white',
            textAlign: 'center',
          }}
          numberOfLines={1}
        >
          Track your
          <Text
            style={{
              color: 'grey',
              textAlign: 'center',
            }}
          >
            {' '}
            {label}
          </Text>
        </Text>
        <DateRow date={date} setDate={setDate} isDateEditable={isDateEditable} textColor="white" />
      </View>
      {!!count && (
        <View style={{ position: 'absolute', zIndex: 299 }}>
          <Text style={{ fontSize: 80, color: 'white' }}>{count}</Text>
        </View>
      )}
      {!imageUrl && (
        <View style={{ position: 'absolute', bottom: 65, right: 20, zIndex: 299 }}>
          <FilePickerButton setImageUrl={setImageUrl} />
        </View>
      )}
      {p === 'denied' && <CameraPermissionButton />}
      {p === 'authorized' && !device && !imageUrl && (
        <Text style={{ color: 'red' }}>Loading devices...</Text>
      )}
      {p === 'authorized' && device && !imageUrl && (
        <>
          {overlayUrl && (
            <View style={{ position: 'absolute', bottom: 165, left: 20, zIndex: 299 }}>
              <OverlaySwitchButton
                isOverlayShown={isCameraOverlayShown}
                onPress={() => {
                  dispatch({
                    type: 'UPDATE_STATE',
                    value: {
                      isCameraOverlayShown: !isCameraOverlayShown,
                    },
                  });
                }}
              />
            </View>
          )}
          <View style={{ position: 'absolute', bottom: 115, left: 20, zIndex: 299 }}>
            <TimerButton
              isTimer={isCameraTimerOn}
              onPress={() => {
                dispatch({
                  type: 'UPDATE_STATE',
                  value: {
                    isCameraTimerOn: !isCameraTimerOn,
                  },
                });
              }}
            />
          </View>
          <View style={{ position: 'absolute', bottom: 65, left: 20, zIndex: 299 }}>
            <CameraSwitchButton
              onPress={() => {
                dispatch({
                  type: 'UPDATE_STATE',
                  value: {
                    cameraSide:
                      cameraSide === cameraSides.BACK ? cameraSides.FRONT : cameraSides.BACK,
                  },
                });
              }}
            />
          </View>
          {overlayUrl && isCameraOverlayShown && (
            <ReadingsImage
              imageUrl={overlayUrl}
              style={{
                zIndex: 1,
                width: windowWidth,
                height: windowHeight,
                position: 'absolute',
                opacity: 0.2,
              }}
            />
          )}
          <Camera
            ref={camera}
            style={{
              width: '100%',
              height: '100%',
            }}
            device={device}
            isActive={isFocused}
            photo
          />
          <TouchableOpacity
            onPress={() => {
              handlePhotoClick();
            }}
            style={{
              position: 'absolute',
              width: 50,
              height: 50,
              bottom: 70,
              backgroundColor: 'white',
              borderColor: 'grey',
              borderWidth: 4,
              borderRadius: 50,
              zIndex: 99,
            }}
          />
        </>
      )}

      {imageUrl && (
        <>
          <TouchableOpacity
            onPress={() => {
              setImageUrl(null);
            }}
            style={{
              position: 'absolute',
              zIndex: 99,
              bottom: 135,
              borderColor: 'grey',
              borderWidth: 1,
              borderRadius: 100,
              paddingHorizontal: 10,
              paddingVertical: 5,
            }}
          >
            <Text
              style={{
                fontSize: 18,
                color: 'grey',
              }}
            >
              Retake
            </Text>
          </TouchableOpacity>
          <ReadingsImage
            imageUrl={imageUrl}
            resizeMode="contain"
            style={{
              width: '100%',
              height: '100%',
            }}
          />
          <TouchableOpacity
            onPress={onNextClick}
            style={{
              position: 'absolute',
              bottom: 80,
            }}
          >
            <Image
              source={arrow}
              style={{
                width: 50,
                height: 50,
                tintColor: '#d8092e',
              }}
            />
          </TouchableOpacity>
        </>
      )}
      {onSkip && (
        <TouchableOpacity
          onPress={() => {
            onSkip();
          }}
          style={{
            position: 'absolute',
            zIndex: 99,
            bottom: 40,
            borderColor: 'grey',
            borderWidth: 1,
            borderRadius: 100,
            paddingHorizontal: 10,
            paddingVertical: 5,
          }}
        >
          <Text
            style={{
              fontSize: 18,
              color: 'grey',
            }}
          >
            Skip
          </Text>
        </TouchableOpacity>
      )}
    </SafeAreaView>
  );
};

export default VisonCamera;

@amaljosea
Copy link

@mrousavy I can give it a try to solve this if you could provide some hints.

@anatoleblanc
Copy link

Any updates on this ?

@mrousavy
Copy link
Owner

Here's the code for flipping if that helps anyone:

val flipHorizontally = lensFacing == CameraCharacteristics.LENS_FACING_FRONT
photo.save(file, flipHorizontally)

and the save func:

if (flipHorizontally) {
val milliseconds = measureTimeMillis {
bytes = flipImage(bytes)
}
Log.i(CameraView.TAG_PERF, "Flipping Image took $milliseconds ms.")
}

@anatoleblanc
Copy link

@mrousavy thanks for the quick answer !!
How can I use this code to prevent the mirror effect ?

Thanks again

@DonDes17
Copy link

Same problem here with version 2.14.0
Any idea ?

@jpike88
Copy link

jpike88 commented Sep 7, 2022

@mrousavy if you look at the iOS camera app, front camera video displays it mirrored on screen, but output video is horizontally flipped. Otherwise, things like words will be back to front.

@jpike88
Copy link

jpike88 commented Sep 7, 2022

This problem occurs on iOS too (front camera only).

@jpike88
Copy link

jpike88 commented Sep 28, 2022

I've implemented an easy solution for ios:

CameraView+RecordVideo

// get pixel format (420f, 420v, x420)
      let pixelFormat = CMFormatDescriptionGetMediaSubType(videoInput.device.activeFormat.formatDescription)
      recordingSession.initializeVideoWriter(withSettings: videoSettings,
                                                                    pixelFormat: pixelFormat, isFrontCamera: videoInput.device.position == .front)

And in RecordingSession:

 func initializeVideoWriter(withSettings settings: [String: Any], pixelFormat: OSType, isFrontCamera: Bool) {
    guard !settings.isEmpty else {
      ReactLogger.log(level: .error, message: "Tried to initialize Video Writer with empty settings!")
        return;
    }
    guard bufferAdaptor == nil else {
      ReactLogger.log(level: .error, message: "Tried to add Video Writer twice!")
        return;
    }

    let videoWriter = AVAssetWriterInput(mediaType: .video, outputSettings: settings)
    videoWriter.expectsMediaDataInRealTime = true
        
    if (isFrontCamera) {
      var transform = CGAffineTransform(scaleX: -1.0, y: 1.0);
      videoWriter.transform = transform
    }

@impu1
Copy link

impu1 commented Aug 4, 2023

So I stumbled upon this problem personally where one android device didn't mirror and another one did. An easy (maybe ugly?) solution was to replace:

const photo = await camera.current.takePhoto({})
with
const photo = Platform.OS === 'android' ? await camera.current.takeSnapshot({ quality: 85, skipMetadata: true, }) : await camera.current.takePhoto({});

Hope this helps someone out there with the same issue as me.

@FouadMagdy01
Copy link

Did anyone found a solution for this issue?

@JamesHemery
Copy link

Any solution ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
💭 question Further information is requested
Projects
None yet
Development

No branches or pull requests

10 participants