vision-camera-code-scanner icon indicating copy to clipboard operation
vision-camera-code-scanner copied to clipboard

add a frame for aiming scanning

Open valery-lavrik opened this issue 2 years ago • 16 comments

Somewhere I came across instructions on how to do this, but I can't find it anymore... Does anyone happen to know?

valery-lavrik avatar Jan 31 '23 06:01 valery-lavrik

It is very difficult to write such a frame yourself. I am sure there must be a ready-made solution...

valery-lavrik avatar Jan 31 '23 06:01 valery-lavrik

I'm currently working on this. I have a working iOS version here.

https://user-images.githubusercontent.com/51379148/217338554-d7ec76ce-0a8f-4cc1-a920-1d83f70bb3b2.mov

const frameProcessor = useFrameProcessor((frame) => {
    'worklet';
    const squareSize = frame.width * MARKER_WIDTH_RATIO - 100;

    const scanFrame: ScanFrame = {
      width: squareSize,
      height: squareSize,
      x: (frame.width - squareSize) / 2,
      y: (frame.height - squareSize) / 2,
    };
    const detectedBarcodes = scanBarcodes(frame, [BarcodeFormat.QR_CODE], {
      checkInverted: true,
      scanFrame: scanFrame,
    });
    runOnJS(setBarcodes)(detectedBarcodes);
  }, []);

@rodgomesc is that a feature you would like to add to the the library ? If so I can start working on a PR.

severinferard avatar Feb 07 '23 18:02 severinferard

I wish it was for android...

valery-lavrik avatar Feb 08 '23 08:02 valery-lavrik

I plan on making the feature available on Android as well, I just don't have an Android device to test it on right now.

severinferard avatar Feb 08 '23 14:02 severinferard

@severinferard Why you have 2 barcodes when you scan the QR code ?

I am also interested in this feature. Would be cool if you manage to finish it.

sanduluca avatar Feb 12 '23 08:02 sanduluca

I'm currently working on this. I have a working iOS version here.

const frameProcessor = useFrameProcessor((frame) => {
    'worklet';
    const squareSize = frame.width * MARKER_WIDTH_RATIO - 100;

    const scanFrame: ScanFrame = {
      width: squareSize,
      height: squareSize,
      x: (frame.width - squareSize) / 2,
      y: (frame.height - squareSize) / 2,
    };
    const detectedBarcodes = scanBarcodes(frame, [BarcodeFormat.QR_CODE], {
      checkInverted: true,
      scanFrame: scanFrame,
    });
    runOnJS(setBarcodes)(detectedBarcodes);
  }, []);

@rodgomesc is that a feature you would like to add to the the library ? If so I can start working on a PR.

I'm need this for iOS and Android :(

mehmettalhairmak avatar Feb 18 '23 06:02 mehmettalhairmak

I managed to make a crooked but working solution on RN. If you're interested, I can post it....

valery-lavrik avatar Feb 19 '23 12:02 valery-lavrik

@valery-lavrik can you please post it? I am interested

koolll avatar Feb 24 '23 14:02 koolll

Just do not judge strictly, I have little such experience. If you have any ideas what can be improved, please let me know

import React, { useState, useRef, useEffect } from 'react';
import { View, StyleSheet, Alert, Dimensions } from 'react-native';
import { runOnJS } from 'react-native-reanimated';
import { Camera, useCameraDevices, useFrameProcessor } from 'react-native-vision-camera';
import { scanBarcodes, BarcodeFormat } from 'vision-camera-code-scanner';

let rotateListener = null;

