我正在使用来自expo软件包的Camera,但遇到相机预览失真的问题。预览使图像在横向视图中看起来更宽,在纵向视图中看起来更细。我发现的大多数解决方案都没有使用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",
},
});
该如何解决?
答案 0 :(得分:6)
这很乏味。
基本上,问题在于摄像头预览与屏幕的宽高比不同。据我所知,这只是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.3333
1:1 => 1
16:9 => 1.77777
c)计算支持的相机长宽比
对于每一个,我们从屏幕的纵横比中减去以找到差异。超过长边屏幕长宽比的任何东西都将被丢弃:
4:3 => 1.333... => 0.333...
(最靠近而不能越过!)1:1 => 1 => 0.666...
(最差比赛)16:9 => 1.777... => -0.111...
(太宽了)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);
}}
/>
需要注意的是,在横向模式下,相机的纵横比始终为width:height,但屏幕可能是纵向或横向。
此示例仅支持纵向模式屏幕。要同时支持两种屏幕类型,您必须检查screen orientation并根据设备所处的方向更改计算。
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,
}
});
最后,使用比例保留的摄像机预览,使用顶部和底部的填充使预览居中:
您也可以try this code out online或在Android上的Expo Snack上
。答案 1 :(得分:0)
纵向模式下的简单解决方案:
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;