首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >世博会相机预览失真

世博会相机预览失真
EN

Stack Overflow用户
提问于 2019-10-31 07:19:57
回答 2查看 5.1K关注 0票数 11

我正在使用来自expo包的相机,我遇到了相机预览失真的问题。预览使图像在横向视图中显示较宽,在纵向视图中显示较薄。我发现的大多数解决方案都没有使用expo-camera。

相关代码:

camera.page.js:

代码语言:javascript
复制
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:

代码语言:javascript
复制
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",
    },
});

我能做些什么来解决这个问题呢?

EN

回答 2

Stack Overflow用户

发布于 2020-08-06 19:12:38

这个有点单调乏味。

问题

基本上,问题是相机预览与屏幕的宽高比不同。据我所知,这只是Android上的一个问题:

  1. 每个摄像头制造商支持不同的长宽比
  2. 每个手机制造商创建不同的屏幕长宽比

理论

解决这个问题的方法基本上是:

  1. 确定screen

的纵横比(和方向

代码语言:javascript
复制
const { height, width } = Dimensions.get('window');
const screenRatio = height / width;

  1. 等待摄像头准备就绪

代码语言:javascript
复制
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>
);

  1. 计算出camera

支持的纵横比

代码语言:javascript
复制
const ratios = await camera.getSupportedRatiosAsync();

这将返回一个格式为'w:h‘的字符串数组,因此您可能会看到如下所示:

代码语言:javascript
复制
[ '4:3', '1:1', '16:9' ]

  1. 查找摄像头与屏幕最接近的宽高比,高度不超过屏幕比例(假设您需要水平缓冲区,而不是垂直缓冲区)

基本上,您在这里尝试做的是循环遍历支持的相机比例,并确定它们中的哪一个在比例上与屏幕最接近。因为在这个例子中,我们希望预览占据整个屏幕的宽度,并且我们不介意预览在纵向模式下比屏幕短,所以我们会去掉太高的部分。

a)获取屏幕纵横比

假设屏幕是480w x 800h,那么高/宽的纵横比是1.666...如果我们在横向模式下,我们会做宽/高。

b)获取支持的摄像头宽高比

然后,我们查看每个相机的宽高比,并计算宽/高。我们计算这个值而不是像计算屏幕那样计算高度/宽度的原因是,相机的纵横比总是处于横向模式。

所以:

  • Aspect => calculation
  • 4:3 => 1.3333
  • 1:1 => 1
  • 16:9 => 1.77777

c)计算支持的摄像头宽高比

对于每一个,我们从屏幕的纵横比中减去,以找出差异。任何超过屏幕长边宽高比的都将被丢弃:

  • Aspect计算与screen
  • 4:3 => 1.333... => 0.333...的差异(最接近但不去over!)
  • 1:1 => 1 => 0.666... (最差match)
  • 16:9 => 1.777... => -0.111... (too => ) =>

d)最短摄像机宽高比匹配屏幕宽高比

所以我们在这个屏幕上为这个摄像头选择4:3宽高比。

e)计算用于填充和定位的摄像头宽高比和屏幕宽高比的差值。

为了将预览定位在屏幕的中心,我们可以计算屏幕高度和相机预览缩放高度之间的差值的一半。

代码语言:javascript
复制
verticalPadding = (screenHeight - bestRatio * screenWidth) / 2

总而言之:

代码语言:javascript
复制
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);

  1. 设置<Camera>组件的样式,使其具有与应用的相机纵横比相匹配的适当缩放高度,并且居中或以屏幕上的任何位置为中心。

代码语言:javascript
复制
<Camera
  style={[styles.cameraPreview, {marginTop: imagePadding, marginBottom: imagePadding}]}
  onCameraReady={setCameraReady}
  ratio={ratio}
  ref={(ref) => {
    setCamera(ref);
  }}
/>

需要注意的是,在横向模式下,相机的纵横比总是宽度:高度,但您的屏幕可能是纵向或横向的。

执行

此示例仅支持纵向模式屏幕。要支持这两种屏幕类型,您必须检查screen orientation并根据设备所处的方向更改计算。

代码语言:javascript
复制
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或者在世博会点心上使用。

票数 37
EN

Stack Overflow用户

发布于 2021-02-26 16:37:11

竖屏模式下的简单解决方案:

代码语言:javascript
复制
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;
票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/58634905

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档