export default function QRScanner({
	onScan = () => { },
}) {
	const AimingBorderSize = useRef(null);
	const isFind = useRef(false);
	const [qrBorders, setQrBorders] = useState([]);
	const devices = useCameraDevices();
	const device = devices.back;



	const barCodesHandler = (detectedBarcodes, frame) => {

		if (!!isFind?.current) {
			return null;
		}

		if (!detectedBarcodes?.length) {
			setQrBorders([]);
			return null;
		}


		// 1) отфильтрую одинаковые
		const codeFiltered = detectedBarcodes.filter((value, index, self) => self.findIndex((el, i) => el.content.data === value.content.data) === index);


		// 2) нарисую им рамки
		const bordersStyles = [];
		const win = Dimensions.get('window');
		const isPortal = win.height > win.width;
		const rH = win.height / (isPortal ? Math.max(frame.height, frame.width) : Math.min(frame.height, frame.width));
		const rW = win.width / (isPortal ? Math.min(frame.height, frame.width) : Math.max(frame.height, frame.width));

		for (let i = 0; i < codeFiltered.length; i++) {
			const b = codeFiltered[i];

			bordersStyles.push({
				inside: false,
				boundingBox: {
					bottom: b.boundingBox.bottom * rH,
					left: b.boundingBox.left * rW,
					right: b.boundingBox.right * rW,
					top: b.boundingBox.top * rH,
				},
				style: {
					borderWidth: 1,
					borderColor: 'red',
					position: 'absolute',
					top: (Math.min(b.cornerPoints[0].y, b.cornerPoints[1].y)) * rH,  //  b.cornerPoints[1].y
					left: (Math.min(b.cornerPoints[0].x, b.cornerPoints[3].x)) * rW, // b.cornerPoints[3].x
					height: (b.cornerPoints[3].y - b.cornerPoints[0].y) * rH,
					width: (b.cornerPoints[1].x - b.cornerPoints[0].x) * rW,
				},
			});

			if (isInside(bordersStyles[bordersStyles.length - 1])) {
				bordersStyles[bordersStyles.length - 1].inside = true;
				bordersStyles[bordersStyles.length - 1].style.borderColor = '#00FF00';
				bordersStyles[bordersStyles.length - 1].style.borderWidth = 2;
			}
		}
		setQrBorders(bordersStyles);



		// 3) отправлю в коллбек
		const insideIndex = bordersStyles.findIndex(el => el.inside);
		if (insideIndex !== -1) {
			isFind.current = true;
			onScan(codeFiltered[insideIndex].displayValue.toLowerCase().trim());
		}

	};



	const isInside = (oneBord) => {
		const ab = AimingBorderSize.current;
		const bb = oneBord.boundingBox;

		if (bb.top > ab.yt && bb.bottom < ab.yb && bb.left > ab.xl && bb.right < ab.xr) {
			return true;
		}

		return false;
	};



	const frameProcessor = useFrameProcessor((frame) => {
		'worklet';
		const detectedBarcodes = scanBarcodes(frame, [BarcodeFormat.QR_CODE], { checkInverted: true });
		runOnJS(barCodesHandler)(detectedBarcodes, frame);
	}, []);



	return (
		<View style={{ flex: 1 }}>
			{device != null && (
				<>
					<Camera
						style={StyleSheet.absoluteFill}
						device={device}
						isActive={true}
						frameProcessor={frameProcessor}
						frameProcessorFps={5}
					/>

					{/* Зеленая рамка для прицеливания */}
					<FrameForAiming getAimingBorderPos={(b) => (AimingBorderSize.current = b)} />

					{/* Подсвечу все что распозналось */}
					{!!qrBorders?.length && (
						qrBorders.map((b, k) => (<View key={k} style={b.style} />))
					)}
				</>
			)}
		</View>
	);
}






export function FrameForAiming({
	getAimingBorderPos = () => { },
}) {
	const [H, setH] = React.useState(0);
	const maxLayouts = 5;
	const curLayouts = useRef(0);



	useEffect(() => {
		rotateListener = Dimensions.addEventListener('change', () => {
			curLayouts.current = 0;
			setH(0);
		});

		return () => {
			rotateListener?.remove();
		};
	});


	return (
		<View style={ST_.ScreenWrap}>
			<View
				style={[ST_.wrap, { height: H }]}
				onLayout={(e) => {
					if (curLayouts.current < maxLayouts) {
						const l = e.nativeEvent.layout;
						l.height = l.width;

						setH(parseInt(l.width, 10));

						getAimingBorderPos({
							xl: parseInt(l.x, 10),
							xr: parseInt(l.x + l.width, 10),
							yt: parseInt(l.y, 10),
							yb: parseInt(l.y + l.height, 10),
						});

						curLayouts.current++;
					}
				}}
			>
				<View style={ST_.tlb} />
				<View style={ST_.trb} />
				<View style={ST_.blb} />
				<View style={ST_.brb} />
			</View>
		</View>
	);
}


