我正在使用来自expo包的相机,我遇到了相机预览失真的问题。预览使图像在横向视图中显示较宽,在纵向视图中显示较薄。我发现的大多数解决方案都没有使用expo-camera。
相关代码:
camera.page.js:
import React from 'react';
import { View, Text } from 'react-native';
import { Camera } from 'expo-camera';
import * as Permissions from 'expo-permissions'
import { Platform } from 'react-native';
import styles from './styles';
import Toolbar from './toolbar.component';
const DESIRED_RATIO = "18:9";
export default class CameraPage extends React.Component {
camera = null;
state = {
hasCameraPermission: null,
};
async componentDidMount() {
const camera = await Permissions.askAsync(Permissions.CAMERA);
const audio = await Permissions.askAsync(Permissions.AUDIO_RECORDING);
const hasCameraPermission = (camera.status === 'granted' && audio.status === 'granted');
this.setState({ hasCameraPermission });
};
render() {
const { hasCameraPermission } = this.state;
if (hasCameraPermission === null) {
return <View />;
} else if (hasCameraPermission === false) {
return <Text>Access to camera has been denied.</Text>;
}
return (
<React.Fragment>
<View>
<Camera
ref={camera => this.camera = camera}
style={styles.preview}
/>
</View>
<Toolbar/>
</React.Fragment>
);
};
};styles.js:
import { StyleSheet, Dimensions } from 'react-native';
const { width: winWidth, height: winHeight } = Dimensions.get('window');
export default StyleSheet.create({
preview: {
height: winHeight,
width: winWidth,
position: 'absolute',
left: 0,
top: 0,
right: 0,
bottom: 0,
paddingBottom: 1000,
},
alignCenter: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
bottomToolbar: {
width: winWidth,
position: 'absolute',
height: 100,
bottom: 0,
},
captureBtn: {
width: 60,
height: 60,
borderWidth: 2,
borderRadius: 60,
borderColor: "#FFFFFF",
},
captureBtnActive: {
width: 80,
height: 80,
},
captureBtnInternal: {
width: 76,
height: 76,
borderWidth: 2,
borderRadius: 76,
backgroundColor: "red",
borderColor: "transparent",
},
});我能做些什么来解决这个问题呢?
发布于 2020-08-06 19:12:38
这个有点单调乏味。
问题
基本上,问题是相机预览与屏幕的宽高比不同。据我所知,这只是Android上的一个问题:
理论
解决这个问题的方法基本上是:
的纵横比(和方向
const { height, width } = Dimensions.get('window');
const screenRatio = height / width;const [isRatioSet, setIsRatioSet] = useState(false);
// the camera must be loaded in order to
// access the supported ratios
const setCameraReady = async() => {
if (!isRatioSet) {
await prepareRatio();
}
};
return (
<Camera
onCameraReady={setCameraReady}
ref={(ref) => {
setCamera(ref);
}}>
</Camera>
);支持的纵横比
const ratios = await camera.getSupportedRatiosAsync();这将返回一个格式为'w:h‘的字符串数组,因此您可能会看到如下所示:
[ '4:3', '1:1', '16:9' ]基本上,您在这里尝试做的是循环遍历支持的相机比例,并确定它们中的哪一个在比例上与屏幕最接近。因为在这个例子中,我们希望预览占据整个屏幕的宽度,并且我们不介意预览在纵向模式下比屏幕短,所以我们会去掉太高的部分。
a)获取屏幕纵横比
假设屏幕是480w x 800h,那么高/宽的纵横比是1.666...如果我们在横向模式下,我们会做宽/高。
b)获取支持的摄像头宽高比
然后,我们查看每个相机的宽高比,并计算宽/高。我们计算这个值而不是像计算屏幕那样计算高度/宽度的原因是,相机的纵横比总是处于横向模式。
所以:
4:3 => 1.33331:1 => 116:9 => 1.77777c)计算支持的摄像头宽高比
对于每一个,我们从屏幕的纵横比中减去,以找出差异。任何超过屏幕长边宽高比的都将被丢弃:
4:3 => 1.333... => 0.333...的差异(最接近但不去over!)1:1 => 1 => 0.666... (最差match)16:9 => 1.777... => -0.111... (too => ) => d)最短摄像机宽高比匹配屏幕宽高比
所以我们在这个屏幕上为这个摄像头选择4:3宽高比。
e)计算用于填充和定位的摄像头宽高比和屏幕宽高比的差值。
为了将预览定位在屏幕的中心,我们可以计算屏幕高度和相机预览缩放高度之间的差值的一半。
verticalPadding = (screenHeight - bestRatio * screenWidth) / 2总而言之:
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);<Camera>组件的样式,使其具有与应用的相机纵横比相匹配的适当缩放高度,并且居中或以屏幕上的任何位置为中心。<Camera
style={[styles.cameraPreview, {marginTop: imagePadding, marginBottom: imagePadding}]}
onCameraReady={setCameraReady}
ratio={ratio}
ref={(ref) => {
setCamera(ref);
}}
/>需要注意的是,在横向模式下,相机的纵横比总是宽度:高度,但您的屏幕可能是纵向或横向的。
执行
此示例仅支持纵向模式屏幕。要支持这两种屏幕类型,您必须检查screen orientation并根据设备所处的方向更改计算。
import React, { useEffect, useState } from 'react';
import {StyleSheet, View, Text, Dimensions, Platform } from 'react-native';
import { Camera } from 'expo-camera';
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 Camera.requestPermissionsAsync();
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);
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,
}
});你可以玩Expo Snack here
结果
最后,保留比例的相机预览,它使用顶部和底部的填充来居中预览:

你也可以在你的安卓系统上使用try this code out online或者在世博会点心上使用。
发布于 2021-02-26 16:37:11
竖屏模式下的简单解决方案:
import * as React from "react";
import { Camera } from "expo-camera";
import { Dimensions } from "react-native";
const CameraComponent = () => {
const dimensions = useRef(Dimensions.get("window"));
const screenWidth = dimensions.current.width;
const height = Math.round((screenWidth * 16) / 9);
return (
<Camera
ratio="16:9"
style={{
height: height,
width: "100%",
}}
></Camera>
);
};
export default CameraComponent;https://stackoverflow.com/questions/58634905
复制相似问题