UNPKG

react-native-abbyy-mobile-capture-sample-core-api

Version:

ABBYY Mobile Capture React Native Module allows to use the Image Capture feature of ABBYY Mobile Capture in apps based on the [React Native](https://reactnative.dev/) framework.

836 lines (796 loc) 22.8 kB
/// ABBYY® Mobile Capture © 2019 ABBYY Production LLC. /// ABBYY is a registered trademark or a trademark of ABBYY Software Ltd. import React, {useState} from 'react'; import { View, Text, Alert, TouchableOpacity, StyleSheet, ScrollView, Modal, } from 'react-native'; import { startImageCapture, rotateImage, assessQualityForOcr, detectDocumentBoundary, cropImage, recognizeText, extractData, exportImagesToPdf, exportImage, } from 'react-native-abbyy-mobile-capture'; import SettingsView, {settings} from './SettingsView'; import Svg, { G, Text as SvgText, Polygon, Rect, Image as SvgImage, } from 'react-native-svg'; import {DocumentDirectoryPath, mkdir, unlink} from 'react-native-fs'; import Share from 'react-native-share'; // Initialize result directory const resultDirectory = DocumentDirectoryPath + '/sample-core-api'; // Delete directory recursively unlink(resultDirectory) .finally(() => { // Create directory mkdir(resultDirectory).then(success => { console.log('Result directory was initialized successfully'); }); }) .catch(error => { // Error is okay in case of file not found }); //-- Actions const selectImage = async completion => { try { const request = { method: 'startImageCapture', settings: { requiredPageCount: 1, destination: settings.destination, exportType: settings.exportType, compressionLevel: settings.compressionLevel, }, }; const result = await startImageCapture(request.settings); if (result.images) { let imageInfo = result.images[0]; const imageUri = result.resultInfo.uriPrefix + (imageInfo.filePath ? imageInfo.filePath : imageInfo.base64); completion({ imageUri: imageUri, imageSize: imageInfo.resultInfo.imageSize, rawResult: result, rawRequest: request, }); } } catch (error) { Alert.alert('Select Error', error.message); } }; const getResultSettings = fileName => { return { destination: settings.destination, exportType: settings.exportType, filePath: resultDirectory + '/' + fileName + Date.now() + // Add timestamp to skip image caching '.' + settings.exportType.toLowerCase(), compressionLevel: settings.compressionLevel, }; }; const rotate = async (imageUri, completion) => { try { const request = { method: 'rotateImage', settings: { imageUri: imageUri, angle: settings.angle, result: getResultSettings('rotated_image'), }, }; const result = await rotateImage(request.settings); completion(request, result); } catch (error) { Alert.alert('Rotate Error', error.message); } }; const assessQuality = async (imageUri, completion) => { try { const request = { method: 'assessQualityForOcr', settings: {imageUri: imageUri}, }; const result = await assessQualityForOcr(request.settings); completion(request, result); } catch (error) { Alert.alert('AssessQualityForOcr Error', error.message); } }; const crop = async (imageUri, documentBoundary, completion) => { try { if (!documentBoundary) { const boundaryResult = await detectDocumentBoundary({ imageUri: imageUri, detectionMode: settings.detectionMode, }); documentBoundary = boundaryResult.documentBoundary; } if (documentBoundary) { const cropRequest = { method: 'cropResult', settings: { imageUri: imageUri, documentBoundary: documentBoundary, result: getResultSettings('cropped_image'), }, }; const cropResult = await cropImage(cropRequest.settings); completion(cropRequest, cropResult); } else { Alert.alert('Document boundary not found'); } } catch (error) { Alert.alert('Crop Error', error.message); } }; const shareToPdf = async (imageUri, completion) => { try { const request = { method: 'exportImagesToPdf', settings: { images: [ { imageUri: imageUri, compressionLevel: settings.compressionLevel, }, ], result: { destination: settings.destination, filePath: resultDirectory + '/file.pdf', }, pdfInfo: { title: settings.pdfInfoTitle, }, }, }; const result = await exportImagesToPdf(request.settings); await Share.open({ title: 'Share pdf', url: result.pdfUri, showAppsToView: true, failOnCancel: false, }); completion(request, result); } catch (error) { Alert.alert('To PDF Error', error.message); } }; const recognize = async (imageUri, size, completion) => { try { const request = { method: 'recognizeText', settings: { imageUri: imageUri, recognitionLanguages: settings.languages, isTextOrientationDetectionEnabled: settings.isTextOrientationDetectionEnabled, }, }; let result = await recognizeText(request.settings); let text = result.text; let textLines = []; let blocks = result.textBlocks ? result.textBlocks : []; for (let block of blocks) { let lines = block.textLines ? block.textLines : []; for (let textLine of lines) { textLines.push(textLine); } } if (result.warnings) { text += '\n\nWarnings: ' + JSON.stringify(result.warnings); } let rotatedImageUri = imageUri; let rotatedSize = size; if (result.orientation !== 0) { const rotationResult = await rotateImage({ imageUri: imageUri, angle: 360 - result.orientation, result: getResultSettings('rotated_after_recognition_image'), }); rotatedImageUri = rotationResult.imageUri; rotatedSize = rotationResult.imageSize; } completion({ text: text, lines: textLines, imageUri: rotatedImageUri, imageSize: rotatedSize, rawResult: result, rawRequest: request, }); } catch (error) { Alert.alert('Recognize Error', error.message); } }; const extract = async (imageUri, size, completion) => { try { const request = { method: '', settings: { imageUri: imageUri, profile: 'BusinessCards', recognitionLanguages: settings.languages, isTextOrientationDetectionEnabled: settings.isTextOrientationDetectionEnabled, }, }; let result = await extractData(request.settings); let fields = result.dataFields ? result.dataFields : []; let text = ''; let lines = []; for (let field of fields) { if (field.id !== 'Text') { text += field.id + ': ' + field.text + '\n'; lines.push(field); if (field.components) { for (let component of field.components) { if (component.id) { text += '\t' + component.id + ': ' + component.text + '\n'; } } } } } if (result.warnings) { text += '\n\nWarnings: ' + JSON.stringify(result.warnings); } let rotatedImageUri = imageUri; let rotatedSize = size; if (result.orientation !== 0) { const rotationResult = await rotateImage({ imageUri: imageUri, angle: 360 - result.orientation, result: getResultSettings('rotated_after_data_extraction_image'), }); rotatedImageUri = rotationResult.imageUri; rotatedSize = rotationResult.imageSize; } completion({ text: text, lines: lines, imageUri: rotatedImageUri, imageSize: rotatedSize, rawResult: result, rawRequest: request, }); } catch (error) { Alert.alert('Extract Error', error.message); } }; const shareImage = async (imageUri, completion) => { try { const request = { method: 'exportImage', settings: { imageUri: imageUri, result: getResultSettings('share_image'), }, }; const result = await exportImage(request.settings); console.log('After result'); await Share.open({ title: 'Share image', url: result.imageUri, showAppsToView: true, failOnCancel: false, }); console.log('After share'); completion(request, result); } catch (error) { Alert.alert('Share Image Error', error.message); } }; const detectBoundary = async (imageUri, completion) => { try { const request = { method: 'detectDocumentBoundary', settings: { imageUri: imageUri, detectionMode: settings.detectionMode, }, }; const result = await detectDocumentBoundary(request.settings); if (!result.documentBoundary) { Alert.alert('Document boundary not found'); } completion(request, result); } catch (error) { Alert.alert('Detect Boundary Error', error.message); } }; const truncateBase64String = dict => { if (dict.imageUri && dict.imageUri.length > 300) { return { ...dict, imageUri: dict.imageUri.substring(0, 50) + ' ... length: ' + dict.imageUri.length, }; } if (dict.base64) { return { ...dict, base64: dict.base64.substring(0, 50) + ' ... length: ' + dict.base64.length, }; } if (dict.settings) { return {...dict, settings: truncateBase64String(dict.settings)}; } if (dict.images) { let truncated = []; for (let image of dict.images) { truncated.push(truncateBase64String(image)); } return {...dict, images: truncated}; } return dict; }; //-- UI const updateImageViewLayout = (parentLayout, image) => { if (image.imageSize) { let imageSize = image.imageSize; const maxImageHeight = 320; let imageViewLayout = {}; if (imageSize.width > imageSize.height) { imageViewLayout.width = parentLayout.width ? parentLayout.width : 320; imageViewLayout.height = (imageViewLayout.width * imageSize.height) / imageSize.width; } else { imageViewLayout.height = maxImageHeight; imageViewLayout.width = (imageViewLayout.height * imageSize.width) / imageSize.height; } return imageViewLayout; } }; const pointsFromQuadrangle = (scaleX, scaleY, quadrangle) => { let points = []; for (let point of quadrangle) { let x = point.x * scaleX; let y = point.y * scaleY; points.push(x + ',' + y); } return points; }; const boundaryUI = (scaleX, scaleY, boundary) => { let points = pointsFromQuadrangle(scaleX, scaleY, boundary); return <Polygon points={points.join(' ')} stroke="blue" />; }; const recognizedTextLinesUI = (scaleX, scaleY, lines) => { var uiLines = []; let key = 0; lines.forEach(function(line) { let points = pointsFromQuadrangle(scaleX, scaleY, line.quadrangle); let topLeft = {x: line.rect.left * scaleX, y: line.rect.top * scaleY}; let fieldIdFontSize = 11; uiLines.push( <G key={key}> <Polygon points={points.join(' ')} stroke="blue" /> <SvgText x={topLeft.x} y={topLeft.y} fontWeight="bold" fontSize={fieldIdFontSize} fill="blue" textAnchor="start"> {line.id} </SvgText> </G>, ); key = key + 1; }); return <G>{uiLines}</G>; }; const assessQualityForOcrUIBlocks = (scaleX, scaleY, blocks) => { var uiBlocks = []; let i = 0; blocks.forEach(function(block) { let left = block.rect.left * scaleX; let right = block.rect.right * scaleX; let top = block.rect.top * scaleY; let bottom = block.rect.bottom * scaleY; let uiBlock = null; if (block.type === 'Text') { let red = Math.ceil(((100 - block.quality) * 255) / 100); let green = Math.ceil((block.quality * 255) / 100); let blue = 0; let color = 'rgb(' + [red, green, blue].join(',') + ')'; uiBlock = ( <Rect x={left} y={top} width={right - left} height={bottom - top} stroke={color} fill={color} opacity={0.4} key={i} /> ); } else { uiBlock = ( <Rect x={left} y={top} width={right - left} height={bottom - top} stroke="rgb(200,200,200)" opacity={0.2} key={i} /> ); } uiBlocks.push(uiBlock); i = i + 1; }); return <G>{uiBlocks}</G>; }; export default props => { const [parentLayout, setParentLayout] = useState({}); const [image, setImage] = useState({}); const [isSettingsVisible, setSettingsVisible] = useState(false); const [rawCallData, setRawCallData] = useState({ request: 'Select image and apply operation to see raw result', result: '', }); const [isRawCallDataVisible, setRawCallDataVisible] = useState(false); const isActionDisabled = image.imageUri === undefined; const actionButtonColor = isActionDisabled ? 'grey' : 'dodgerblue'; let svg = null; if (image.imageUri) { let imageViewLayout = updateImageViewLayout(parentLayout, image); const scaleX = imageViewLayout.width / image.imageSize.width; const scaleY = imageViewLayout.height / image.imageSize.height; let assessmentBlocks = null; if (image.qualityAssessmentForOcrBlocks) { assessmentBlocks = assessQualityForOcrUIBlocks( scaleX, scaleY, image.qualityAssessmentForOcrBlocks, ); } let boundary = null; if (image.documentBoundary) { boundary = boundaryUI(scaleX, scaleY, image.documentBoundary); } let textLines = null; if (image.lines) { textLines = recognizedTextLinesUI(scaleX, scaleY, image.lines); } svg = ( <Svg style={styles.image} height={imageViewLayout.height} width={imageViewLayout.width}> <SvgImage x={0} y={0} width={imageViewLayout.width} height={imageViewLayout.height} preserveAspectRatio="xMinYMin meet" href={image.imageUri} /> {assessmentBlocks} {boundary} {textLines} </Svg> ); } let settingsView = null; if (isSettingsVisible) { settingsView = ( <SettingsView onClose={() => { setSettingsVisible(false); }} /> ); } const showSettings = () => { setSettingsVisible(true); }; let rawResultView = null; if (isRawCallDataVisible) { let request = truncateBase64String(rawCallData.request); let result = truncateBase64String(rawCallData.result); const requestResultText = 'Request:\n' + JSON.stringify(request, null, 2) + '\nResult:\n' + JSON.stringify(result, null, 2); rawResultView = ( <Modal transparent={false} animationType="slide" onRequestClose={() => { setRawCallDataVisible(false); }}> <ScrollView> <Text style={styles.rawResultText} selectable={true}> {requestResultText} </Text> </ScrollView> <TouchableOpacity style={styles.closeRawResultButton} onPress={() => { setRawCallDataVisible(false); }}> <Text style={styles.mainButtonText}>CLOSE</Text> </TouchableOpacity> </Modal> ); } const showRawResult = () => { setRawCallDataVisible(true); }; return ( <View style={styles.container} onLayout={event => { setParentLayout(event.nativeEvent.layout); }}> {rawResultView} {settingsView} <ScrollView style={styles.scrollView}> {svg} <View style={styles.resultView}> <Text>{image.text}</Text> </View> </ScrollView> <View style={styles.secondaryButtonRow}> <TouchableOpacity style={styles.rawResultButton} onPress={showRawResult}> <Text style={styles.secondaryButtonText}>RAW RESULT</Text> </TouchableOpacity> <TouchableOpacity style={styles.settingsButton} onPress={showSettings}> <Text style={styles.secondaryButtonText}>SETTINGS</Text> </TouchableOpacity> </View> <View style={styles.actionButtonRow}> <TouchableOpacity disabled={isActionDisabled} style={{...styles.button, backgroundColor: actionButtonColor}} onPress={() => detectBoundary(image.imageUri, (request, result) => { setImage({...image, ...result}); setRawCallData({request: request, result: result}); }) }> <Text style={styles.buttonText}>DETECT BOUNDARY</Text> </TouchableOpacity> <TouchableOpacity disabled={isActionDisabled} style={{...styles.button, backgroundColor: actionButtonColor}} onPress={() => crop(image.imageUri, image.documentBoundary, (request, result) => { setImage(result); setRawCallData({request: request, result: result}); }) }> <Text style={styles.buttonText}>CROP</Text> </TouchableOpacity> </View> <View style={styles.actionButtonRow}> <TouchableOpacity disabled={isActionDisabled} style={{...styles.button, backgroundColor: actionButtonColor}} onPress={() => rotate(image.imageUri, (request, result) => { setImage(result); setRawCallData({request: request, result: result}); }) }> <Text style={styles.buttonText}>ROTATE</Text> </TouchableOpacity> <TouchableOpacity disabled={isActionDisabled} style={{...styles.button, backgroundColor: actionButtonColor}} onPress={() => assessQuality(image.imageUri, (request, result) => { setImage({...image, ...result}); setRawCallData({request: request, result: result}); }) }> <Text style={styles.buttonText}>OCR QUALITY</Text> </TouchableOpacity> </View> <View style={styles.actionButtonRow}> <TouchableOpacity disabled={isActionDisabled} style={{...styles.button, backgroundColor: actionButtonColor}} onPress={() => shareImage(image.imageUri, (request, result) => { setRawCallData({request: request, result: result}); }) }> <Text style={styles.buttonText}>SHARE IMAGE</Text> </TouchableOpacity> <TouchableOpacity disabled={isActionDisabled} style={{...styles.button, backgroundColor: actionButtonColor}} onPress={() => shareToPdf(image.imageUri, (request, result) => { setRawCallData({request: request, result: result}); }) }> <Text style={styles.buttonText}>SHARE PDF</Text> </TouchableOpacity> </View> <View style={styles.actionButtonRow}> <TouchableOpacity disabled={isActionDisabled} style={{...styles.button, backgroundColor: actionButtonColor}} onPress={() => recognize(image.imageUri, image.imageSize, async result => { if (result.imageUri === image.imageUri) { setImage({...image, ...result}); } else { setImage(result); } setRawCallData({ request: result.rawRequest, result: result.rawResult, }); }) }> <Text style={styles.buttonText}>RECOGNIZE TEXT</Text> </TouchableOpacity> <TouchableOpacity disabled={isActionDisabled} style={{...styles.button, backgroundColor: actionButtonColor}} onPress={() => extract(image.imageUri, image.imageSize, async result => { if (result.imageUri === image.imageUri) { setImage({...image, ...result}); } else { setImage(result); } setRawCallData({ request: result.rawRequest, result: result.rawResult, }); }) }> <Text style={styles.buttonText}>BUSINESS CARD</Text> </TouchableOpacity> </View> <View style={styles.selectButtonRow}> <TouchableOpacity style={styles.button} onPress={() => { selectImage(result => { setImage(result); setRawCallData({ result: result.rawResult, request: result.rawRequest, }); }); }}> <Text style={styles.mainButtonText}>SELECT IMAGE</Text> </TouchableOpacity> </View> </View> ); }; const styles = StyleSheet.create({ container: { flex: 1, alignItems: 'stretch', justifyContent: 'center', }, scrollView: { flex: 1, }, resultView: { flexGrow: 1, }, resultText: { fontSize: 20, }, button: { flex: 1, backgroundColor: 'dodgerblue', height: 32, borderRadius: 5, justifyContent: 'center', alignItems: 'center', marginHorizontal: 8, }, closeRawResultButton: { backgroundColor: 'dodgerblue', height: 48, borderRadius: 5, justifyContent: 'center', alignItems: 'center', marginHorizontal: 16, marginVertical: 16, }, buttonText: { fontSize: 12, color: 'white', }, secondaryButtonText: { fontSize: 12, color: 'gray', textAlign: 'right', }, settingsButton: { height: 32, width: 100, borderRadius: 5, justifyContent: 'center', alignItems: 'flex-end', marginHorizontal: 8, }, rawResultButton: { height: 32, width: 100, borderRadius: 5, justifyContent: 'center', alignItems: 'flex-start', marginHorizontal: 8, }, secondaryButtonRow: { marginVertical: 8, marginHorizontal: 8, flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', }, actionButtonRow: { marginVertical: 8, marginHorizontal: 8, flexDirection: 'row', justifyContent: 'space-around', alignItems: 'center', }, title: { flex: 1, fontSize: 20, }, rawResultText: { flex: 1, fontSize: 16, marginVertical: 16, marginHorizontal: 16, }, touchable: { fontSize: 20, padding: 10, color: 'dodgerblue', }, image: { flex: 1, alignSelf: 'center', }, mainButtonText: { fontSize: 20, color: 'white', }, selectButtonRow: { height: 60, marginVertical: 8, paddingHorizontal: 8, paddingBottom: 16, }, });