const BorderWidth = 1;
const BorderStyle = {
	position: 'absolute',
	width: '35%',
	height: '35%',
	borderColor: '#00FF00',
};
const ST_ = {
	ScreenWrap: {
		justifyContent: 'center',
		alignItems: 'center',
		flex: 1,
	},
	wrap: {
		width: '45%',
	},
	tlb: {
		top: 0,
		left: 0,
		borderTopWidth: BorderWidth,
		borderLeftWidth: BorderWidth,
		...BorderStyle,
	},
	trb: {
		top: 0,
		right: 0,
		borderTopWidth: BorderWidth,
		borderRightWidth: BorderWidth,
		...BorderStyle,
	},
	blb: {
		bottom: 0,
		left: 0,
		borderBottomWidth: BorderWidth,
		borderLeftWidth: BorderWidth,
		...BorderStyle,
	},
	brb: {
		bottom: 0,
		right: 0,
		borderBottomWidth: BorderWidth,
		borderRightWidth: BorderWidth,
		...BorderStyle,
	},
};

valery-lavrik avatar Feb 24 '23 14:02 valery-lavrik

I took inspiration from @valery-lavrik code and came up with the following solution.

index.tsx

import { Text, TouchableRipple, useTheme } from "react-native-paper"
import { AppTheme } from "../../theme"
import createStyles from "./styles"
import { NativeStackScreenProps } from "@react-navigation/native-stack"
import { RootStackParamList } from "../../navigation/RootStackParamList"
import { RouteName } from "../../navigation/RouteName"
import { Camera, useCameraDevices, useFrameProcessor } from "react-native-vision-camera"
import { Dimensions, Platform, StyleSheet, View } from "react-native"
import { useEffect, useState } from "react"
import Icon from "react-native-vector-icons/MaterialIcons";
import { Barcode, BarcodeFormat, scanBarcodes } from "vision-camera-code-scanner"
import Log from "../../utils/log"
import { RNHoleView } from 'react-native-hole-view';
import { heightPercentageToDP, widthPercentageToDP } from 'react-native-responsive-screen'
import { SafeAreaView } from 'react-native-safe-area-context'
import { runOnJS } from 'react-native-reanimated'




type Props = NativeStackScreenProps<RootStackParamList, RouteName.CameraScanner>


