vision-camera-mrz-scanner
Version:
VisionCamera Frame Processor Plugin to detect and read MRZ data from passports using MLKit Text Recognition.
255 lines (241 loc) • 10.8 kB
JavaScript
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { Button, PixelRatio, StyleSheet, Text, TouchableOpacity, useWindowDimensions, View } from 'react-native';
import { runOnJS } from 'react-native-reanimated';
import { Camera, useCameraDevices, useFrameProcessor } from 'react-native-vision-camera';
import { boundingBoxAdjustToView, scanMRZ, sortFormatsByResolution } from 'vision-camera-mrz-scanner';
const MRZCamera = _ref => {
let {
enableBoundingBox,
boundingBoxStyle,
boundingBoxHorizontalPadding,
boundingBoxVerticalPadding,
style,
skipButtonEnabled: photoSkipButtonEnabled,
skipButton: photoSkipButton,
onSkipPressed: photoSkipOnPress,
skipButtonStyle: photoSkipButtonStyle,
cameraProps,
onData,
scanSuccess,
skipButtonText,
cameraDirection,
isActiveCamera
} = _ref;
//*****************************************************************************************
// setting up the state
//*****************************************************************************************
// Permissions
const [hasPermission, setHasPermission] = React.useState(false);
// camera states
const devices = useCameraDevices();
const direction = cameraDirection ?? 'back';
const device = devices[direction];
const camera = useRef(null);
const {
height: screenHeight,
width: screenWidth
} = useWindowDimensions();
const [isActive, setIsActive] = useState(true);
const [feedbackText, setFeedbackText] = useState('');
const [ocrElements, setOcrElements] = useState([]);
const [frameDimensions, setFrameDimensions] = useState();
const landscapeMode = screenWidth > screenHeight;
const [pixelRatio, setPixelRatio] = React.useState(1);
//*****************************************************************************************
// Comp Logic
//*****************************************************************************************
// const xRatio = frame.width / WINDOW_WIDTH;
// const yRatio = frame.height / WINDOW_HEIGHT;
/* A cleanup function that is called when the component is unmounted. */
useEffect(() => {
return () => {
setIsActive(false);
};
}, []);
// which format should we use
const formats = useMemo(() => device === null || device === void 0 ? void 0 : device.formats.sort(sortFormatsByResolution), [device === null || device === void 0 ? void 0 : device.formats]);
//figure our what happens if it is undefined?
const [format, setFormat] = useState(formats && formats.length > 0 ? formats[0] : undefined);
/**
* Prevents sending copious amounts of scans
*/
const handleScan = useCallback((data, frame) => {
const isRotated = !landscapeMode;
setFrameDimensions(isRotated ? {
width: frame.height,
height: frame.width
} : {
width: frame.width,
height: frame.height
});
if (data && data.result && data.result.blocks && data.result.blocks.length === 0) {
setFeedbackText('');
}
/* Scanning the text from the image and then setting the state of the component. */
if (data && data.result && data.result.blocks && data.result.blocks.length > 0) {
let updatedOCRElements = [];
data.result.blocks.forEach(block => {
if (block.frame.width / screenWidth < 0.8) {
setFeedbackText('Hold Still');
} else {
setFeedbackText('Scanning...');
}
updatedOCRElements.push({
...block.frame
});
});
let lines = [];
data.result.blocks.forEach(block => {
lines.push(block.text);
});
if (lines.length > 0 && isActive && onData) {
setOcrElements(updatedOCRElements);
onData(lines);
} else {
setOcrElements([]);
}
}
}, [isActive, landscapeMode, onData, screenWidth]);
/* Setting the format to the first format in the formats array. */
useEffect(() => {
setFormat(formats && formats.length > 0 ? formats[0] : undefined);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [device]);
/* Using the useFrameProcessor hook to process the video frames. */
const frameProcessor = useFrameProcessor(frame => {
'worklet';
if (!scanSuccess) {
const ocrData = scanMRZ(frame);
runOnJS(handleScan)(ocrData, frame);
}
}, [handleScan]);
useEffect(() => {
(async () => {
const status = await Camera.requestCameraPermission();
setHasPermission(status === 'authorized');
})();
}, []);
/* Using the useMemo hook to create a style object. */
const boundingStyle = useMemo(() => ({
position: 'absolute',
top: 0,
left: 0,
width: screenWidth,
height: screenHeight
}), [screenWidth, screenHeight]);
const bounds = ocrElements[ocrElements.length - 1];
//*****************************************************************************************
// stylesheet
//*****************************************************************************************
const styles = StyleSheet.create({
fixToText: {
flexDirection: 'row',
justifyContent: 'space-between'
},
skipButtonContainer: {
position: 'absolute',
bottom: screenHeight * 0.05,
width: screenWidth,
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'row'
},
feedbackContainer: {
position: 'absolute',
top: screenHeight * 0.3,
width: screenWidth,
alignItems: 'center'
},
feedbackText: {
backgroundColor: 'white',
color: 'black',
fontSize: 18,
paddingRight: 8,
paddingLeft: 8,
textAlign: 'center'
},
boundingBox: {
borderRadius: 5,
borderWidth: 3,
borderColor: 'yellow',
position: 'absolute',
left: bounds ? bounds.x * pixelRatio : 0,
top: bounds ? bounds.y * pixelRatio : 0,
height: bounds ? bounds.height : 35,
width: bounds ? bounds.width : 35
}
});
//*****************************************************************************************
// Components
//*****************************************************************************************
return /*#__PURE__*/React.createElement(View, {
style: style
}, device && hasPermission ? /*#__PURE__*/React.createElement(Camera, {
style: (cameraProps === null || cameraProps === void 0 ? void 0 : cameraProps.style) ?? StyleSheet.absoluteFill,
device: (cameraProps === null || cameraProps === void 0 ? void 0 : cameraProps.device) ?? device,
torch: cameraProps === null || cameraProps === void 0 ? void 0 : cameraProps.torch,
isActive: isActiveCamera ? isActiveCamera : cameraProps !== null && cameraProps !== void 0 && cameraProps.isActive ? cameraProps === null || cameraProps === void 0 ? void 0 : cameraProps.isActive : isActive,
ref: camera,
photo: cameraProps === null || cameraProps === void 0 ? void 0 : cameraProps.photo,
video: cameraProps === null || cameraProps === void 0 ? void 0 : cameraProps.video,
audio: cameraProps === null || cameraProps === void 0 ? void 0 : cameraProps.audio,
zoom: cameraProps === null || cameraProps === void 0 ? void 0 : cameraProps.zoom,
enableZoomGesture: cameraProps === null || cameraProps === void 0 ? void 0 : cameraProps.enableZoomGesture,
preset: cameraProps === null || cameraProps === void 0 ? void 0 : cameraProps.preset,
format: (cameraProps === null || cameraProps === void 0 ? void 0 : cameraProps.format) ?? format,
fps: (cameraProps === null || cameraProps === void 0 ? void 0 : cameraProps.fps) ?? 10,
hdr: cameraProps === null || cameraProps === void 0 ? void 0 : cameraProps.hdr,
lowLightBoost: cameraProps === null || cameraProps === void 0 ? void 0 : cameraProps.lowLightBoost,
colorSpace: cameraProps === null || cameraProps === void 0 ? void 0 : cameraProps.colorSpace,
videoStabilizationMode: cameraProps === null || cameraProps === void 0 ? void 0 : cameraProps.videoStabilizationMode,
enableDepthData: cameraProps === null || cameraProps === void 0 ? void 0 : cameraProps.enableDepthData,
enablePortraitEffectsMatteDelivery: cameraProps === null || cameraProps === void 0 ? void 0 : cameraProps.enablePortraitEffectsMatteDelivery,
enableHighQualityPhotos: cameraProps === null || cameraProps === void 0 ? void 0 : cameraProps.enableHighQualityPhotos,
onError: cameraProps === null || cameraProps === void 0 ? void 0 : cameraProps.onError,
onInitialized: cameraProps === null || cameraProps === void 0 ? void 0 : cameraProps.onInitialized,
onFrameProcessorPerformanceSuggestionAvailable: cameraProps === null || cameraProps === void 0 ? void 0 : cameraProps.onFrameProcessorPerformanceSuggestionAvailable,
frameProcessor: (cameraProps === null || cameraProps === void 0 ? void 0 : cameraProps.frameProcessor) ?? frameProcessor,
frameProcessorFps: (cameraProps === null || cameraProps === void 0 ? void 0 : cameraProps.frameProcessorFps) ?? 30,
onLayout: event => {
setPixelRatio(event.nativeEvent.layout.width / PixelRatio.getPixelSizeForLayoutSize(event.nativeEvent.layout.width));
}
}) : undefined, enableBoundingBox && ocrElements.length > 0 ? /*#__PURE__*/React.createElement(View, {
style: boundingStyle,
testID: "faceDetectionBoxView"
}, frameDimensions && (() => {
const {
adjustRect
} = boundingBoxAdjustToView(frameDimensions, {
width: landscapeMode ? screenHeight : screenWidth,
height: landscapeMode ? screenWidth : screenHeight
}, landscapeMode, boundingBoxVerticalPadding, boundingBoxHorizontalPadding);
return ocrElements ? ocrElements.map((i, index) => {
const {
left,
...others
} = adjustRect(i);
return /*#__PURE__*/React.createElement(View, {
key: index,
style: [styles.boundingBox, {
...others,
left: left
}, boundingBoxStyle]
});
}) : undefined;
})()) : null, photoSkipButton ? /*#__PURE__*/React.createElement(View, {
style: [styles.fixToText]
}, photoSkipButtonEnabled ? photoSkipButton ? /*#__PURE__*/React.createElement(TouchableOpacity, {
onPress: photoSkipOnPress
}, photoSkipButton) : /*#__PURE__*/React.createElement(View, {
style: [styles.skipButtonContainer, photoSkipButtonStyle]
}, /*#__PURE__*/React.createElement(Button, {
title: skipButtonText ? skipButtonText : 'Skip',
onPress: photoSkipOnPress
})) : undefined) : undefined, feedbackText ? /*#__PURE__*/React.createElement(View, {
style: styles.feedbackContainer
}, /*#__PURE__*/React.createElement(Text, {
style: styles.feedbackText
}, feedbackText)) : null);
};
export default MRZCamera;
//# sourceMappingURL=MRZCamera.js.map