@gooddollar/react-native-web-facecapture
Version:
240 lines (229 loc) • 6.73 kB
JavaScript
import { Buffer } from "buffer/";
import React, { useState, useCallback } from "react";
import { StyleSheet, View, Text, Image } from "react-native";
import _throttle from "lodash/throttle";
import Camera from "./Camera";
import CameraOverlay from "./CameraOverlay";
import {
isValidFace as isValidFaceDefault,
isQualityImage as isQualityImageDefault,
cropToFace
} from "./faceQuality";
global.Buffer = Buffer;
let takingPicture = false;
const FaceCapture = ({
isValidFace = isValidFaceDefault,
isQualityImage = isQualityImageDefault,
onFaces,
onError,
cameraContainerStyle = {},
containerStyle = {},
cameraProps = {},
overlayProps,
pictureOptions = {}
}) => {
let camera;
pictureOptions = {
...{
width: 640,
orientation: "portrait",
quality: 0.9,
base64: true,
doNotSave: true,
forceOrientation: true,
fixOrientation: true,
exif: true
},
...pictureOptions
};
let [face, setFace] = useState();
let [message, setMessage] = useState();
let [valid, setValid] = useState(false);
let [brightness, setBrightness] = useState({ ok: true });
let [captured, setCaptured] = useState([]);
let [sample, setSample] = useState();
const onFacesProxy = async (faces, viewport, camera) => {
// console.log({ faces }, faces.faces[0])
try {
//stop once we have a valid face
if (valid) return;
if (!faces || !faces.faces) return;
if (faces.faces.length === 1) {
let imageResult;
let tookPicture = false;
setFace(faces.faces[0]);
if (takingPicture === false) {
imageResult = takePicture(camera, faces.faces[0], viewport);
tookPicture = true;
}
//if badlighting status is on then dont continue unless new image is ok
if (brightness.ok === false) {
setMessage("Too Dark " + brightness.brightness);
const isGoodLighting = await imageResult;
console.log({ isGoodLighting });
if (isGoodLighting !== true) return;
setMessage(undefined);
}
const facesResult = faces.faces.map(_ => {
const res = isValidFace(viewport, _);
return { ..._, ...res };
});
const validFaces = facesResult.filter(_ => _.ok);
if (validFaces.length === 1) {
setMessage(undefined);
setValid(true);
//take last shot
if (tookPicture === false)
await takePicture(camera, faces.faces[0], viewport);
onFaces(validFaces, camera, captured);
} else {
console.log(facesResult[0]);
setMessage(facesResult[0].error);
}
} else if (faces.faces.length > 1) {
setMessage("More Than One Face Detected");
} else {
setMessage("No faces detected");
}
} catch (e) {
console.log("Exception in onFacesProxy", { e });
onError && onError({ error: "onFacesProxy failed", exception: e });
}
};
const handler = useCallback(_throttle(onFacesProxy, 200), [
valid,
brightness,
captured
]);
const renderFace = ({ bounds, faceID, rollAngle, yawAngle }) => {
console.log("Rendering", { bounds });
return (
<View
key={faceID}
transform={[
{ rotateZ: `${rollAngle.toFixed(0)}deg` },
{ rotateY: `${yawAngle.toFixed(0)}deg` }
]}
style={[
styles.face,
{
...bounds.size,
left: bounds.origin.x,
top: bounds.origin.y
}
]}
>
<Text style={styles.faceText}>ID: {faceID}</Text>
<Text style={styles.faceText}>rollAngle: {rollAngle.toFixed(0)}</Text>
<Text style={styles.faceText}>yawAngle: {yawAngle.toFixed(0)}</Text>
</View>
);
};
const takePicture = async (camera, face, viewport) => {
if (camera) {
try {
takingPicture = true;
const data = await camera.takePictureAsync(pictureOptions);
takingPicture = false;
const faceBase64 = await cropToFace(data, face, viewport);
setSample(faceBase64);
const isValid = isQualityImage(faceBase64);
setBrightness(isValid);
captured = captured.slice(0, 5);
captured.unshift(data);
setCaptured(captured);
console.log(
Object.keys(data),
captured.length,
data.exif,
data.deviceOrientation,
data.pictureOrientation
);
return isValid.ok;
} catch (e) {
console.log("Error capturing face:", e);
onError && onError({ error: "takePicture failed", exception: e });
}
}
};
return (
<View style={[styles.container, containerStyle]}>
<View style={[styles.cameraContainer, cameraContainerStyle]}>
<Camera onError={onError} onFaces={handler} cameraProps={cameraProps} />
</View>
<CameraOverlay
color={valid ? "rgba(0,250,50,0.5)" : "rgba(0,0,0,0.5)"}
{...overlayProps}
/>
<View style={styles.facesContainer}>
<Text style={styles.feedBack}>{message}</Text>
{process.env.NODE_ENV === "development" && (
<Text style={styles.feedBack}>{brightness.brightness}</Text>
)}
</View>
{process.env.NODE_ENV === "development" && face && (
<View style={styles.facesContainer}>{renderFace(face)}</View>
)}
{process.env.NODE_ENV === "development" && sample && (
<Image
source={{
isStatic: true,
uri: "data:image/jpeg;base64," + sample
}}
resizeMethod="auto"
resizeMode="center"
style={{
position: "absolute",
top: 400,
height: "50%",
width: "50%"
}}
/>
)}
</View>
);
};
const styles = StyleSheet.create({
container: {
flexDirection: "column",
backgroundColor: "white",
width: "100%",
height: "100%",
justifyContent: "center",
alignContent: "center",
alignSelf: "center"
},
cameraContainer: {
backgroundColor: "blue",
borderColor: "orange",
width: "100%",
height: "100%",
alignSelf: "center"
},
facesContainer: {
position: "absolute",
flex: 1,
bottom: 0,
right: 0,
left: 0,
top: 0
},
feedBack: {
lineHeight: 40,
fontSize: 20,
fontWeight: "bold",
color: "white",
backgroundColor: "blue",
alignSelf: "center"
},
face: {
padding: 10,
borderWidth: 5,
borderRadius: 2,
position: "absolute",
borderColor: "#FFD700",
justifyContent: "center",
backgroundColor: "rgba(0, 0, 0, 0.5)"
}
});
export default FaceCapture;