const CameraScanner = ({ navigation, route }: Props) => {


    const TAG = CameraScanner.name

    const theme = useTheme<AppTheme>()
    const styles = createStyles(theme)

    const [hasPermission, setHasPermission] = useState(false);
    const [barcodes, setBarcodes] = useState<Barcode[]>([])
    const [isScanned, setIsScanned] = useState<boolean>(false);

    const devices = useCameraDevices()
    const device = devices.back

    const vf_x = widthPercentageToDP('8.5%')
    const vf_y = heightPercentageToDP('20%')
    const vf_width = widthPercentageToDP('83%')
    const vf_height = heightPercentageToDP('25%')

    const win = Dimensions.get('window');



    useEffect(() => {

        requestPermissions()

    }, [])


    useEffect(() => {

        toggleActiveState()

        return () => {
            barcodes;
        }

    }, [barcodes])


    const toggleActiveState = async () => {

        if (barcodes && barcodes.length > 0 && isScanned === false) {

            setIsScanned(true)

            if (barcodes[0].rawValue !== '') {

                setBarcodes([barcodes[0]]);
                Log(TAG, 'scanned barcode = ' + barcodes[0].rawValue)

            }

        }

    }


    const frameProcessor = useFrameProcessor(frame => {
        "worklet"
        const detectedBarcodes = scanBarcodes(frame, [BarcodeFormat.ALL_FORMATS], { checkInverted: true })

        if (detectedBarcodes?.length) {

            // console.log('frameProcessor Barcodes :' + JSON.stringify(detectedBarcodes))

            var handleBarcode = function () {

                // Looked into sample code from https://github.com/rodgomesc/vision-camera-code-scanner/issues/125

                for (let barcode of detectedBarcodes) {

                    const isPortal = win.height > win.width;
                    const rH = win.height / (isPortal ? Math.max(frame.height, frame.width) : Math.min(frame.height, frame.width));
                    const rW = win.width / (isPortal ? Math.min(frame.height, frame.width) : Math.max(frame.height, frame.width));

                    var boundingBottom = (barcode.boundingBox?.bottom ?? 0) * rH
                    var boundingLeft = (barcode.boundingBox?.left ?? 0) * rW
                    var boundingRight = (barcode.boundingBox?.right ?? 0) * rW
                    var boundingTop = (barcode.boundingBox?.top ?? 0) * rH

                    // iOS - wasn't sending bounding box so using cornerPoints instead

                    // cornerPoints
                    // The four corner points of the barcode, in clockwise order starting with the top left relative to the detected image in the view coordinate system. 
                    // These are CGPoints wrapped in NSValues. Due to the possible perspective distortions, this is not necessarily a rectangle.
                    // https://developers.google.com/ml-kit/reference/swift/mlkitbarcodescanning/api/reference/Classes/Barcode#cornerpoints

                    if (Platform.OS !== 'android') {

                        boundingLeft = (barcode.cornerPoints?.[0].x ?? 0) * rW
                        boundingRight = (barcode.cornerPoints?.[1].x ?? 0) * rW
                        boundingTop = (barcode.cornerPoints?.[0].y ?? 0) * rH
                        boundingBottom = (barcode.cornerPoints?.[2].y ?? 0) * rH

                    }

                    // console.log(
                    //     'Bounding Box Bottom = ' + boundingBottom +
                    //     ' Left = ' + boundingLeft +
                    //     ' Right = ' + boundingRight +
                    //     ' Top = ' + boundingTop
                    // )

                    const vf_bottom = vf_y + vf_height;
                    const vf_left = vf_x;
                    const vf_right = vf_x + vf_width;
                    const vf_top = vf_y;

                    // console.log(
                    //     'view finder Bottom = ' + vf_bottom +
                    //     ' Left = ' + vf_left +
                    //     ' Right = ' + vf_right +
                    //     ' Top = ' + vf_top
                    // )

                    if (
                        boundingTop > vf_top &&
                        boundingBottom < vf_bottom &&
                        boundingLeft > vf_left &&
                        boundingRight < vf_right
                    ) {
                        // console.log('Barcode is inside View Finder')
                        runOnJS(setBarcodes)([barcode])
                        break
                    }

                }

            }

            handleBarcode()

        }

    }, [])


    const requestPermissions = async () => {
        const status = await Camera.requestCameraPermission()
        setHasPermission(status === "authorized")
    }


    return (

        <View
            style={styles.container}
        >

            {
                device !== undefined &&
                hasPermission && frameProcessor !== undefined && (
                    <>
                        <Camera
                            style={StyleSheet.absoluteFill}
                            device={device}
                            audio={false}
                            isActive={!isScanned}
                            frameProcessor={frameProcessor}
                            frameProcessorFps={5}
                        />
                        <RNHoleView
                            holes={[
                                {
                                    x: vf_x,
                                    y: vf_y,
                                    width: vf_width,
                                    height: vf_height,
                                    borderRadius: 10,
                                },
                            ]}
                            style={styles.rnholeView}
                        />
                        <View
                            style={{
                                height: 2,
                                width: widthPercentageToDP('83%'),
                                marginStart: widthPercentageToDP('8.5%'),
                                marginTop: heightPercentageToDP('32.5%'),
                                backgroundColor: 'red'
                            }}
                        />

                    </>
                )
            }

            <SafeAreaView
                style={styles.safeAreaView}
            >

                <TouchableRipple
                    style={styles.backButton}
                    onPress={
                        () => {
                            navigation.goBack()
                        }
                    }
                    rippleColor={theme.colors.elevation.level0}
                    borderless={true}
                >
                    <Icon
                        name="arrow-back-ios"
                        size={30}
                        color={theme.colors.onPrimary}
                        style={{ start: 10 }}
                    />
                </TouchableRipple>

                {
                    barcodes.length ?

                        <View
                            style={styles.containerView}
                        >

                            <View
                                style={styles.barcodeDetectedView}
                            >
                                <Text
                                    variant="titleMedium"
                                    style={styles.barcodeTitle}
                                >
                                    Barcode Detected
                                </Text>
                                <Text
                                    style={styles.barcodeTextURL}
                                    variant="titleLarge"
                                >
                                    {barcodes[0].displayValue}
                                </Text>
                            </View>

                        </View>

                        :

                        <></>

                }

            </SafeAreaView>

        </View>

    )

}


