react-native-vision-camera icon indicating copy to clipboard operation
react-native-vision-camera copied to clipboard

How to limit codeScanner to View height and width❓

Open sorinrinse opened this issue 2 years ago • 14 comments

Question

Let's say the Camera has 200 width and 200 height and the device height and width is 600 and 400.

Currently, it's scanning QRs out of view, I tried to implement a boundary check with onLayout and a few more tricks but it varies so much between devices (iOS and Android, orientation...) that it works on most but it doesn't on a few that I still need to support.

Any tips on doing this in the JS side in a more elegant way without using resizeMode='contain'?

What I tried

No response

VisionCamera Version

3.6.4

Additional information

sorinrinse avatar Nov 03 '23 10:11 sorinrinse

did you find the solution?

Bayartogtokh avatar Nov 07 '23 06:11 Bayartogtokh

did you find the solution?

Using resizeMode for now.

sorinrinse avatar Nov 07 '23 07:11 sorinrinse

https://react-native-vision-camera.com/docs/api/interfaces/CodeScanner#regionofinterest

xtl-geiger avatar Nov 07 '23 17:11 xtl-geiger

https://react-native-vision-camera.com/docs/api/interfaces/CodeScanner#regionofinterest

Doesn't work

sorinrinse avatar Nov 07 '23 17:11 sorinrinse

What exactly is not working about it?

xtl-geiger avatar Nov 07 '23 17:11 xtl-geiger

What exactly is not working about it?

I was running into the same issues as before (mentioned in the first comment) when I was using onLayout to find out the frame size and ignore any scans outside of the view. How are you using it to only scan QRs that appear on the resized view of a Camera component?

sorinrinse avatar Nov 07 '23 17:11 sorinrinse

Perhaps see here: https://github.com/mrousavy/react-native-vision-camera/issues/2014#issuecomment-1769294150

spsaucier avatar Nov 08 '23 15:11 spsaucier

@spsaucier I have checked your soultion, those corner points calculated are still relative to camera view not to phone screen right, since values are too big to be screen relative? Do you have idea or solution how to convert those coordinates to screen relative values, I'm having hard time figuring that out?

SocDario avatar Nov 26 '23 19:11 SocDario

@SocDario The solution I posted works 90% of the time, but there are a couple of iPhone models which do not properly report the resolution, so I have stopped using it. There is a solution in newer versions of vision camera though.

spsaucier avatar Nov 26 '23 20:11 spsaucier

@spsaucier Yeah they recently added corners property on scan result, but also as far as I know camera view relative coordinates and not well documented about implementation

SocDario avatar Nov 26 '23 20:11 SocDario

You're right that it's not documented well. It does seem to be working everywhere, though. I'll post my new version tomorrow.

spsaucier avatar Nov 26 '23 21:11 spsaucier

@spsaucier Thanks mate, you are life saver!

SocDario avatar Nov 26 '23 21:11 SocDario


  const actOnBestFitCode = useCallback(
    (data: Code[], frame: CodeScannerFrame) => {
      if (!frame.width || !frame.height) return;

      const frameLongSide = Math.max(frame.width, frame.height);
      const frameShortSide = Math.min(frame.width, frame.height);

      const foundCodes = data.filter(code => {
        if (code.value) {
          const xValues = code.corners?.map(p => p.x) || [];
          const yValues = code.corners?.map(p => p.y) || [];
          const minX = Math.min(...xValues);
          const maxX = Math.max(...xValues);
          const minY = Math.min(...yValues);
          const maxY = Math.max(...yValues);

          const isLandscape =
            (device?.sensorOrientation || '').indexOf('landscape') > -1;
          const minShortSide = isLandscape ? minY : minX;
          const maxShortSide = isLandscape ? maxY : maxX;
          const minLongSide = isLandscape ? minX : minY;
          const maxLongSide = isLandscape ? maxX : maxY;

          // Ensure code is within the 'scanning area' square in the center
          const shortSidePad = frameShortSide / 18;
          const longSidePad = frameLongSide / 3.5;
          if (
            minShortSide > shortSidePad &&
            maxShortSide < frameShortSide - shortSidePad &&
            minLongSide > longSidePad &&
            maxLongSide < frameLongSide - longSidePad
          ) {
            return true;
          }
        }
        return false;
      });
      if (!foundCodes.length) {
        return;
      }
      const fitScores = foundCodes.map(code => {
        if (!code.frame) return 5000; // This is already filtered out above
        const xValues = code.corners?.map(p => p.x) || [];
        const yValues = code.corners?.map(p => p.y) || [];
        const minX = Math.min(...xValues);
        const maxX = Math.max(...xValues);
        const minY = Math.min(...yValues);
        const maxY = Math.max(...yValues);

        const isLandscape =
          (device?.sensorOrientation || '').indexOf('landscape') > -1;
        const minShortSide = isLandscape ? minY : minX;
        const maxShortSide = isLandscape ? maxY : maxX;
        const minLongSide = isLandscape ? minX : minY;
        const maxLongSide = isLandscape ? maxX : maxY;

        const actualXCenter = (minShortSide + maxShortSide) / 2;
        const idealXCenter = frameShortSide / 2;
        const distanceToXCenter = Math.abs(idealXCenter - actualXCenter);
        const actualYCenter = (minLongSide + maxLongSide) / 2;
        const idealYCenter = frameLongSide / 2;
        const distanceToYCenter = Math.abs(idealYCenter - actualYCenter);
        const fitScore = distanceToXCenter + distanceToYCenter;
        return fitScore;
      });
      const bestFitScore = Math.min(...fitScores);
      const bestFitIndex = fitScores.indexOf(bestFitScore);
      const type = foundCodes[bestFitIndex].type;
      const value = cleanBarcode(foundCodes[bestFitIndex].value || '');
      if (
        value &&
        type !== 'unknown' &&
        barcodeFormats.includes(type) &&
        value &&
        (validateBarcode(value) || validatePickupBarcode(value)) &&
        value !== currentBarcode
      ) {
        setCurrentBarcode(value);
      }
    },
    [barcodeFormats, currentBarcode, device]
  );

spsaucier avatar Nov 27 '23 15:11 spsaucier

@spsaucier

Thanks for example mate, I'll try it out today!

SocDario avatar Nov 27 '23 19:11 SocDario

On iOS there's regionOfInterest. Otherwise the Frame has a bounding box. And yes, resizeMode also exists for the preview

mrousavy avatar Jan 15 '24 13:01 mrousavy

This should be re-opened until regionOfInterest is within Android, closing it with that comment is unfair really.

JshGrn avatar Jul 08 '24 13:07 JshGrn

This should be re-opened until regionOfInterest is within Android, closing it with that comment is unfair really.

Thank you for your reply JshGrn, if you open a PR to add regionOfInterest to Android I will gladly review and merge it. Otherwise I don't plan on working on this, simply because it is not part of the native Android API and will probably require HUGE efforts to implement this (as we need to resize the Frame ourselves manually, causing additional performance overhead).

mrousavy avatar Jul 10 '24 08:07 mrousavy