Reputation: 31
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:
Preview image horizontal:
Captured image vertical:
Preview image vertical:
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
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
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):
Screen (assume portrait):
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