export default CameraScanner

styles.ts

import { StyleSheet } from "react-native";
import { MD3Theme } from "react-native-paper";


const createStyles = (theme: MD3Theme) => StyleSheet.create({
    container: {
        backgroundColor: 'black',
        color: 'black',
        flex: 1,
    },
    backButton: {
        borderRadius: 50,
        height: 40,
        width: 40,
        marginEnd: 10,
        left: 20,
        top: 15,
    },
    rnholeView: {
        position: 'absolute',
        width: '100%',
        height: '100%',
        alignSelf: 'center',
        alignItems: 'center',
        justifyContent: 'center',
        backgroundColor: 'rgba(0,0,0,0.5)',
    },
    safeAreaView: {
        position: 'absolute',
        left: 0,
        top: 0,
        right: 0,
        bottom: 0,
    },
    containerView: {
        flex: 1,
    },
    barcodeDetectedView: {
        flex: 1,
        justifyContent: 'flex-end',
        paddingBottom: 40,
    },
    barcodeTitle: {
        alignSelf: 'center',
        color: 'white',
    },
    barcodeTextURL: {
        fontSize: 20,
        color: 'white',
        alignSelf: 'center',
    },
})


export default createStyles

Ar-Shak avatar Jun 23 '23 19:06 Ar-Shak

I took inspiration from @valery-lavrik code and came up with the following solution.

index.tsx

import { Text, TouchableRipple, useTheme } from "react-native-paper"
import { AppTheme } from "../../theme"
import createStyles from "./styles"
import { NativeStackScreenProps } from "@react-navigation/native-stack"
import { RootStackParamList } from "../../navigation/RootStackParamList"
import { RouteName } from "../../navigation/RouteName"
import { Camera, useCameraDevices, useFrameProcessor } from "react-native-vision-camera"
import { Dimensions, Platform, StyleSheet, View } from "react-native"
import { useEffect, useState } from "react"
import Icon from "react-native-vector-icons/MaterialIcons";
import { Barcode, BarcodeFormat, scanBarcodes } from "vision-camera-code-scanner"
import Log from "../../utils/log"
import { RNHoleView } from 'react-native-hole-view';
import { heightPercentageToDP, widthPercentageToDP } from 'react-native-responsive-screen'
import { SafeAreaView } from 'react-native-safe-area-context'
import { runOnJS } from 'react-native-reanimated'




type Props = NativeStackScreenProps<RootStackParamList, RouteName.CameraScanner>


