Skip to content

Instantly share code, notes, and snippets.

@Michaelvilleneuve
Created October 12, 2017 14:36
Show Gist options
  • Save Michaelvilleneuve/857469c2fb900d8b85fa718a69c5531a to your computer and use it in GitHub Desktop.
Save Michaelvilleneuve/857469c2fb900d8b85fa718a69c5531a to your computer and use it in GitHub Desktop.
Document Scanner and Cropper used together
import {
Dimensions, Image, Text, TouchableOpacity, View, LayoutAnimation } from 'react-native';
import DocumentScanner from 'react-native-document-scanner';
import Icon from 'react-native-vector-icons/Ionicons';
import React, { Component } from 'react';
import CustomCrop from 'react-native-custom-crop';
import { I18n } from '../utils';
import FloatingButton from '../shared/FloatingButton';
class AutomaticCardAdd extends Component {
constructor(props) {
super(props);
this.state = {
image: null,
torchEnabled: false,
stableCounter: 0,
lastDetectionTime: new Date().getTime(),
detectionCountBeforeCapture: 5,
indicatorVisible: false,
blackAndWhite: false,
brightness: 0,
contrast: 1,
saturation: 1,
};
}
componentDidMount() {
this.interval = setInterval(() => {
const timeDifference = new Date().getTime() - this.state.lastDetectionTime;
this.setState({
showCapture: (Math.floor(timeDifference / 1000) > 5),
indicatorVisible: (Math.floor(timeDifference / 1000) < 2),
});
}, 1000);
}
componentWillUpdate() {
LayoutAnimation.spring();
}
componentWillUnmount() {
this.setState({ torchEnabled: false });
clearInterval(this.interval);
}
toggleBlackAndWhite() {
if (!this.state.blackAndWhite) {
this.setState({ saturation: 0, contrast: 1.8, brightness: 0.4 });
} else {
this.setState({ saturation: 1, contrast: 1, brightness: 0 });
}
this.setState({ blackAndWhite: !this.state.blackAndWhite });
}
startCrop() {
this.setState({ cropping: true });
}
endCrop() {
this.setState({ cropping: false });
}
handleCapture() {
this.props.handleCapture(
`data:image/jpeg;base64,${this.state.image}`,
this.props.index,
this.state.finalImageWidth,
this.state.finalImageHeight,
);
}
showTutorialContent() {
switch (this.state.lastDetectionType) {
case 0:
return I18n.t('correctRectangle');
case 1:
return I18n.t('badAngle');
case 2:
return I18n.t('tooFar');
default:
return I18n.t('tutoCapture');
}
}
updateImage(image, coordinates) {
Image.getSize(`data:image/jpeg;base64,${image}`, (width, height) => {
this.setState({
image,
cropping: false,
rectangleCoordinates: coordinates,
finalImageHeight: height,
finalImageWidth: width,
});
});
}
crop() { this.customCrop.crop(); }
capture() { this.scanner.capture(); }
renderRectangleCountIndicator() {
const framesLeft = this.state.detectionCountBeforeCapture - this.state.stableCounter;
if (framesLeft < 4 && framesLeft > 0 && this.state.indicatorVisible) {
setTimeout(() => this.setState({ indicatorVisible: false }), 1000);
return (
<View style={s.indicator} blurType="light" blurAmount={10}>
<Text style={s.indicatorT}>{framesLeft}...</Text>
</View>
);
}
}
renderCrop() {
return (
<View style={s.container}>
<CustomCrop
ref={(ref) => this.customCrop = ref}
updateImage={this.updateImage.bind(this)}
rectangleCoordinates={this.state.rectangleCoordinates}
initialImage={this.state.initialImage}
height={this.state.imageHeight}
width={this.state.imageWidth}
overlayColor="rgba(18,190,210, 1)"
overlayStrokeColor="rgba(20,190,210, 1)"
handlerColor="rgba(20,150,160, 1)"
/>
<View style={[s.actions]}>
<TouchableOpacity style={s.button} onPress={this.endCrop.bind(this)}>
<Icon style={s.buttonI} name="md-arrow-back" />
</TouchableOpacity>
<TouchableOpacity style={s.button} onPress={this.crop.bind(this)}>
<Icon style={s.buttonI} name="md-checkmark" />
</TouchableOpacity>
</View>
</View>
);
}
renderScanner() {
return (
<View style={s.container}>
{this.state.image ?
<Image
source={{ uri: `data:image/jpeg;base64,${this.state.image}` }}
style={s.imageResult}
resizeMode="contain"
/> :
<DocumentScanner
ref={(ref) => this.scanner = ref}
onPictureTaken={data => {
Image.getSize(`data:image/jpeg;base64,${data.initialImage}`, (width, height) => {
this.setState({
imageWidth: width,
imageHeight: height,
finalImageHeight: height,
finalImageWidth: width,
image: data.croppedImage || data.initialImage,
initialImage: data.initialImage,
rectangleCoordinates: data.rectangleCoordinates,
});
});
}}
onRectangleDetect={({ stableCounter, lastDetectionType }) => {
this.setState({
stableCounter,
lastDetectionType,
lastDetectionTime: new Date().getTime()
});
}}
enableTorch={this.state.torchEnabled}
brightness={this.state.brightness}
saturation={this.state.saturation}
contrast={this.state.contrast}
detectionCountBeforeCapture={this.state.detectionCountBeforeCapture}
detectionRefreshRateInMS={50}
overlayColor="rgba(20,190,210, 0.7)"
style={s.scanner}
/>
}
{this.state.image &&
<View style={s.actions}>
<TouchableOpacity style={s.button} onPress={() => this.setState({ image: null })}>
<Icon style={s.buttonI} name="md-refresh" />
</TouchableOpacity>
<TouchableOpacity style={s.button} onPress={this.startCrop.bind(this)}>
<Icon style={s.buttonI} name="md-crop" />
</TouchableOpacity>
<TouchableOpacity style={s.button} onPress={this.handleCapture.bind(this)}>
<Icon style={s.buttonI} name="md-checkmark" />
</TouchableOpacity>
</View>
}
{!this.state.image &&
<View style={s.tutoC}>
<Text style={s.tuto}>{this.showTutorialContent()}</Text>
</View>
}
{!this.state.image &&
<View style={s.actions}>
<FloatingButton
onPress={() => this.setState({ torchEnabled: !this.state.torchEnabled })}
>
{this.state.torchEnabled ? I18n.t('disable') : I18n.t('enable')} flash
</FloatingButton>
<FloatingButton onPress={this.toggleBlackAndWhite.bind(this)}>
{this.state.blackAndWhite ? I18n.t('colors') : I18n.t('b&w')}
</FloatingButton>
{this.state.showCapture &&
<View style={s.capture}>
<TouchableOpacity onPress={() => this.capture()} style={s.captureI} />
</View>
}
</View>
}
{this.renderRectangleCountIndicator()}
</View>
);
}
render() {
return this.state.cropping ? this.renderCrop() : this.renderScanner();
}
}
const s = {
container: { flex: 1, backgroundColor: 'black' },
scanner: { flex: 1, backgroundColor: 'black' },
imageResult: { flex: 1 },
capture: {
position: 'absolute',
width: 65,
height: 65,
left: (Dimensions.get('window').width / 2) - 32.5,
bottom: 75,
borderWidth: 2,
borderColor: '#FFF',
alignItems: 'center',
justifyContent: 'center',
borderRadius: 100,
},
captureI: {
backgroundColor: '#fff',
borderRadius: 100,
width: 55,
height: 55,
},
crop: {
position: 'absolute',
bottom: 100,
width: 40,
height: 40,
backgroundColor: 'black',
},
tutoC: {
position: 'absolute',
width: Dimensions.get('window').width - 40,
left: 20,
top: 24,
padding: 10,
borderRadius: 15,
backgroundColor: 'rgba(255,255,255, 0.2)',
},
actionsPicture: {
position: 'absolute',
width: 50,
top: 84,
left: 15,
height: 200,
alignItems: 'center',
justifyContent: 'flex-start',
flexDirection: 'column'
},
indicator: {
width: 200,
height: 60,
left: (Dimensions.get('window').width / 2) - 100,
position: 'absolute',
alignItems: 'center',
justifyContent: 'center',
bottom: 80,
borderRadius: 15,
},
indicatorT: {
fontSize: 21,
color: '#FFF',
},
tuto: {
color: '#000',
textAlign: 'center',
fontSize: 17,
},
actions: {
backgroundColor: '#111',
width: Dimensions.get('window').width,
flex: 0.1,
alignItems: 'center',
padding: 0,
justifyContent: 'space-between',
flexDirection: 'row'
},
button: {
backgroundColor: 'transparent',
borderRadius: 200,
height: 54,
paddingHorizontal: 15,
alignItems: 'center',
justifyContent: 'center',
},
buttonI: {
color: '#FFF',
fontSize: 30,
}
};
export default AutomaticCardAdd;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment