Vishal Kumar
Vishal Kumar

Reputation: 31

React Native android camera2 aspect ratio issue

I have created a library (check this for more information) for Android for Android Camera2 functionality in Android all is working good, but in the aspect ratio issue with full screen camera.

Please Check below image how it looks in preview and the real picture (Only preview has the issue not the real picture after capturing)

Captured image horizontal:

enter image description here

Preview image horizontal:

enter image description here

Captured image vertical:

enter image description here

Preview image vertical:

enter image description here

Google sample of Camera2 API have similar issue and resolved here.

But same code not working in my react native library code. May be I am using one code for image and video both and add additional code for video capture.

Upvotes: 3

Views: 4200

Answers (2)

Adonis Gaitatzis
Adonis Gaitatzis

Reputation: 3729

I answered a similar question here in great detail for anyone who wants more theory.

In short, on cameraReady you must grab the screen size, the supported camera aspect ratios, and then find the ratio that's the closest match to your screen without being too tall.

An example app looks like this:

import React, { useEffect, useState } from 'react';
import {StyleSheet, View, Text, Dimensions, Platform } from 'react-native';
import { Camera } from 'expo-camera';
import * as Permissions from 'expo-permissions';

export default function App() {
  //  camera permissions
  const [hasCameraPermission, setHasCameraPermission] = useState(null);
  const [camera, setCamera] = useState(null);

  // Screen Ratio and image padding
  const [imagePadding, setImagePadding] = useState(0);
  const [ratio, setRatio] = useState('4:3');  // default is 4:3
  const { height, width } = Dimensions.get('window');
  const screenRatio = height / width;
  const [isRatioSet, setIsRatioSet] =  useState(false);

  // on screen  load, ask for permission to use the camera
  useEffect(() => {
    async function getCameraStatus() {
      const { status } = await Permissions.askAsync(Permissions.CAMERA);
      setHasCameraPermission(status == 'granted');
    }
    getCameraStatus();
  }, []);

  // set the camera ratio and padding.
  // this code assumes a portrait mode screen
  const prepareRatio = async () => {
    let desiredRatio = '4:3';  // Start with the system default
    // This issue only affects Android
    if (Platform.OS === 'android') {
      const ratios = await camera.getSupportedRatiosAsync();

      // Calculate the width/height of each of the supported camera ratios
      // These width/height are measured in landscape mode
      // find the ratio that is closest to the screen ratio without going over
      let distances = {};
      let realRatios = {};
      let minDistance = null;
      for (const ratio of ratios) {
        const parts = ratio.split(':');
        const realRatio = parseInt(parts[0]) / parseInt(parts[1]);
        realRatios[ratio] = realRatio;
        // ratio can't be taller than screen, so we don't want an abs()
        const distance = screenRatio - realRatio; 
        distances[ratio] = realRatio;
        if (minDistance == null) {
          minDistance = ratio;
        } else {
          if (distance >= 0 && distance < distances[minDistance]) {
            minDistance = ratio;
          }
        }
      }
      // set the best match
      desiredRatio = minDistance;
      //  calculate the difference between the camera width and the screen height
      const remainder = Math.floor(
        (height - realRatios[desiredRatio] * width) / 2
      );
      // set the preview padding and preview ratio
      setImagePadding(remainder / 2);
      setRatio(desiredRatio);
      // Set a flag so we don't do this 
      // calculation each time the screen refreshes
      setIsRatioSet(true);
    }
  };

  // the camera must be loaded in order to access the supported ratios
  const setCameraReady = async() => {
    if (!isRatioSet) {
      await prepareRatio();
    }
  };

  if (hasCameraPermission === null) {
    return (
      <View style={styles.information}>
        <Text>Waiting for camera permissions</Text>
      </View>
    );
  } else if (hasCameraPermission === false) {
    return (
      <View style={styles.information}>
        <Text>No access to camera</Text>
      </View>
    );
  } else {
    return (
      <View style={styles.container}>
        {/* 
        We created a Camera height by adding margins to the top and bottom, 
        but we could set the width/height instead 
        since we know the screen dimensions
        */}
        <Camera
          style={[styles.cameraPreview, {marginTop: imagePadding, marginBottom: imagePadding}]}
          onCameraReady={setCameraReady}
          ratio={ratio}
          ref={(ref) => {
            setCamera(ref);
          }}>
        </Camera>
      </View>
    );
  }
}

const styles = StyleSheet.create({
  information: { 
    flex: 1,
    justifyContent: 'center',
    alignContent: 'center',
    alignItems: 'center',
  },
  container: {
    flex: 1,
    backgroundColor: '#000',
    justifyContent: 'center',
  },
  cameraPreview: {
    flex: 1,
  }
});

You can also try this code out online or in your Android on Expo Snack.

Upvotes: 2

William Guedes
William Guedes

Reputation: 81

More often than not, the camera sensor aspect ratio won't match the screen aspect ratio. The result is what you're currently experiencing.

Because of this mismatch in aspect ratios. You can't actually force the preview to be full screen. You have to select "valid" dimensions. So how do you do that?

There's nothing we can do about the sensor's dimensions. The question we have to answer is what should the height of my preview be? (assuming portrait mode)

Here's an example:

Sensor (assume portrait):

  • width: 50
  • height: 100
  • aspect ratio: (width / height) = 0.5

Screen (assume portrait):

  • width: 400
  • height: 1000
  • aspect ratio: (width / height) = 0.4

With the values above, your preview image will be "stretched out".

Here's how to fix it:

We know the aspect ratio we want: 0.5

width / height = 0.5

We know the width of the screen (portrait): 400

400 / height = 0.5

height = 400 / 0.5 = 800

In order to have no stretching in the x or y direction, the height (assume portrait) needs to be: preview width / desired aspect ratio

Upvotes: 2

Related Questions