const CameraScanner = ({ navigation, route }: Props) => {


    const TAG = CameraScanner.name

    const theme = useTheme<AppTheme>()
    const styles = createStyles(theme)

    const [hasPermission, setHasPermission] = useState(false);
    const [barcodes, setBarcodes] = useState<Barcode[]>([])
    const [isScanned, setIsScanned] = useState<boolean>(false);

    const devices = useCameraDevices()
    const device = devices.back

    const vf_x = widthPercentageToDP('8.5%')
    const vf_y = heightPercentageToDP('20%')
    const vf_width = widthPercentageToDP('83%')
    const vf_height = heightPercentageToDP('25%')

    const win = Dimensions.get('window');



    useEffect(() => {

        requestPermissions()

    }, [])


    useEffect(() => {

        toggleActiveState()

        return () => {
            barcodes;
        }

    }, [barcodes])


    const toggleActiveState = async () => {

        if (barcodes && barcodes.length > 0 && isScanned === false) {

            setIsScanned(true)

            if (barcodes[0].rawValue !== '') {

                setBarcodes([barcodes[0]]);
                Log(TAG, 'scanned barcode = ' + barcodes[0].rawValue)

            }

        }

    }


    const frameProcessor = useFrameProcessor(frame => {
        "worklet"
        const detectedBarcodes = scanBarcodes(frame, [BarcodeFormat.ALL_FORMATS], { checkInverted: true })

        if (detectedBarcodes?.length) {

            // console.log('frameProcessor Barcodes :' + JSON.stringify(detectedBarcodes))

            var handleBarcode = function () {

                // Looked into sample code from https://github.com/rodgomesc/vision-camera-code-scanner/issues/125

                for (let barcode of detectedBarcodes) {

                    const isPortal = win.height > win.width;
                    const rH = win.height / (isPortal ? Math.max(frame.height, frame.width) : Math.min(frame.height, frame.width));
                    const rW = win.width / (isPortal ? Math.min(frame.height, frame.width) : Math.max(frame.height, frame.width));

                    var boundingBottom = (barcode.boundingBox?.bottom ?? 0) * rH
                    var boundingLeft = (barcode.boundingBox?.left ?? 0) * rW
                    var boundingRight = (barcode.boundingBox?.right ?? 0) * rW
                    var boundingTop = (barcode.boundingBox?.top ?? 0) * rH

                    // iOS - wasn't sending bounding box so using cornerPoints instead

                    // cornerPoints
                    // The four corner points of the barcode, in clockwise order starting with the top left relative to the detected image in the view coordinate system. 
                    // These are CGPoints wrapped in NSValues. Due to the possible perspective distortions, this is not necessarily a rectangle.
                    // https://developers.google.com/ml-kit/reference/swift/mlkitbarcodescanning/api/reference/Classes/Barcode#cornerpoints

                    if (Platform.OS !== 'android') {

                        boundingLeft = (barcode.cornerPoints?.[0].x ?? 0) * rW
                        boundingRight = (barcode.cornerPoints?.[1].x ?? 0) * rW
                        boundingTop = (barcode.cornerPoints?.[0].y ?? 0) * rH
                        boundingBottom = (barcode.cornerPoints?.[2].y ?? 0) * rH

                    }

                    // console.log(
                    //     'Bounding Box Bottom = ' + boundingBottom +
                    //     ' Left = ' + boundingLeft +
                    //     ' Right = ' + boundingRight +
                    //     ' Top = ' + boundingTop
                    // )

                    const vf_bottom = vf_y + vf_height;
                    const vf_left = vf_x;
                    const vf_right = vf_x + vf_width;
                    const vf_top = vf_y;

                    // console.log(
                    //     'view finder Bottom = ' + vf_bottom +
                    //     ' Left = ' + vf_left +
                    //     ' Right = ' + vf_right +
                    //     ' Top = ' + vf_top
                    // )

                    if (
                        boundingTop > vf_top &&
                        boundingBottom < vf_bottom &&
                        boundingLeft > vf_left &&
                        boundingRight < vf_right
                    ) {
                        // console.log('Barcode is inside View Finder')
                        runOnJS(setBarcodes)([barcode])
                        break
                    }

                }

            }

            handleBarcode()

        }

    }, [])


    const requestPermissions = async () => {
        const status = await Camera.requestCameraPermission()
        setHasPermission(status === "authorized")
    }


    return (

        <View
            style={styles.container}
        >

            {
                device !== undefined &&
                hasPermission && frameProcessor !== undefined && (
                    <>
                        <Camera
                            style={StyleSheet.absoluteFill}
                            device={device}
                            audio={false}
                            isActive={!isScanned}
                            frameProcessor={frameProcessor}
                            frameProcessorFps={5}
                        />
                        <RNHoleView
                            holes={[
                                {
                                    x: vf_x,
                                    y: vf_y,
                                    width: vf_width,
                                    height: vf_height,
                                    borderRadius: 10,
                                },
                            ]}
                            style={styles.rnholeView}
                        />
                        <View
                            style={{
                                height: 2,
                                width: widthPercentageToDP('83%'),
                                marginStart: widthPercentageToDP('8.5%'),
                                marginTop: heightPercentageToDP('32.5%'),
                                backgroundColor: 'red'
                            }}
                        />

                    </>
                )
            }

            <SafeAreaView
                style={styles.safeAreaView}
            >

                <TouchableRipple
                    style={styles.backButton}
                    onPress={
                        () => {
                            navigation.goBack()
                        }
                    }
                    rippleColor={theme.colors.elevation.level0}
                    borderless={true}
                >
                    <Icon
                        name="arrow-back-ios"
                        size={30}
                        color={theme.colors.onPrimary}
                        style={{ start: 10 }}
                    />
                </TouchableRipple>

                {
                    barcodes.length ?

                        <View
                            style={styles.containerView}
                        >

                            <View
                                style={styles.barcodeDetectedView}
                            >
                                <Text
                                    variant="titleMedium"
                                    style={styles.barcodeTitle}
                                >
                                    Barcode Detected
                                </Text>
                                <Text
                                    style={styles.barcodeTextURL}
                                    variant="titleLarge"
                                >
                                    {barcodes[0].displayValue}
                                </Text>
                            </View>

                        </View>

                        :

                        <></>

                }

            </SafeAreaView>

        </View>

    )

}


