图像调整大小后绘制边界框,React为本地

vmdwslir  于 2023-03-09  发布在  React
关注(0)|答案(1)|浏览(135)
    • bounty将在7天后过期**。回答此问题可获得+50声望奖励。219CID希望引起更多人关注此问题。

我想在image中画一个包围猴子脸的边界框,坐标如下:

64,111
347,111, 
64,304, 
347,304

这是所需的外观:

我使用resizeMode = contain显示图像,这会导致边界框错位和大小错误。
我认为矩形的左上角应该是64,111(使用绝对位置),宽度和高度应该正好是两点之间的差值(width = 283和height = 193)。我知道由于图像没有以其完整的高度和宽度显示,所以我需要相应地缩小边界框。然而,我无法计算图像调整大小后的最终大小。我如何计算才能正确显示边界框?当我这样使用onLayout时:

onLayout={(event) => {
const {x, y, width, height} = event.nativeEvent.layout;
}}

高度总是不正确的,因为它包括其图像和背景的总高度(当我所有我需要的是图像本身的高度)
下面是我的代码:https://snack.expo.dev/@melampus123/forlorn-raisins

5n0oy7gb

5n0oy7gb1#

这比预期的要难,因为正如你所说,onLayout不包括图像被缩小的大小。我以为onLoad也会缩小图像,但那是不可能的。所以我想知道缩小大小的唯一方法就是自己重新计算。如果你知道原始图像的大小,以及它将被包含到的视图的大小,你就可以做到这一点。
使用图像大小挂钩:

import { useState, useEffect } from 'react';
import { Image } from 'react-native';
export default function useImageSize(imgUri) {
  const [size, setSize] = useState({
    width: 0,
    height: 0,
  });
  const [error, setError] = useState(null);
  useEffect(() => {
    Image.getSize(
      imgUri,
      (width, height) => setSize({ width, height }),
      setError
    );
  }, []);
  return [size, error];
}

视图大小钩子(我实际上经常使用钩子,所以它在 typescript 中)

/*https://stackoverflow.com/questions/56738500/react-native-onlayout-with-react-hooks*/
import { useCallback, useState } from 'react';
import { LayoutChangeEvent, LayoutRectangle } from 'react-native';

const defaultInitVal = {
  x: 0,
  y: 0,
  width: 0,
  height: 0,
};

export default function useLayout(
  initialVal?: Partial<LayoutRectangle>
): [LayoutRectangle, (l: LayoutChangeEvent) => void] {
  initialVal = initialVal || {};
  const [size, setSize] = useState({
    ...defaultInitVal,
    ...initialVal,
  } as LayoutRectangle);

  const onLayout = useCallback((e: LayoutChangeEvent) => {
    const layout = e.nativeEvent.layout;
    setSize(layout);
  }, []);
  return [size, onLayout];
}

将所有功能整合在一起:

import * as React from 'react';
import { Text, View, StyleSheet, Image, SafeAreaView } from 'react-native';
import useLayout from './useLayout';
import useImageSize from './useImageSize'

const imgUri =
  'https://discovery.sndimg.com/content/dam/images/discovery/fullset/2021/4/30/GettyImages-1189192456.jpg.rend.hgtvcom.406.406.suffix/1619849704543.jpeg';

export default function App() {
  
  const [imgLayout, onImageLayout] = useLayout();
 
  const [imgDimensions,imgSizeErr] = useImageSize(imgUri) 
  const [boundingBox, setBoundingBox] = React.useState({
    x: 0,
    y: 0,
    width: 0,
    height: 0,
  });
  
  React.useEffect(() => {
    if (!imgDimensions.width || !imgDimensions.height) {
      console.log('Image dimensions not loaded yet');
    } else {
      const { width: originalWidth, height: originalHeight } = imgDimensions;
      console.log(imgDimensions);
      const { width: containerWidth, height: containerHeight } = imgLayout;
      const imgAspectRatio = originalWidth / originalHeight;
      // find image dimension closest to container dimension
      const widthDiff = Math.abs(originalWidth - containerWidth);
      const heightDiff = Math.abs(originalHeight - containerHeight);
      let imgWidth, imgHeight;
      // if width is closest then  scale imgHeight to containerWidth
      if (widthDiff < heightDiff) {
        imgWidth = containerWidth;
        imgHeight = imgWidth * imgAspectRatio;
      }
      // if height is closest scale imgWidth to containerWidth
      else {
        imgHeight = containerHeight;
        imgWidth = imgHeight * imgAspectRatio;
      }
      setBoundingBox({
        x: imgLayout.left || imgLayout.x,
        y: imgLayout.top || imgLayout.y,
        width: imgWidth,
        height: imgHeight,
      });
    }
  }, [imgDimensions, imgLayout]);
  React.useEffect(() => {
    console.log('boundingBox:\n' + JSON.stringify(boundingBox, null, 2));
  }, [boundingBox]);
  React.useEffect(() => {
    console.log('imgLayout:\n' + JSON.stringify(imgLayout, null, 2));
  }, [imgLayout]);
  return (
    <SafeAreaView style={styles.container}>
      <Text> Monkey </Text>
      <Image
        style={styles.image}
        source={{
          uri: imgUri,
        }}
        onLayout={onImageLayout}
        resizeMode="contain"
      />
      <View
        style={{
          position: 'absolute',
          borderWidth: 3,
          ...boundingBox,
          // I thought that onLayout would provide
          // values reflecting the movement caused by
          // justifyContent and alignItems but it doesnt
          // so this I'll just calculate the centering
          top: boundingBox.y + (imgLayout.height - boundingBox.height) / 2,
          zIndex: 400,
        }}
      />
    </SafeAreaView>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 20,
    // justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: 'lightblue',
    flexDirection: 'column',
  },
  image: {
    width: 300,
    height: 800,
    backgroundColor: 'orange',
  },
});

Demo

相关问题