export default CameraScanner

styles.ts

import { StyleSheet } from "react-native";
import { MD3Theme } from "react-native-paper";


const createStyles = (theme: MD3Theme) => StyleSheet.create({
    container: {
        backgroundColor: 'black',
        color: 'black',
        flex: 1,
    },
    backButton: {
        borderRadius: 50,
        height: 40,
        width: 40,
        marginEnd: 10,
        left: 20,
        top: 15,
    },
    rnholeView: {
        position: 'absolute',
        width: '100%',
        height: '100%',
        alignSelf: 'center',
        alignItems: 'center',
        justifyContent: 'center',
        backgroundColor: 'rgba(0,0,0,0.5)',
    },
    safeAreaView: {
        position: 'absolute',
        left: 0,
        top: 0,
        right: 0,
        bottom: 0,
    },
    containerView: {
        flex: 1,
    },
    barcodeDetectedView: {
        flex: 1,
        justifyContent: 'flex-end',
        paddingBottom: 40,
    },
    barcodeTitle: {
        alignSelf: 'center',
        color: 'white',
    },
    barcodeTextURL: {
        fontSize: 20,
        color: 'white',
        alignSelf: 'center',
    },
})


export default createStyles

In fact, with this implementation, the scanning range is still the entire camera area. When scanning multiple codes, it is still difficult to recognize the desired code.

chaos2171053 avatar Jun 25 '23 08:06 chaos2171053

I'm currently working on this. I have a working iOS version here.

scanFrame-3.mov

const frameProcessor = useFrameProcessor((frame) => {
    'worklet';
    const squareSize = frame.width * MARKER_WIDTH_RATIO - 100;

    const scanFrame: ScanFrame = {
      width: squareSize,
      height: squareSize,
      x: (frame.width - squareSize) / 2,
      y: (frame.height - squareSize) / 2,
    };
    const detectedBarcodes = scanBarcodes(frame, [BarcodeFormat.QR_CODE], {
      checkInverted: true,
      scanFrame: scanFrame,
    });
    runOnJS(setBarcodes)(detectedBarcodes);
  }, []);

@rodgomesc is that a feature you would like to add to the the library ? If so I can start working on a PR.

Have you implemented this functionality on the Android version?

liangchaoqin avatar Jun 25 '23 09:06 liangchaoqin

> here

this is best solution I have found, did you successful to implement on Android side?

fukemy avatar Jul 26 '23 06:07 fukemy

I found other solution work : https://www.dynamsoft.com/codepool/react-native-qr-code-scanner-vision-camera.html

fukemy avatar Jul 26 '23 07:07 fukemy

I wish it was for android...

here's snippet for android. https://github.com/rodgomesc/vision-camera-code-scanner/commit/f4430376396f1bbaead5dc7a1b352c45abf02864

azuddin avatar Aug 28 '23 16:08 azuddin

Here is a working overlay that I made from scratch using 4 customizable square corners wrapped by a bigger square container. Makes it easier to make a full rectangle or just use rounded corner like most QR readers use to "aim". To make it without gaps, just change size of squares to be half of the overlay and customize borderRadius to taste:

const OVERLAY_HEIGHT = 250;
const OVERLAY_WIDTH = 250;
const SQUARE_HEIGHT = 50;
const SQUARE_WIDTH = 50;
const SQUARE_BORDER_WIDTH = 3;

const styles = StyleSheet.create({
    container: {
        alignItems: 'center',
        flex: 1,
        justifyContent: 'center',
    },
    iconButton: {
        position: 'absolute',
        right: 10,
        top: 10,
    },
    overlayContainer: {
        flexDirection: 'column',
        height: OVERLAY_HEIGHT,
        width: OVERLAY_WIDTH,
    },
    rowContainer: {
        flexDirection: 'row',
        flex: 1,
        justifyContent: 'space-between',
    },
});

const SquareComponent = ({ corner }) => {
    const { colors } = useTheme();
    const borderRadius = 20;
    let borderStyle = {};

    switch (corner) {
        case 'UpperLeft':
            borderStyle = {
                borderTopWidth: SQUARE_BORDER_WIDTH,
                borderLeftWidth: SQUARE_BORDER_WIDTH,
                borderColor: colors.white,
                borderTopLeftRadius: borderRadius,
                alignSelf: 'flex-start',
            };
            break;
        case 'UpperRight':
            borderStyle = {
                borderTopWidth: SQUARE_BORDER_WIDTH,
                borderRightWidth: SQUARE_BORDER_WIDTH,
                borderColor: colors.white,
                borderTopRightRadius: borderRadius,
                alignSelf: 'flex-start',
            };
            break;
        case 'LowerRight':
            borderStyle = {
                borderBottomWidth: SQUARE_BORDER_WIDTH,
                borderRightWidth: SQUARE_BORDER_WIDTH,
                borderColor: colors.white,
                borderBottomRightRadius: borderRadius,
                alignSelf: 'flex-end',
            };
            break;
        case 'LowerLeft':
            borderStyle = {
                borderBottomWidth: SQUARE_BORDER_WIDTH,
                borderLeftWidth: SQUARE_BORDER_WIDTH,
                borderColor: colors.white,
                borderBottomLeftRadius: borderRadius,
                alignSelf: 'flex-end',
            };
            break;
        default:
            break;
    }

    return (
        <View
            style={{
                width: SQUARE_WIDTH,
                height: SQUARE_HEIGHT,
                backgroundColor: colors.transparent,
                ...borderStyle,
            }}
        />
    );
};

const AimpointOverlay = () => {
    return (
        <View style={styles.overlayContainer}>
            <View style={styles.rowContainer}>
                <SquareComponent corner="UpperLeft" />
                <SquareComponent corner="UpperRight" />
            </View>
            <View style={styles.rowContainer}>
                <SquareComponent corner="LowerLeft" />
                <SquareComponent corner="LowerRight" />
            </View>
        </View>
    );
};

<View style={styles.container}>
    <Camera
        style={StyleSheet.absoluteFill}
        device={device}
         isActive
         frameProcessor={frameProcessor}
         frameProcessorFps={1}
     />
     <AimpointOverlay />
</View>

Hope this helps some React Native users that needs this for both platforms, using simple styling.

eisodev avatar Aug 30 '23 08:08 eisodev