mirror of
https://github.com/status-im/react-native-camera.git
synced 2026-08-27 11:41:09 +00:00
Add Camera implementation using CameraView on Android
This is most a copy of expo camera module this still half baked, missing javascript part and package and modules fixes see #246
This commit is contained in:
+17
-6
@@ -1,24 +1,27 @@
|
||||
buildscript {
|
||||
repositories {
|
||||
jcenter()
|
||||
maven {
|
||||
url 'https://maven.google.com'
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:1.2.3'
|
||||
classpath 'com.android.tools.build:gradle:3.0.0'
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'com.android.library'
|
||||
|
||||
android {
|
||||
compileSdkVersion 25
|
||||
buildToolsVersion "25.0.2"
|
||||
compileSdkVersion 26
|
||||
buildToolsVersion "26.0.2"
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 22
|
||||
targetSdkVersion 26
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
versionName "1.0.0"
|
||||
}
|
||||
lintOptions {
|
||||
abortOnError false
|
||||
@@ -28,10 +31,18 @@ android {
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven {
|
||||
url 'https://maven.google.com'
|
||||
}
|
||||
maven { url "https://jitpack.io" }
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile "com.facebook.react:react-native:0.19.+"
|
||||
compile 'com.facebook.react:react-native:+'
|
||||
compile "com.google.zxing:core:3.2.1"
|
||||
compile "com.drewnoakes:metadata-extractor:2.9.1"
|
||||
compile 'com.google.android.gms:play-services-vision:+'
|
||||
compile "com.android.support:exifinterface:26.0.2"
|
||||
|
||||
compile 'com.github.react-native-community:cameraview:df60b07573'
|
||||
}
|
||||
|
||||
BIN
Binary file not shown.
@@ -0,0 +1,6 @@
|
||||
#Sun Dec 31 13:43:56 BRST 2017
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip
|
||||
+160
@@ -0,0 +1,160 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
##############################################################################
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS=""
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn ( ) {
|
||||
echo "$*"
|
||||
}
|
||||
|
||||
die ( ) {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
esac
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD="java"
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# For Darwin, add options to specify how the application appears in the dock
|
||||
if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin, switch paths to Windows format before running java
|
||||
if $cygwin ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
SEP="|"
|
||||
done
|
||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||
# Add a user-defined pattern to the cygpath arguments
|
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||
fi
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=$((i+1))
|
||||
done
|
||||
case $i in
|
||||
(0) set -- ;;
|
||||
(1) set -- "$args0" ;;
|
||||
(2) set -- "$args0" "$args1" ;;
|
||||
(3) set -- "$args0" "$args1" "$args2" ;;
|
||||
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
|
||||
function splitJvmOpts() {
|
||||
JVM_OPTS=("$@")
|
||||
}
|
||||
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
|
||||
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
|
||||
|
||||
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
|
||||
Vendored
+90
@@ -0,0 +1,90 @@
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS=
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:init
|
||||
@rem Get command-line arguments, handling Windowz variants
|
||||
|
||||
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||
if "%@eval[2+2]" == "4" goto 4NT_args
|
||||
|
||||
:win9xME_args
|
||||
@rem Slurp the command line arguments.
|
||||
set CMD_LINE_ARGS=
|
||||
set _SKIP=2
|
||||
|
||||
:win9xME_args_slurp
|
||||
if "x%~1" == "x" goto execute
|
||||
|
||||
set CMD_LINE_ARGS=%*
|
||||
goto execute
|
||||
|
||||
:4NT_args
|
||||
@rem Get arguments from the 4NT Shell from JP Software
|
||||
set CMD_LINE_ARGS=%$
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
@@ -0,0 +1,272 @@
|
||||
package org.reactnative;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.Matrix;
|
||||
import android.media.ExifInterface;
|
||||
import android.util.Base64;
|
||||
import android.util.Log;
|
||||
|
||||
import com.drew.imaging.ImageMetadataReader;
|
||||
import com.drew.imaging.ImageProcessingException;
|
||||
import com.drew.metadata.Directory;
|
||||
import com.drew.metadata.Metadata;
|
||||
import com.drew.metadata.MetadataException;
|
||||
import com.drew.metadata.Tag;
|
||||
import com.drew.metadata.exif.ExifIFD0Directory;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
public class MutableImage {
|
||||
private static final String TAG = "RNCamera";
|
||||
|
||||
private final byte[] originalImageData;
|
||||
private Bitmap currentRepresentation;
|
||||
private Metadata originalImageMetaData;
|
||||
private boolean hasBeenReoriented = false;
|
||||
|
||||
public MutableImage(byte[] originalImageData) {
|
||||
this.originalImageData = originalImageData;
|
||||
this.currentRepresentation = toBitmap(originalImageData);
|
||||
}
|
||||
|
||||
public void mirrorImage() throws ImageMutationFailedException {
|
||||
Matrix m = new Matrix();
|
||||
|
||||
m.preScale(-1, 1);
|
||||
|
||||
Bitmap bitmap = Bitmap.createBitmap(
|
||||
currentRepresentation,
|
||||
0,
|
||||
0,
|
||||
currentRepresentation.getWidth(),
|
||||
currentRepresentation.getHeight(),
|
||||
m,
|
||||
false
|
||||
);
|
||||
|
||||
if (bitmap == null)
|
||||
throw new ImageMutationFailedException("failed to mirror");
|
||||
|
||||
this.currentRepresentation = bitmap;
|
||||
}
|
||||
|
||||
public void fixOrientation() throws ImageMutationFailedException {
|
||||
try {
|
||||
Metadata metadata = originalImageMetaData();
|
||||
|
||||
ExifIFD0Directory exifIFD0Directory = metadata.getFirstDirectoryOfType(ExifIFD0Directory.class);
|
||||
if (exifIFD0Directory == null) {
|
||||
return;
|
||||
} else if (exifIFD0Directory.containsTag(ExifIFD0Directory.TAG_ORIENTATION)) {
|
||||
int exifOrientation = exifIFD0Directory.getInt(ExifIFD0Directory.TAG_ORIENTATION);
|
||||
if(exifOrientation != 1) {
|
||||
rotate(exifOrientation);
|
||||
exifIFD0Directory.setInt(ExifIFD0Directory.TAG_ORIENTATION, 1);
|
||||
}
|
||||
}
|
||||
} catch (ImageProcessingException | IOException | MetadataException e) {
|
||||
throw new ImageMutationFailedException("failed to fix orientation", e);
|
||||
}
|
||||
}
|
||||
|
||||
//see http://www.impulseadventure.com/photo/exif-orientation.html
|
||||
private void rotate(int exifOrientation) throws ImageMutationFailedException {
|
||||
final Matrix bitmapMatrix = new Matrix();
|
||||
switch (exifOrientation) {
|
||||
case 1:
|
||||
return;//no rotation required
|
||||
case 2:
|
||||
bitmapMatrix.postScale(-1, 1);
|
||||
break;
|
||||
case 3:
|
||||
bitmapMatrix.postRotate(180);
|
||||
break;
|
||||
case 4:
|
||||
bitmapMatrix.postRotate(180);
|
||||
bitmapMatrix.postScale(-1, 1);
|
||||
break;
|
||||
case 5:
|
||||
bitmapMatrix.postRotate(90);
|
||||
bitmapMatrix.postScale(-1, 1);
|
||||
break;
|
||||
case 6:
|
||||
bitmapMatrix.postRotate(90);
|
||||
break;
|
||||
case 7:
|
||||
bitmapMatrix.postRotate(270);
|
||||
bitmapMatrix.postScale(-1, 1);
|
||||
break;
|
||||
case 8:
|
||||
bitmapMatrix.postRotate(270);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
Bitmap transformedBitmap = Bitmap.createBitmap(
|
||||
currentRepresentation,
|
||||
0,
|
||||
0,
|
||||
currentRepresentation.getWidth(),
|
||||
currentRepresentation.getHeight(),
|
||||
bitmapMatrix,
|
||||
false
|
||||
);
|
||||
|
||||
if (transformedBitmap == null)
|
||||
throw new ImageMutationFailedException("failed to rotate");
|
||||
|
||||
this.currentRepresentation = transformedBitmap;
|
||||
this.hasBeenReoriented = true;
|
||||
}
|
||||
|
||||
private static Bitmap toBitmap(byte[] data) {
|
||||
try {
|
||||
ByteArrayInputStream inputStream = new ByteArrayInputStream(data);
|
||||
Bitmap photo = BitmapFactory.decodeStream(inputStream);
|
||||
inputStream.close();
|
||||
return photo;
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException("Will not happen", e);
|
||||
}
|
||||
}
|
||||
|
||||
public String toBase64(int jpegQualityPercent) {
|
||||
return Base64.encodeToString(toJpeg(currentRepresentation, jpegQualityPercent), Base64.DEFAULT);
|
||||
}
|
||||
|
||||
public void writeDataToFile(File file, ReadableMap options, int jpegQualityPercent) throws IOException {
|
||||
FileOutputStream fos = new FileOutputStream(file);
|
||||
fos.write(toJpeg(currentRepresentation, jpegQualityPercent));
|
||||
fos.close();
|
||||
|
||||
try {
|
||||
ExifInterface exif = new ExifInterface(file.getAbsolutePath());
|
||||
|
||||
// copy original exif data to the output exif...
|
||||
// unfortunately, this Android ExifInterface class doesn't understand all the tags so we lose some
|
||||
for (Directory directory : originalImageMetaData().getDirectories()) {
|
||||
for (Tag tag : directory.getTags()) {
|
||||
int tagType = tag.getTagType();
|
||||
Object object = directory.getObject(tagType);
|
||||
exif.setAttribute(tag.getTagName(), object.toString());
|
||||
}
|
||||
}
|
||||
|
||||
writeLocationExifData(options, exif);
|
||||
|
||||
if(hasBeenReoriented)
|
||||
rewriteOrientation(exif);
|
||||
|
||||
exif.saveAttributes();
|
||||
} catch (ImageProcessingException | IOException e) {
|
||||
Log.e(TAG, "failed to save exif data", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void rewriteOrientation(ExifInterface exif) {
|
||||
exif.setAttribute(ExifInterface.TAG_ORIENTATION, String.valueOf(ExifInterface.ORIENTATION_NORMAL));
|
||||
}
|
||||
|
||||
private void writeLocationExifData(ReadableMap options, ExifInterface exif) {
|
||||
if(!options.hasKey("metadata"))
|
||||
return;
|
||||
|
||||
ReadableMap metadata = options.getMap("metadata");
|
||||
if (!metadata.hasKey("location"))
|
||||
return;
|
||||
|
||||
ReadableMap location = metadata.getMap("location");
|
||||
if(!location.hasKey("coords"))
|
||||
return;
|
||||
|
||||
try {
|
||||
ReadableMap coords = location.getMap("coords");
|
||||
double latitude = coords.getDouble("latitude");
|
||||
double longitude = coords.getDouble("longitude");
|
||||
|
||||
GPS.writeExifData(latitude, longitude, exif);
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Couldn't write location data", e);
|
||||
}
|
||||
}
|
||||
|
||||
private Metadata originalImageMetaData() throws ImageProcessingException, IOException {
|
||||
if(this.originalImageMetaData == null) {//this is expensive, don't do it more than once
|
||||
originalImageMetaData = ImageMetadataReader.readMetadata(
|
||||
new BufferedInputStream(new ByteArrayInputStream(originalImageData)),
|
||||
originalImageData.length
|
||||
);
|
||||
}
|
||||
return originalImageMetaData;
|
||||
}
|
||||
|
||||
private static byte[] toJpeg(Bitmap bitmap, int quality) throws OutOfMemoryError {
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
bitmap.compress(Bitmap.CompressFormat.JPEG, quality, outputStream);
|
||||
|
||||
try {
|
||||
return outputStream.toByteArray();
|
||||
} finally {
|
||||
try {
|
||||
outputStream.close();
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "problem compressing jpeg", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class ImageMutationFailedException extends Exception {
|
||||
public ImageMutationFailedException(String detailMessage, Throwable throwable) {
|
||||
super(detailMessage, throwable);
|
||||
}
|
||||
|
||||
public ImageMutationFailedException(String detailMessage) {
|
||||
super(detailMessage);
|
||||
}
|
||||
}
|
||||
|
||||
private static class GPS {
|
||||
public static void writeExifData(double latitude, double longitude, ExifInterface exif) throws IOException {
|
||||
exif.setAttribute(ExifInterface.TAG_GPS_LATITUDE, toDegreeMinuteSecods(latitude));
|
||||
exif.setAttribute(ExifInterface.TAG_GPS_LATITUDE_REF, latitudeRef(latitude));
|
||||
exif.setAttribute(ExifInterface.TAG_GPS_LONGITUDE, toDegreeMinuteSecods(longitude));
|
||||
exif.setAttribute(ExifInterface.TAG_GPS_LONGITUDE_REF, longitudeRef(longitude));
|
||||
}
|
||||
|
||||
private static String latitudeRef(double latitude) {
|
||||
return latitude < 0.0d ? "S" : "N";
|
||||
}
|
||||
|
||||
private static String longitudeRef(double longitude) {
|
||||
return longitude < 0.0d ? "W" : "E";
|
||||
}
|
||||
|
||||
private static String toDegreeMinuteSecods(double latitude) {
|
||||
latitude = Math.abs(latitude);
|
||||
int degree = (int) latitude;
|
||||
latitude *= 60;
|
||||
latitude -= (degree * 60.0d);
|
||||
int minute = (int) latitude;
|
||||
latitude *= 60;
|
||||
latitude -= (minute * 60.0d);
|
||||
int second = (int) (latitude * 1000.0d);
|
||||
|
||||
StringBuffer sb = new StringBuffer();
|
||||
sb.append(degree);
|
||||
sb.append("/1,");
|
||||
sb.append(minute);
|
||||
sb.append("/1,");
|
||||
sb.append(second);
|
||||
sb.append("/1000,");
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
package org.reactnative.camera;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import org.reactnative.facedetector.ExpoFaceDetector;
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.bridge.Promise;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||
import com.facebook.react.bridge.ReactMethod;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.facebook.react.bridge.WritableArray;
|
||||
import com.google.android.cameraview.AspectRatio;
|
||||
import com.google.zxing.BarcodeFormat;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public class CameraModule extends ReactContextBaseJavaModule {
|
||||
private static final String TAG = "CameraModule";
|
||||
|
||||
private static ReactApplicationContext mReactContext;
|
||||
|
||||
// private static ScopedContext mScopedContext;
|
||||
static final int VIDEO_2160P = 0;
|
||||
static final int VIDEO_1080P = 1;
|
||||
static final int VIDEO_720P = 2;
|
||||
static final int VIDEO_480P = 3;
|
||||
static final int VIDEO_4x3 = 4;
|
||||
|
||||
public static final Map<String, Object> VALID_BARCODE_TYPES =
|
||||
Collections.unmodifiableMap(new HashMap<String, Object>() {
|
||||
{
|
||||
put("aztec", BarcodeFormat.AZTEC.toString());
|
||||
put("ean13", BarcodeFormat.EAN_13.toString());
|
||||
put("ean8", BarcodeFormat.EAN_8.toString());
|
||||
put("qr", BarcodeFormat.QR_CODE.toString());
|
||||
put("pdf417", BarcodeFormat.PDF_417.toString());
|
||||
put("upc_e", BarcodeFormat.UPC_E.toString());
|
||||
put("datamatrix", BarcodeFormat.DATA_MATRIX.toString());
|
||||
put("code39", BarcodeFormat.CODE_39.toString());
|
||||
put("code93", BarcodeFormat.CODE_93.toString());
|
||||
put("interleaved2of5", BarcodeFormat.ITF.toString());
|
||||
put("codabar", BarcodeFormat.CODABAR.toString());
|
||||
put("code128", BarcodeFormat.CODE_128.toString());
|
||||
put("maxicode", BarcodeFormat.MAXICODE.toString());
|
||||
put("rss14", BarcodeFormat.RSS_14.toString());
|
||||
put("rssexpanded", BarcodeFormat.RSS_EXPANDED.toString());
|
||||
put("upc_a", BarcodeFormat.UPC_A.toString());
|
||||
put("upc_ean", BarcodeFormat.UPC_EAN_EXTENSION.toString());
|
||||
}
|
||||
});
|
||||
|
||||
public CameraModule(ReactApplicationContext reactContext) {
|
||||
super(reactContext);
|
||||
mReactContext = reactContext;
|
||||
}
|
||||
|
||||
public static ReactApplicationContext getReactContextSingleton() {
|
||||
return mReactContext;
|
||||
}
|
||||
|
||||
public static Context getScopedContextSingleton() {
|
||||
return mReactContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "ExponentCameraModule";
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Map<String, Object> getConstants() {
|
||||
return Collections.unmodifiableMap(new HashMap<String, Object>() {
|
||||
{
|
||||
put("Type", getTypeConstants());
|
||||
put("FlashMode", getFlashModeConstants());
|
||||
put("AutoFocus", getAutoFocusConstants());
|
||||
put("WhiteBalance", getWhiteBalanceConstants());
|
||||
put("VideoQuality", getVideoQualityConstants());
|
||||
put("BarCodeType", getBarCodeConstants());
|
||||
put("FaceDetection", Collections.unmodifiableMap(new HashMap<String, Object>() {
|
||||
{
|
||||
put("Mode", getFaceDetectionModeConstants());
|
||||
put("Landmarks", getFaceDetectionLandmarksConstants());
|
||||
put("Classifications", getFaceDetectionClassificationsConstants());
|
||||
}
|
||||
|
||||
private Map<String, Object> getFaceDetectionModeConstants() {
|
||||
return Collections.unmodifiableMap(new HashMap<String, Object>() {
|
||||
{
|
||||
put("fast", ExpoFaceDetector.FAST_MODE);
|
||||
put("accurate", ExpoFaceDetector.ACCURATE_MODE);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private Map<String, Object> getFaceDetectionClassificationsConstants() {
|
||||
return Collections.unmodifiableMap(new HashMap<String, Object>() {
|
||||
{
|
||||
put("all", ExpoFaceDetector.ALL_CLASSIFICATIONS);
|
||||
put("none", ExpoFaceDetector.NO_CLASSIFICATIONS);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private Map<String, Object> getFaceDetectionLandmarksConstants() {
|
||||
return Collections.unmodifiableMap(new HashMap<String, Object>() {
|
||||
{
|
||||
put("all", ExpoFaceDetector.ALL_LANDMARKS);
|
||||
put("none", ExpoFaceDetector.NO_LANDMARKS);
|
||||
}
|
||||
});
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private Map<String, Object> getTypeConstants() {
|
||||
return Collections.unmodifiableMap(new HashMap<String, Object>() {
|
||||
{
|
||||
put("front", Constants.FACING_FRONT);
|
||||
put("back", Constants.FACING_BACK);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private Map<String, Object> getFlashModeConstants() {
|
||||
return Collections.unmodifiableMap(new HashMap<String, Object>() {
|
||||
{
|
||||
put("off", Constants.FLASH_OFF);
|
||||
put("on", Constants.FLASH_ON);
|
||||
put("auto", Constants.FLASH_AUTO);
|
||||
put("torch", Constants.FLASH_TORCH);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private Map<String, Object> getAutoFocusConstants() {
|
||||
return Collections.unmodifiableMap(new HashMap<String, Object>() {
|
||||
{
|
||||
put("on", true);
|
||||
put("off", false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private Map<String, Object> getWhiteBalanceConstants() {
|
||||
return Collections.unmodifiableMap(new HashMap<String, Object>() {
|
||||
{
|
||||
put("auto", Constants.WB_AUTO);
|
||||
put("cloudy", Constants.WB_CLOUDY);
|
||||
put("sunny", Constants.WB_SUNNY);
|
||||
put("shadow", Constants.WB_SHADOW);
|
||||
put("fluorescent", Constants.WB_FLUORESCENT);
|
||||
put("incandescent", Constants.WB_INCANDESCENT);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private Map<String, Object> getVideoQualityConstants() {
|
||||
return Collections.unmodifiableMap(new HashMap<String, Object>() {
|
||||
{
|
||||
put("2160p", VIDEO_2160P);
|
||||
put("1080p", VIDEO_1080P);
|
||||
put("720p", VIDEO_720P);
|
||||
put("480p", VIDEO_480P);
|
||||
put("4:3", VIDEO_4x3);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private Map<String, Object> getBarCodeConstants() {
|
||||
return VALID_BARCODE_TYPES;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void takePicture(ReadableMap options, final Promise promise) {
|
||||
CameraViewManager.getInstance().takePicture(options, promise);
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void record(ReadableMap options, final Promise promise) {
|
||||
CameraViewManager.getInstance().record(options, promise);
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void stopRecording() {
|
||||
CameraViewManager.getInstance().stopRecording();
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void getSupportedRatios(final Promise promise) {
|
||||
WritableArray result = Arguments.createArray();
|
||||
Set<AspectRatio> ratios = CameraViewManager.getInstance().getSupportedRatios();
|
||||
if (ratios != null) {
|
||||
for (AspectRatio ratio : ratios) {
|
||||
result.pushString(ratio.toString());
|
||||
}
|
||||
promise.resolve(result);
|
||||
} else {
|
||||
promise.reject("E_CAMERA_UNAVAILABLE", "Camera is not running");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
package org.reactnative.camera;
|
||||
|
||||
import android.Manifest;
|
||||
import android.graphics.Bitmap;
|
||||
import android.os.Build;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import org.reactnative.camera.tasks.ResolveTakenPictureAsyncTask;
|
||||
import com.facebook.react.bridge.Promise;
|
||||
import com.facebook.react.bridge.ReadableArray;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.facebook.react.common.MapBuilder;
|
||||
import com.facebook.react.uimanager.ThemedReactContext;
|
||||
import com.facebook.react.uimanager.ViewGroupManager;
|
||||
import com.facebook.react.uimanager.annotations.ReactProp;
|
||||
import com.google.android.cameraview.AspectRatio;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public class CameraViewManager extends ViewGroupManager<ExpoCameraView> {
|
||||
public enum Events {
|
||||
EVENT_CAMERA_READY("onCameraReady"),
|
||||
EVENT_ON_MOUNT_ERROR("onMountError"),
|
||||
EVENT_ON_BAR_CODE_READ("onBarCodeRead"),
|
||||
EVENT_ON_FACES_DETECTED("onFacesDetected"),
|
||||
EVENT_ON_FACE_DETECTION_ERROR("onFaceDetectionError");
|
||||
|
||||
private final String mName;
|
||||
|
||||
Events(final String name) {
|
||||
mName = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return mName;
|
||||
}
|
||||
}
|
||||
|
||||
private static final String REACT_CLASS = "ExponentCamera";
|
||||
|
||||
private static CameraViewManager instance;
|
||||
private ExpoCameraView mCameraView;
|
||||
|
||||
public CameraViewManager() {
|
||||
super();
|
||||
instance = this;
|
||||
}
|
||||
|
||||
public static CameraViewManager getInstance() { return instance; }
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return REACT_CLASS;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ExpoCameraView createViewInstance(ThemedReactContext themedReactContext) {
|
||||
mCameraView = new ExpoCameraView(themedReactContext);
|
||||
return mCameraView;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Map<String, Object> getExportedCustomDirectEventTypeConstants() {
|
||||
MapBuilder.Builder<String, Object> builder = MapBuilder.builder();
|
||||
for (Events event : Events.values()) {
|
||||
builder.put(event.toString(), MapBuilder.of("registrationName", event.toString()));
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
@ReactProp(name = "type")
|
||||
public void setType(ExpoCameraView view, int type) {
|
||||
view.setFacing(type);
|
||||
}
|
||||
|
||||
@ReactProp(name = "ratio")
|
||||
public void setRatio(ExpoCameraView view, String ratio) {
|
||||
view.setAspectRatio(AspectRatio.parse(ratio));
|
||||
}
|
||||
|
||||
@ReactProp(name = "flashMode")
|
||||
public void setFlashMode(ExpoCameraView view, int torchMode) {
|
||||
view.setFlash(torchMode);
|
||||
}
|
||||
|
||||
@ReactProp(name = "autoFocus")
|
||||
public void setAutoFocus(ExpoCameraView view, boolean autoFocus) {
|
||||
view.setAutoFocus(autoFocus);
|
||||
}
|
||||
|
||||
@ReactProp(name = "focusDepth")
|
||||
public void setFocusDepth(ExpoCameraView view, float depth) {
|
||||
view.setFocusDepth(depth);
|
||||
}
|
||||
|
||||
@ReactProp(name = "zoom")
|
||||
public void setZoom(ExpoCameraView view, float zoom) {
|
||||
view.setZoom(zoom);
|
||||
}
|
||||
|
||||
@ReactProp(name = "whiteBalance")
|
||||
public void setWhiteBalance(ExpoCameraView view, int whiteBalance) {
|
||||
view.setWhiteBalance(whiteBalance);
|
||||
}
|
||||
|
||||
@ReactProp(name = "barCodeTypes")
|
||||
public void setBarCodeTypes(ExpoCameraView view, ReadableArray barCodeTypes) {
|
||||
if (barCodeTypes == null) {
|
||||
return;
|
||||
}
|
||||
List<String> result = new ArrayList<>(barCodeTypes.size());
|
||||
for (int i = 0; i < barCodeTypes.size(); i++) {
|
||||
result.add(barCodeTypes.getString(i));
|
||||
}
|
||||
view.setBarCodeTypes(result);
|
||||
}
|
||||
|
||||
@ReactProp(name = "barCodeScannerEnabled")
|
||||
public void setBarCodeScanning(ExpoCameraView view, boolean barCodeScannerEnabled) {
|
||||
view.setShouldScanBarCodes(barCodeScannerEnabled);
|
||||
}
|
||||
|
||||
@ReactProp(name = "faceDetectorEnabled")
|
||||
public void setFaceDetecting(ExpoCameraView view, boolean faceDetectorEnabled) {
|
||||
view.setShouldDetectFaces(faceDetectorEnabled);
|
||||
}
|
||||
|
||||
@ReactProp(name = "faceDetectionMode")
|
||||
public void setFaceDetectionMode(ExpoCameraView view, int mode) {
|
||||
view.setFaceDetectionMode(mode);
|
||||
}
|
||||
|
||||
@ReactProp(name = "faceDetectionLandmarks")
|
||||
public void setFaceDetectionLandmarks(ExpoCameraView view, int landmarks) {
|
||||
view.setFaceDetectionLandmarks(landmarks);
|
||||
}
|
||||
|
||||
@ReactProp(name = "faceDetectionClassifications")
|
||||
public void setFaceDetectionClassifications(ExpoCameraView view, int classifications) {
|
||||
view.setFaceDetectionClassifications(classifications);
|
||||
}
|
||||
|
||||
public void takePicture(ReadableMap options, Promise promise) {
|
||||
if (!Build.FINGERPRINT.contains("generic")) {
|
||||
if (mCameraView.isCameraOpened()) {
|
||||
mCameraView.takePicture(options, promise);
|
||||
} else {
|
||||
promise.reject("E_CAMERA_UNAVAILABLE", "Camera is not running");
|
||||
}
|
||||
} else {
|
||||
Bitmap image = ExpoCameraViewHelper.generateSimulatorPhoto(mCameraView.getWidth(), mCameraView.getHeight());
|
||||
ByteBuffer byteBuffer = ByteBuffer.allocate(image.getRowBytes() * image.getHeight());
|
||||
image.copyPixelsToBuffer(byteBuffer);
|
||||
new ResolveTakenPictureAsyncTask(byteBuffer.array(), promise, options).execute();
|
||||
}
|
||||
}
|
||||
|
||||
public void record(final ReadableMap options, final Promise promise) {
|
||||
// TODO fix this
|
||||
// Exponent.getInstance().getPermissions(new Exponent.PermissionsListener() {
|
||||
// @Override
|
||||
// public void permissionsGranted() {
|
||||
// if (mCameraView.isCameraOpened()) {
|
||||
// mCameraView.record(options, promise);
|
||||
// } else {
|
||||
// promise.reject("E_CAMERA_UNAVAILABLE", "Camera is not running");
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public void permissionsDenied() {
|
||||
// promise.reject(new SecurityException("User rejected audio permissions"));
|
||||
// }
|
||||
// }, new String[]{Manifest.permission.RECORD_AUDIO});
|
||||
|
||||
}
|
||||
|
||||
public void stopRecording() {
|
||||
if (mCameraView.isCameraOpened()) {
|
||||
mCameraView.stopRecording();
|
||||
}
|
||||
}
|
||||
|
||||
public Set<AspectRatio> getSupportedRatios() {
|
||||
if (mCameraView.isCameraOpened()) {
|
||||
return mCameraView.getSupportedAspectRatios();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright (C) 2016 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.reactnative.camera;
|
||||
|
||||
import com.google.android.cameraview.AspectRatio;
|
||||
|
||||
public interface Constants {
|
||||
|
||||
AspectRatio DEFAULT_ASPECT_RATIO = AspectRatio.of(4, 3);
|
||||
|
||||
int FACING_BACK = 0;
|
||||
int FACING_FRONT = 1;
|
||||
|
||||
int FLASH_OFF = 0;
|
||||
int FLASH_ON = 1;
|
||||
int FLASH_TORCH = 2;
|
||||
int FLASH_AUTO = 3;
|
||||
int FLASH_RED_EYE = 4;
|
||||
|
||||
int LANDSCAPE_90 = 90;
|
||||
int LANDSCAPE_270 = 270;
|
||||
|
||||
int WB_AUTO = 0;
|
||||
int WB_CLOUDY = 1;
|
||||
int WB_SUNNY = 2;
|
||||
int WB_SHADOW = 3;
|
||||
int WB_FLUORESCENT = 4;
|
||||
int WB_INCANDESCENT = 5;
|
||||
}
|
||||
@@ -0,0 +1,316 @@
|
||||
package org.reactnative.camera;
|
||||
|
||||
import android.Manifest;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.graphics.Color;
|
||||
import android.media.CamcorderProfile;
|
||||
import android.os.Build;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.util.SparseArray;
|
||||
import android.view.View;
|
||||
|
||||
import org.reactnative.camera.tasks.BarCodeScannerAsyncTask;
|
||||
import org.reactnative.camera.tasks.BarCodeScannerAsyncTaskDelegate;
|
||||
import org.reactnative.camera.tasks.FaceDetectorAsyncTask;
|
||||
import org.reactnative.camera.tasks.FaceDetectorAsyncTaskDelegate;
|
||||
import org.reactnative.camera.tasks.ResolveTakenPictureAsyncTask;
|
||||
import org.reactnative.camera.utils.ImageDimensions;
|
||||
import org.reactnative.facedetector.ExpoFaceDetector;
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.bridge.LifecycleEventListener;
|
||||
import com.facebook.react.bridge.Promise;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.facebook.react.uimanager.ThemedReactContext;
|
||||
import com.google.android.cameraview.CameraView;
|
||||
import com.google.android.gms.vision.face.Face;
|
||||
import com.google.zxing.BarcodeFormat;
|
||||
import com.google.zxing.DecodeHintType;
|
||||
import com.google.zxing.MultiFormatReader;
|
||||
import com.google.zxing.Result;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.EnumMap;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
|
||||
public class ExpoCameraView extends CameraView implements LifecycleEventListener, BarCodeScannerAsyncTaskDelegate, FaceDetectorAsyncTaskDelegate {
|
||||
private Queue<Promise> mPictureTakenPromises = new ConcurrentLinkedQueue<>();
|
||||
private Map<Promise, ReadableMap> mPictureTakenOptions = new ConcurrentHashMap<>();
|
||||
private Promise mVideoRecordedPromise;
|
||||
private List<String> mBarCodeTypes = null;
|
||||
|
||||
// Concurrency lock for scanners to avoid flooding the runtime
|
||||
public volatile boolean barCodeScannerTaskLock = false;
|
||||
public volatile boolean faceDetectorTaskLock = false;
|
||||
|
||||
// Scanning-related properties
|
||||
private final MultiFormatReader mMultiFormatReader = new MultiFormatReader();
|
||||
private final ExpoFaceDetector mFaceDetector;
|
||||
private boolean mShouldDetectFaces = false;
|
||||
private boolean mShouldScanBarCodes = false;
|
||||
private int mFaceDetectorMode = ExpoFaceDetector.FAST_MODE;
|
||||
private int mFaceDetectionLandmarks = ExpoFaceDetector.NO_LANDMARKS;
|
||||
private int mFaceDetectionClassifications = ExpoFaceDetector.NO_CLASSIFICATIONS;
|
||||
|
||||
public ExpoCameraView(ThemedReactContext themedReactContext) {
|
||||
super(themedReactContext);
|
||||
initBarcodeReader();
|
||||
mFaceDetector = new ExpoFaceDetector(themedReactContext);
|
||||
setupFaceDetector();
|
||||
themedReactContext.addLifecycleEventListener(this);
|
||||
|
||||
addCallback(new Callback() {
|
||||
@Override
|
||||
public void onCameraOpened(CameraView cameraView) {
|
||||
ExpoCameraViewHelper.emitCameraReadyEvent(cameraView);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMountError(CameraView cameraView) {
|
||||
ExpoCameraViewHelper.emitMountErrorEvent(cameraView);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPictureTaken(CameraView cameraView, final byte[] data) {
|
||||
Promise promise = mPictureTakenPromises.poll();
|
||||
ReadableMap options = mPictureTakenOptions.remove(promise);
|
||||
new ResolveTakenPictureAsyncTask(data, promise, options).execute();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVideoRecorded(CameraView cameraView, String path) {
|
||||
if (mVideoRecordedPromise != null) {
|
||||
if (path != null) {
|
||||
WritableMap result = Arguments.createMap();
|
||||
// TODO - fix this
|
||||
//result.putString("uri", ExpFileUtils.uriFromFile(new File(path)).toString());
|
||||
mVideoRecordedPromise.resolve(result);
|
||||
} else {
|
||||
mVideoRecordedPromise.reject("E_RECORDING", "Couldn't stop recording - there is none in progress");
|
||||
}
|
||||
mVideoRecordedPromise = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFramePreview(CameraView cameraView, byte[] data, int width, int height, int rotation) {
|
||||
int correctRotation = ExpoCameraViewHelper.getCorrectCameraRotation(rotation, getFacing());
|
||||
|
||||
if (mShouldScanBarCodes && !barCodeScannerTaskLock && cameraView instanceof BarCodeScannerAsyncTaskDelegate) {
|
||||
barCodeScannerTaskLock = true;
|
||||
BarCodeScannerAsyncTaskDelegate delegate = (BarCodeScannerAsyncTaskDelegate) cameraView;
|
||||
new BarCodeScannerAsyncTask(delegate, mMultiFormatReader, data, width, height).execute();
|
||||
}
|
||||
|
||||
if (mShouldDetectFaces && !faceDetectorTaskLock && cameraView instanceof FaceDetectorAsyncTaskDelegate) {
|
||||
faceDetectorTaskLock = true;
|
||||
FaceDetectorAsyncTaskDelegate delegate = (FaceDetectorAsyncTaskDelegate) cameraView;
|
||||
new FaceDetectorAsyncTask(delegate, mFaceDetector, data, width, height, correctRotation).execute();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
|
||||
View preview = getView();
|
||||
if (null == preview) {
|
||||
return;
|
||||
}
|
||||
this.setBackgroundColor(Color.BLACK);
|
||||
int width = right - left;
|
||||
int height = bottom - top;
|
||||
preview.layout(0, 0, width, height);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestLayout() {
|
||||
// React handles this for us, so we don't need to call super.requestLayout();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewAdded(View child) {
|
||||
if (this.getView() == child || this.getView() == null) return;
|
||||
// remove and readd view to make sure it is in the back.
|
||||
// @TODO figure out why there was a z order issue in the first place and fix accordingly.
|
||||
this.removeView(this.getView());
|
||||
this.addView(this.getView(), 0);
|
||||
}
|
||||
|
||||
public void setBarCodeTypes(List<String> barCodeTypes) {
|
||||
mBarCodeTypes = barCodeTypes;
|
||||
initBarcodeReader();
|
||||
}
|
||||
|
||||
public void takePicture(ReadableMap options, final Promise promise) {
|
||||
mPictureTakenPromises.add(promise);
|
||||
mPictureTakenOptions.put(promise, options);
|
||||
super.takePicture();
|
||||
}
|
||||
|
||||
public void record(ReadableMap options, final Promise promise) {
|
||||
// try {
|
||||
// TODO - fix this
|
||||
String path = "";
|
||||
//String path = ExpFileUtils.generateOutputPath(CameraModule.getScopedContextSingleton().getCacheDir(), "Camera", ".mp4");
|
||||
int maxDuration = options.hasKey("maxDuration") ? options.getInt("maxDuration") : -1;
|
||||
int maxFileSize = options.hasKey("maxFileSize") ? options.getInt("maxFileSize") : -1;
|
||||
|
||||
CamcorderProfile profile = CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH);
|
||||
if (options.hasKey("quality")) {
|
||||
profile = ExpoCameraViewHelper.getCamcorderProfile(options.getInt("quality"));
|
||||
}
|
||||
|
||||
boolean recordAudio = !options.hasKey("mute");
|
||||
|
||||
if (super.record(path, maxDuration * 1000, maxFileSize, recordAudio, profile)) {
|
||||
mVideoRecordedPromise = promise;
|
||||
} else {
|
||||
promise.reject("E_RECORDING_FAILED", "Starting video recording failed. Another recording might be in progress.");
|
||||
}
|
||||
// } catch (IOException e) {
|
||||
// promise.reject("E_RECORDING_FAILED", "Starting video recording failed - could not create video file.");
|
||||
// }
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the barcode decoder.
|
||||
* Supports all iOS codes except [code138, code39mod43, itf14]
|
||||
* Additionally supports [codabar, code128, maxicode, rss14, rssexpanded, upc_a, upc_ean]
|
||||
*/
|
||||
private void initBarcodeReader() {
|
||||
EnumMap<DecodeHintType, Object> hints = new EnumMap<>(DecodeHintType.class);
|
||||
EnumSet<BarcodeFormat> decodeFormats = EnumSet.noneOf(BarcodeFormat.class);
|
||||
|
||||
if (mBarCodeTypes != null) {
|
||||
for (String code : mBarCodeTypes) {
|
||||
String formatString = (String) CameraModule.VALID_BARCODE_TYPES.get(code);
|
||||
if (formatString != null) {
|
||||
decodeFormats.add(BarcodeFormat.valueOf(code));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
hints.put(DecodeHintType.POSSIBLE_FORMATS, decodeFormats);
|
||||
mMultiFormatReader.setHints(hints);
|
||||
}
|
||||
|
||||
public void setShouldScanBarCodes(boolean shouldScanBarCodes) {
|
||||
this.mShouldScanBarCodes = shouldScanBarCodes;
|
||||
setScanning(mShouldDetectFaces || mShouldScanBarCodes);
|
||||
}
|
||||
|
||||
public void onBarCodeRead(Result barCode) {
|
||||
String barCodeType = barCode.getBarcodeFormat().toString();
|
||||
if (!mShouldScanBarCodes || !mBarCodeTypes.contains(barCodeType)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ExpoCameraViewHelper.emitBarCodeReadEvent(this, barCode);
|
||||
}
|
||||
|
||||
public void onBarCodeScanningTaskCompleted() {
|
||||
barCodeScannerTaskLock = false;
|
||||
mMultiFormatReader.reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initial setup of the face detector
|
||||
*/
|
||||
private void setupFaceDetector() {
|
||||
mFaceDetector.setMode(mFaceDetectorMode);
|
||||
mFaceDetector.setLandmarkType(mFaceDetectionLandmarks);
|
||||
mFaceDetector.setClassificationType(mFaceDetectionClassifications);
|
||||
mFaceDetector.setTracking(true);
|
||||
}
|
||||
|
||||
public void setFaceDetectionLandmarks(int landmarks) {
|
||||
mFaceDetectionLandmarks = landmarks;
|
||||
if (mFaceDetector != null) {
|
||||
mFaceDetector.setLandmarkType(landmarks);
|
||||
}
|
||||
}
|
||||
|
||||
public void setFaceDetectionClassifications(int classifications) {
|
||||
mFaceDetectionClassifications = classifications;
|
||||
if (mFaceDetector != null) {
|
||||
mFaceDetector.setClassificationType(classifications);
|
||||
}
|
||||
}
|
||||
|
||||
public void setFaceDetectionMode(int mode) {
|
||||
mFaceDetectorMode = mode;
|
||||
if (mFaceDetector != null) {
|
||||
mFaceDetector.setMode(mode);
|
||||
}
|
||||
}
|
||||
|
||||
public void setShouldDetectFaces(boolean shouldDetectFaces) {
|
||||
this.mShouldDetectFaces = shouldDetectFaces;
|
||||
setScanning(mShouldDetectFaces || mShouldScanBarCodes);
|
||||
}
|
||||
|
||||
public void onFacesDetected(SparseArray<Face> facesReported, int sourceWidth, int sourceHeight, int sourceRotation) {
|
||||
if (!mShouldDetectFaces) {
|
||||
return;
|
||||
}
|
||||
|
||||
SparseArray<Face> facesDetected = facesReported == null ? new SparseArray<Face>() : facesReported;
|
||||
|
||||
ImageDimensions dimensions = new ImageDimensions(sourceWidth, sourceHeight, sourceRotation, getFacing());
|
||||
ExpoCameraViewHelper.emitFacesDetectedEvent(this, facesDetected, dimensions);
|
||||
}
|
||||
|
||||
public void onFaceDetectionError(ExpoFaceDetector faceDetector) {
|
||||
if (!mShouldDetectFaces) {
|
||||
return;
|
||||
}
|
||||
|
||||
ExpoCameraViewHelper.emitFaceDetectionErrorEvent(this, faceDetector);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFaceDetectingTaskCompleted() {
|
||||
faceDetectorTaskLock = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHostResume() {
|
||||
if (hasCameraPermissions()) {
|
||||
if (!Build.FINGERPRINT.contains("generic")) {
|
||||
start();
|
||||
}
|
||||
} else {
|
||||
WritableMap error = Arguments.createMap();
|
||||
error.putString("message", "Camera permissions not granted - component could not be rendered.");
|
||||
ExpoCameraViewHelper.emitMountErrorEvent(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHostPause() {
|
||||
stop();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHostDestroy() {
|
||||
mFaceDetector.release();
|
||||
stop();
|
||||
}
|
||||
|
||||
private boolean hasCameraPermissions() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
int result = ContextCompat.checkSelfPermission(getContext(), Manifest.permission.CAMERA);
|
||||
return result == PackageManager.PERMISSION_GRANTED;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
package org.reactnative.camera;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.media.CamcorderProfile;
|
||||
import android.os.Build;
|
||||
import android.support.media.ExifInterface;
|
||||
import android.util.SparseArray;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import org.reactnative.camera.events.BarCodeReadEvent;
|
||||
import org.reactnative.camera.events.CameraMountErrorEvent;
|
||||
import org.reactnative.camera.events.CameraReadyEvent;
|
||||
import org.reactnative.camera.events.FaceDetectionErrorEvent;
|
||||
import org.reactnative.camera.events.FacesDetectedEvent;
|
||||
import org.reactnative.camera.utils.ImageDimensions;
|
||||
import org.reactnative.facedetector.ExpoFaceDetector;
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.bridge.ReactContext;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.facebook.react.uimanager.UIManagerModule;
|
||||
import com.google.android.cameraview.CameraView;
|
||||
import com.google.android.gms.vision.face.Face;
|
||||
import com.google.zxing.Result;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Calendar;
|
||||
import java.util.Locale;
|
||||
import java.util.UUID;
|
||||
|
||||
public class ExpoCameraViewHelper {
|
||||
// Mount error event
|
||||
|
||||
public static void emitMountErrorEvent(ViewGroup view) {
|
||||
CameraMountErrorEvent event = CameraMountErrorEvent.obtain(view.getId());
|
||||
ReactContext reactContext = (ReactContext) view.getContext();
|
||||
reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher().dispatchEvent(event);
|
||||
}
|
||||
|
||||
// Camera ready event
|
||||
|
||||
public static void emitCameraReadyEvent(ViewGroup view) {
|
||||
CameraReadyEvent event = CameraReadyEvent.obtain(view.getId());
|
||||
ReactContext reactContext = (ReactContext) view.getContext();
|
||||
reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher().dispatchEvent(event);
|
||||
}
|
||||
|
||||
// Face detection events
|
||||
|
||||
public static void emitFacesDetectedEvent(
|
||||
ViewGroup view,
|
||||
SparseArray<Face> faces,
|
||||
ImageDimensions dimensions
|
||||
) {
|
||||
float density = view.getResources().getDisplayMetrics().density;
|
||||
|
||||
double scaleX = (double) view.getWidth() / (dimensions.getWidth() * density);
|
||||
double scaleY = (double) view.getHeight() / (dimensions.getHeight() * density);
|
||||
|
||||
FacesDetectedEvent event = FacesDetectedEvent.obtain(
|
||||
view.getId(),
|
||||
faces,
|
||||
dimensions,
|
||||
scaleX,
|
||||
scaleY
|
||||
);
|
||||
|
||||
ReactContext reactContext = (ReactContext) view.getContext();
|
||||
reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher().dispatchEvent(event);
|
||||
}
|
||||
|
||||
public static void emitFaceDetectionErrorEvent(ViewGroup view, ExpoFaceDetector faceDetector) {
|
||||
FaceDetectionErrorEvent event = FaceDetectionErrorEvent.obtain(view.getId(), faceDetector);
|
||||
ReactContext reactContext = (ReactContext) view.getContext();
|
||||
reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher().dispatchEvent(event);
|
||||
}
|
||||
|
||||
// Bar code read event
|
||||
|
||||
public static void emitBarCodeReadEvent(ViewGroup view, Result barCode) {
|
||||
BarCodeReadEvent event = BarCodeReadEvent.obtain(view.getId(), barCode);
|
||||
ReactContext reactContext = (ReactContext) view.getContext();
|
||||
reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher().dispatchEvent(event);
|
||||
}
|
||||
|
||||
// Utilities
|
||||
|
||||
public static int getCorrectCameraRotation(int rotation, int facing) {
|
||||
if (facing == CameraView.FACING_FRONT) {
|
||||
return (rotation - 90 + 360) % 360;
|
||||
} else {
|
||||
return (-rotation + 90 + 360) % 360;
|
||||
}
|
||||
}
|
||||
|
||||
public static CamcorderProfile getCamcorderProfile(int quality) {
|
||||
CamcorderProfile profile = CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH);
|
||||
switch (quality) {
|
||||
case CameraModule.VIDEO_2160P:
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
profile = CamcorderProfile.get(CamcorderProfile.QUALITY_2160P);
|
||||
}
|
||||
break;
|
||||
case CameraModule.VIDEO_1080P:
|
||||
profile = CamcorderProfile.get(CamcorderProfile.QUALITY_1080P);
|
||||
break;
|
||||
case CameraModule.VIDEO_720P:
|
||||
profile = CamcorderProfile.get(CamcorderProfile.QUALITY_720P);
|
||||
break;
|
||||
case CameraModule.VIDEO_480P:
|
||||
profile = CamcorderProfile.get(CamcorderProfile.QUALITY_480P);
|
||||
break;
|
||||
case CameraModule.VIDEO_4x3:
|
||||
profile = CamcorderProfile.get(CamcorderProfile.QUALITY_480P);
|
||||
profile.videoFrameWidth = 640;
|
||||
break;
|
||||
}
|
||||
return profile;
|
||||
}
|
||||
|
||||
public static WritableMap getExifData(ExifInterface exifInterface) {
|
||||
WritableMap exifMap = Arguments.createMap();
|
||||
// TODO - fix this
|
||||
// for (String[] tagInfo : ImagePickerModule.exifTags) {
|
||||
// String name = tagInfo[1];
|
||||
// if (exifInterface.getAttribute(name) != null) {
|
||||
// String type = tagInfo[0];
|
||||
// switch (type) {
|
||||
// case "string":
|
||||
// exifMap.putString(name, exifInterface.getAttribute(name));
|
||||
// break;
|
||||
// case "int":
|
||||
// exifMap.putInt(name, exifInterface.getAttributeInt(name, 0));
|
||||
// break;
|
||||
// case "double":
|
||||
// exifMap.putDouble(name, exifInterface.getAttributeDouble(name, 0));
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
double[] latLong = exifInterface.getLatLong();
|
||||
if (latLong != null) {
|
||||
exifMap.putDouble(ExifInterface.TAG_GPS_LATITUDE, latLong[0]);
|
||||
exifMap.putDouble(ExifInterface.TAG_GPS_LONGITUDE, latLong[1]);
|
||||
exifMap.putDouble(ExifInterface.TAG_GPS_ALTITUDE, exifInterface.getAltitude(0));
|
||||
}
|
||||
|
||||
return exifMap;
|
||||
}
|
||||
|
||||
public static Bitmap generateSimulatorPhoto(int width, int height) {
|
||||
Bitmap fakePhoto = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
|
||||
Canvas canvas = new Canvas(fakePhoto);
|
||||
Paint background = new Paint();
|
||||
background.setColor(Color.BLACK);
|
||||
canvas.drawRect(0, 0, width, height, background);
|
||||
Paint textPaint = new Paint();
|
||||
textPaint.setColor(Color.YELLOW);
|
||||
textPaint.setTextSize(35);
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("dd.MM.YY HH:mm:ss", Locale.getDefault());
|
||||
canvas.drawText(simpleDateFormat.format(calendar.getTime()), width * 0.1f, height * 0.9f, textPaint);
|
||||
return fakePhoto;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package org.reactnative.camera.events;
|
||||
|
||||
import android.support.v4.util.Pools;
|
||||
|
||||
import org.reactnative.camera.CameraViewManager;
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.facebook.react.uimanager.events.Event;
|
||||
import com.facebook.react.uimanager.events.RCTEventEmitter;
|
||||
import com.google.zxing.Result;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
public class BarCodeReadEvent extends Event<BarCodeReadEvent> {
|
||||
private static final Pools.SynchronizedPool<BarCodeReadEvent> EVENTS_POOL =
|
||||
new Pools.SynchronizedPool<>(3);
|
||||
|
||||
private Result mBarCode;
|
||||
|
||||
private BarCodeReadEvent() {}
|
||||
|
||||
public static BarCodeReadEvent obtain(int viewTag, Result barCode) {
|
||||
BarCodeReadEvent event = EVENTS_POOL.acquire();
|
||||
if (event == null) {
|
||||
event = new BarCodeReadEvent();
|
||||
}
|
||||
event.init(viewTag);
|
||||
return event;
|
||||
}
|
||||
|
||||
private void init(int viewTag, Result barCode) {
|
||||
super.init(viewTag);
|
||||
mBarCode = barCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* We want every distinct barcode to be reported to the JS listener.
|
||||
* If we return some static value as a coalescing key there may be two barcode events
|
||||
* containing two different barcodes waiting to be transmitted to JS
|
||||
* that would get coalesced (because both of them would have the same coalescing key).
|
||||
* So let's differentiate them with a hash of the contents (mod short's max value).
|
||||
*/
|
||||
@Override
|
||||
public short getCoalescingKey() {
|
||||
int hashCode = mBarCode.getText().hashCode() % Short.MAX_VALUE;
|
||||
return (short) hashCode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getEventName() {
|
||||
return CameraViewManager.Events.EVENT_ON_BAR_CODE_READ.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispatch(RCTEventEmitter rctEventEmitter) {
|
||||
rctEventEmitter.receiveEvent(getViewTag(), getEventName(), serializeEventData());
|
||||
}
|
||||
|
||||
private WritableMap serializeEventData() {
|
||||
WritableMap event = Arguments.createMap();
|
||||
|
||||
event.putInt("target", getViewTag());
|
||||
event.putString("data", mBarCode.getText());
|
||||
event.putString("type", mBarCode.getBarcodeFormat().toString());
|
||||
|
||||
return event;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package org.reactnative.camera.events;
|
||||
|
||||
import android.support.v4.util.Pools;
|
||||
|
||||
import org.reactnative.camera.CameraViewManager;
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.facebook.react.uimanager.events.Event;
|
||||
import com.facebook.react.uimanager.events.RCTEventEmitter;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
public class CameraMountErrorEvent extends Event<CameraMountErrorEvent> {
|
||||
private static final Pools.SynchronizedPool<CameraMountErrorEvent> EVENTS_POOL = new Pools.SynchronizedPool<>(3);
|
||||
private CameraMountErrorEvent() {}
|
||||
|
||||
public static CameraMountErrorEvent obtain(int viewTag) {
|
||||
CameraMountErrorEvent event = EVENTS_POOL.acquire();
|
||||
if (event == null) {
|
||||
event = new CameraMountErrorEvent();
|
||||
}
|
||||
event.init(viewTag);
|
||||
return event;
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getCoalescingKey() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getEventName() {
|
||||
return CameraViewManager.Events.EVENT_ON_MOUNT_ERROR.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispatch(RCTEventEmitter rctEventEmitter) {
|
||||
rctEventEmitter.receiveEvent(getViewTag(), getEventName(), serializeEventData());
|
||||
}
|
||||
|
||||
private WritableMap serializeEventData() {
|
||||
return Arguments.createMap();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package org.reactnative.camera.events;
|
||||
|
||||
import android.support.v4.util.Pools;
|
||||
|
||||
import org.reactnative.camera.CameraViewManager;
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.facebook.react.uimanager.events.Event;
|
||||
import com.facebook.react.uimanager.events.RCTEventEmitter;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
public class CameraReadyEvent extends Event<CameraReadyEvent> {
|
||||
private static final Pools.SynchronizedPool<CameraReadyEvent> EVENTS_POOL = new Pools.SynchronizedPool<>(3);
|
||||
private CameraReadyEvent() {}
|
||||
|
||||
public static CameraReadyEvent obtain(int viewTag) {
|
||||
CameraReadyEvent event = EVENTS_POOL.acquire();
|
||||
if (event == null) {
|
||||
event = new CameraReadyEvent();
|
||||
}
|
||||
event.init(viewTag);
|
||||
return event;
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getCoalescingKey() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getEventName() {
|
||||
return CameraViewManager.Events.EVENT_CAMERA_READY.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispatch(RCTEventEmitter rctEventEmitter) {
|
||||
rctEventEmitter.receiveEvent(getViewTag(), getEventName(), serializeEventData());
|
||||
}
|
||||
|
||||
private WritableMap serializeEventData() {
|
||||
return Arguments.createMap();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package org.reactnative.camera.events;
|
||||
|
||||
import android.support.v4.util.Pools;
|
||||
|
||||
import org.reactnative.camera.CameraViewManager;
|
||||
import org.reactnative.facedetector.ExpoFaceDetector;
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.facebook.react.uimanager.events.Event;
|
||||
import com.facebook.react.uimanager.events.RCTEventEmitter;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
public class FaceDetectionErrorEvent extends Event<FaceDetectionErrorEvent> {
|
||||
private static final Pools.SynchronizedPool<FaceDetectionErrorEvent> EVENTS_POOL = new Pools.SynchronizedPool<>(3);
|
||||
private ExpoFaceDetector mFaceDetector;
|
||||
private FaceDetectionErrorEvent() {}
|
||||
|
||||
public static FaceDetectionErrorEvent obtain(int viewTag, ExpoFaceDetector faceDetector) {
|
||||
FaceDetectionErrorEvent event = EVENTS_POOL.acquire();
|
||||
if (event == null) {
|
||||
event = new FaceDetectionErrorEvent();
|
||||
}
|
||||
event.init(viewTag);
|
||||
return event;
|
||||
}
|
||||
|
||||
private void init(int viewTag, ExpoFaceDetector faceDetector) {
|
||||
super.init(viewTag);
|
||||
mFaceDetector = faceDetector;
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getCoalescingKey() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getEventName() {
|
||||
return CameraViewManager.Events.EVENT_ON_MOUNT_ERROR.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispatch(RCTEventEmitter rctEventEmitter) {
|
||||
rctEventEmitter.receiveEvent(getViewTag(), getEventName(), serializeEventData());
|
||||
}
|
||||
|
||||
private WritableMap serializeEventData() {
|
||||
WritableMap map = Arguments.createMap();
|
||||
map.putBoolean("isOperational", mFaceDetector.isOperational());
|
||||
return map;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
package org.reactnative.camera.events;
|
||||
|
||||
import android.support.v4.util.Pools;
|
||||
import android.util.SparseArray;
|
||||
|
||||
import org.reactnative.camera.CameraViewManager;
|
||||
import org.reactnative.camera.utils.ImageDimensions;
|
||||
import org.reactnative.facedetector.FaceDetectorUtils;
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.bridge.WritableArray;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.facebook.react.uimanager.events.Event;
|
||||
import com.facebook.react.uimanager.events.RCTEventEmitter;
|
||||
import com.google.android.cameraview.CameraView;
|
||||
import com.google.android.gms.vision.face.Face;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
public class FacesDetectedEvent extends Event<FacesDetectedEvent> {
|
||||
private static final Pools.SynchronizedPool<FacesDetectedEvent> EVENTS_POOL =
|
||||
new Pools.SynchronizedPool<>(3);
|
||||
|
||||
private double mScaleX;
|
||||
private double mScaleY;
|
||||
private SparseArray<Face> mFaces;
|
||||
private ImageDimensions mImageDimensions;
|
||||
|
||||
private FacesDetectedEvent() {}
|
||||
|
||||
public static FacesDetectedEvent obtain(
|
||||
int viewTag,
|
||||
SparseArray<Face> faces,
|
||||
ImageDimensions dimensions,
|
||||
double scaleX,
|
||||
double scaleY
|
||||
) {
|
||||
FacesDetectedEvent event = EVENTS_POOL.acquire();
|
||||
if (event == null) {
|
||||
event = new FacesDetectedEvent();
|
||||
}
|
||||
event.init(viewTag, faces, dimensions, scaleX, scaleY);
|
||||
return event;
|
||||
}
|
||||
|
||||
private void init(
|
||||
int viewTag,
|
||||
SparseArray<Face> faces,
|
||||
ImageDimensions dimensions,
|
||||
double scaleX,
|
||||
double scaleY
|
||||
) {
|
||||
super.init(viewTag);
|
||||
mFaces = faces;
|
||||
mImageDimensions = dimensions;
|
||||
mScaleX = scaleX;
|
||||
mScaleY = scaleY;
|
||||
}
|
||||
|
||||
/**
|
||||
* note(@sjchmiela)
|
||||
* Should the events about detected faces coalesce, the best strategy will be
|
||||
* to ensure that events with different faces count are always being transmitted.
|
||||
*/
|
||||
@Override
|
||||
public short getCoalescingKey() {
|
||||
if (mFaces.size() > Short.MAX_VALUE) {
|
||||
return Short.MAX_VALUE;
|
||||
}
|
||||
|
||||
return (short) mFaces.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getEventName() {
|
||||
return CameraViewManager.Events.EVENT_ON_FACES_DETECTED.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispatch(RCTEventEmitter rctEventEmitter) {
|
||||
rctEventEmitter.receiveEvent(getViewTag(), getEventName(), serializeEventData());
|
||||
}
|
||||
|
||||
private WritableMap serializeEventData() {
|
||||
WritableArray facesList = Arguments.createArray();
|
||||
|
||||
for(int i = 0; i < mFaces.size(); i++) {
|
||||
Face face = mFaces.valueAt(i);
|
||||
WritableMap serializedFace = FaceDetectorUtils.serializeFace(face, mScaleX, mScaleY);
|
||||
if (mImageDimensions.getFacing() == CameraView.FACING_FRONT) {
|
||||
serializedFace = FaceDetectorUtils.rotateFaceX(serializedFace, mImageDimensions.getWidth(), mScaleX);
|
||||
} else {
|
||||
serializedFace = FaceDetectorUtils.changeAnglesDirection(serializedFace);
|
||||
}
|
||||
facesList.pushMap(serializedFace);
|
||||
}
|
||||
|
||||
WritableMap event = Arguments.createMap();
|
||||
event.putString("type", "face");
|
||||
event.putArray("faces", facesList);
|
||||
event.putInt("target", getViewTag());
|
||||
return event;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package org.reactnative.camera.tasks;
|
||||
|
||||
import com.google.zxing.BinaryBitmap;
|
||||
import com.google.zxing.MultiFormatReader;
|
||||
import com.google.zxing.NotFoundException;
|
||||
import com.google.zxing.PlanarYUVLuminanceSource;
|
||||
import com.google.zxing.Result;
|
||||
import com.google.zxing.common.HybridBinarizer;
|
||||
|
||||
public class BarCodeScannerAsyncTask extends android.os.AsyncTask<Void, Void, Result> {
|
||||
private byte[] mImageData;
|
||||
private int mWidth;
|
||||
private int mHeight;
|
||||
private BarCodeScannerAsyncTaskDelegate mDelegate;
|
||||
private final MultiFormatReader mMultiFormatReader;
|
||||
|
||||
// note(sjchmiela): From my short research it's ok to ignore rotation of the image.
|
||||
public BarCodeScannerAsyncTask(
|
||||
BarCodeScannerAsyncTaskDelegate delegate,
|
||||
MultiFormatReader multiFormatReader,
|
||||
byte[] imageData,
|
||||
int width,
|
||||
int height
|
||||
) {
|
||||
mImageData = imageData;
|
||||
mWidth = width;
|
||||
mHeight = height;
|
||||
mDelegate = delegate;
|
||||
mMultiFormatReader = multiFormatReader;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Result doInBackground(Void... ignored) {
|
||||
if (isCancelled() || mDelegate == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Result result = null;
|
||||
|
||||
try {
|
||||
BinaryBitmap bitmap = generateBitmapFromImageData(mImageData, mWidth, mHeight);
|
||||
result = mMultiFormatReader.decodeWithState(bitmap);
|
||||
} catch (NotFoundException e) {
|
||||
// No barcode found, result is already null.
|
||||
} catch (Throwable t) {
|
||||
t.printStackTrace();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Result result) {
|
||||
super.onPostExecute(result);
|
||||
if (result != null) {
|
||||
mDelegate.onBarCodeRead(result);
|
||||
}
|
||||
mDelegate.onBarCodeScanningTaskCompleted();
|
||||
}
|
||||
|
||||
private BinaryBitmap generateBitmapFromImageData(byte[] imageData, int width, int height) {
|
||||
PlanarYUVLuminanceSource source = new PlanarYUVLuminanceSource(
|
||||
imageData, // byte[] yuvData
|
||||
width, // int dataWidth
|
||||
height, // int dataHeight
|
||||
0, // int left
|
||||
0, // int top
|
||||
width, // int width
|
||||
height, // int height
|
||||
false // boolean reverseHorizontal
|
||||
);
|
||||
return new BinaryBitmap(new HybridBinarizer(source));
|
||||
}
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
package org.reactnative.camera.tasks;
|
||||
|
||||
import com.google.zxing.Result;
|
||||
|
||||
public interface BarCodeScannerAsyncTaskDelegate {
|
||||
void onBarCodeRead(Result barCode);
|
||||
void onBarCodeScanningTaskCompleted();
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package org.reactnative.camera.tasks;
|
||||
|
||||
import android.util.SparseArray;
|
||||
|
||||
import org.reactnative.facedetector.ExpoFaceDetector;
|
||||
import org.reactnative.facedetector.ExpoFrame;
|
||||
import org.reactnative.facedetector.ExpoFrameFactory;
|
||||
import com.google.android.gms.vision.face.Face;
|
||||
|
||||
public class FaceDetectorAsyncTask extends android.os.AsyncTask<Void, Void, SparseArray<Face>> {
|
||||
private byte[] mImageData;
|
||||
private int mWidth;
|
||||
private int mHeight;
|
||||
private int mRotation;
|
||||
private ExpoFaceDetector mFaceDetector;
|
||||
private FaceDetectorAsyncTaskDelegate mDelegate;
|
||||
|
||||
public FaceDetectorAsyncTask(
|
||||
FaceDetectorAsyncTaskDelegate delegate,
|
||||
ExpoFaceDetector faceDetector,
|
||||
byte[] imageData,
|
||||
int width,
|
||||
int height,
|
||||
int rotation
|
||||
) {
|
||||
mImageData = imageData;
|
||||
mWidth = width;
|
||||
mHeight = height;
|
||||
mRotation = rotation;
|
||||
mDelegate = delegate;
|
||||
mFaceDetector = faceDetector;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SparseArray<Face> doInBackground(Void... ignored) {
|
||||
if (isCancelled() || mDelegate == null || mFaceDetector == null || !mFaceDetector.isOperational()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
ExpoFrame frame = ExpoFrameFactory.buildFrame(mImageData, mWidth, mHeight, mRotation);
|
||||
return mFaceDetector.detect(frame);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(SparseArray<Face> faces) {
|
||||
super.onPostExecute(faces);
|
||||
|
||||
if (faces == null) {
|
||||
mDelegate.onFaceDetectionError(mFaceDetector);
|
||||
} else {
|
||||
mDelegate.onFacesDetected(faces, mWidth, mHeight, mRotation);
|
||||
mDelegate.onFaceDetectingTaskCompleted();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package org.reactnative.camera.tasks;
|
||||
|
||||
import android.util.SparseArray;
|
||||
|
||||
import org.reactnative.facedetector.ExpoFaceDetector;
|
||||
import com.google.android.gms.vision.face.Face;
|
||||
|
||||
public interface FaceDetectorAsyncTaskDelegate {
|
||||
void onFacesDetected(SparseArray<Face> face, int sourceWidth, int sourceHeight, int sourceRotation);
|
||||
void onFaceDetectionError(ExpoFaceDetector faceDetector);
|
||||
void onFaceDetectingTaskCompleted();
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package org.reactnative.camera.tasks;
|
||||
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Matrix;
|
||||
import android.os.AsyncTask;
|
||||
|
||||
import org.reactnative.MutableImage;
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.bridge.Promise;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
public class ResolveTakenPictureAsyncTask extends AsyncTask<Void, Void, WritableMap> {
|
||||
private static final String ERROR_TAG = "E_TAKING_PICTURE_FAILED";
|
||||
private Promise mPromise;
|
||||
private byte[] mImageData;
|
||||
private ReadableMap mOptions;
|
||||
|
||||
public ResolveTakenPictureAsyncTask(byte[] imageData, Promise promise, ReadableMap options) {
|
||||
mPromise = promise;
|
||||
mOptions = options;
|
||||
mImageData = imageData;
|
||||
}
|
||||
|
||||
private int getQuality() {
|
||||
return (int) (mOptions.getDouble("quality") * 100);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected WritableMap doInBackground(Void... voids) {
|
||||
WritableMap response = Arguments.createMap();
|
||||
ByteArrayInputStream inputStream = new ByteArrayInputStream(mImageData);
|
||||
|
||||
try {
|
||||
MutableImage mutableImage = new MutableImage(mImageData);
|
||||
mutableImage.mirrorImage();
|
||||
mutableImage.fixOrientation();
|
||||
String encoded = mutableImage.toBase64(getQuality());
|
||||
|
||||
response.putString("base64", encoded);
|
||||
|
||||
return response;
|
||||
} catch (Resources.NotFoundException e) {
|
||||
mPromise.reject(ERROR_TAG, "Documents directory of the app could not be found.", e);
|
||||
e.printStackTrace();
|
||||
} catch (MutableImage.ImageMutationFailedException e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
try {
|
||||
if (inputStream != null) {
|
||||
inputStream.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
// An exception had to occur, promise has already been rejected. Do not try to resolve it again.
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(WritableMap response) {
|
||||
super.onPostExecute(response);
|
||||
|
||||
// If the response is not null everything went well and we can resolve the promise.
|
||||
if (response != null) {
|
||||
mPromise.resolve(response);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package org.reactnative.camera.utils;
|
||||
|
||||
public class ImageDimensions {
|
||||
private int mWidth;
|
||||
private int mHeight;
|
||||
private int mFacing;
|
||||
private int mRotation;
|
||||
|
||||
public ImageDimensions(int width, int height) {
|
||||
this(width, height, 0);
|
||||
}
|
||||
|
||||
public ImageDimensions(int width, int height, int rotation) {
|
||||
this(width, height, rotation, -1);
|
||||
}
|
||||
|
||||
public ImageDimensions(int width, int height, int rotation, int facing) {
|
||||
mWidth = width;
|
||||
mHeight = height;
|
||||
mFacing = facing;
|
||||
mRotation = rotation;
|
||||
}
|
||||
|
||||
public boolean isLandscape() {
|
||||
return mRotation % 180 == 90;
|
||||
}
|
||||
|
||||
public int getWidth() {
|
||||
if (isLandscape()) {
|
||||
return mHeight;
|
||||
}
|
||||
|
||||
return mWidth;
|
||||
}
|
||||
|
||||
public int getHeight() {
|
||||
if (isLandscape()) {
|
||||
return mWidth;
|
||||
}
|
||||
|
||||
return mHeight;
|
||||
}
|
||||
|
||||
public int getRotation() {
|
||||
return mRotation;
|
||||
}
|
||||
|
||||
public int getFacing() {
|
||||
return mFacing;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj instanceof ImageDimensions) {
|
||||
ImageDimensions otherDimensions = (ImageDimensions) obj;
|
||||
return (otherDimensions.getWidth() == getWidth() &&
|
||||
otherDimensions.getHeight() == getHeight() &&
|
||||
otherDimensions.getFacing() == getFacing() &&
|
||||
otherDimensions.getRotation() == getRotation());
|
||||
} else {
|
||||
return super.equals(obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
package org.reactnative.facedetector;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
import android.util.SparseArray;
|
||||
|
||||
import org.reactnative.camera.utils.ImageDimensions;
|
||||
import com.google.android.gms.vision.face.Face;
|
||||
import com.google.android.gms.vision.face.FaceDetector;
|
||||
|
||||
public class ExpoFaceDetector {
|
||||
public static int ALL_CLASSIFICATIONS = FaceDetector.ALL_CLASSIFICATIONS;
|
||||
public static int NO_CLASSIFICATIONS = FaceDetector.NO_CLASSIFICATIONS;
|
||||
public static int ALL_LANDMARKS = FaceDetector.ALL_LANDMARKS;
|
||||
public static int NO_LANDMARKS = FaceDetector.NO_LANDMARKS;
|
||||
public static int ACCURATE_MODE = FaceDetector.ACCURATE_MODE;
|
||||
public static int FAST_MODE = FaceDetector.FAST_MODE;
|
||||
|
||||
private FaceDetector mFaceDetector = null;
|
||||
private ImageDimensions mPreviousDimensions;
|
||||
private FaceDetector.Builder mBuilder = null;
|
||||
|
||||
private int mClassificationType = NO_CLASSIFICATIONS;
|
||||
private int mLandmarkType = NO_LANDMARKS;
|
||||
private float mMinFaceSize = 0.15f;
|
||||
private int mMode = FAST_MODE;
|
||||
|
||||
public ExpoFaceDetector(Context context) {
|
||||
mBuilder = new FaceDetector.Builder(context);
|
||||
mBuilder.setMinFaceSize(mMinFaceSize);
|
||||
mBuilder.setMode(mMode);
|
||||
mBuilder.setLandmarkType(mLandmarkType);
|
||||
mBuilder.setClassificationType(mClassificationType);
|
||||
}
|
||||
|
||||
// Public API
|
||||
|
||||
public boolean isOperational() {
|
||||
if (mFaceDetector == null) {
|
||||
createFaceDetector();
|
||||
}
|
||||
|
||||
return mFaceDetector.isOperational();
|
||||
}
|
||||
|
||||
public SparseArray<Face> detect(ExpoFrame frame) {
|
||||
// If the frame has different dimensions, create another face detector.
|
||||
// Otherwise we will get nasty "inconsistent image dimensions" error from detector
|
||||
// and no face will be detected.
|
||||
if (!frame.getDimensions().equals(mPreviousDimensions)) {
|
||||
releaseFaceDetector();
|
||||
}
|
||||
|
||||
if (mFaceDetector == null) {
|
||||
createFaceDetector();
|
||||
mPreviousDimensions = frame.getDimensions();
|
||||
}
|
||||
|
||||
return mFaceDetector.detect(frame.getFrame());
|
||||
}
|
||||
|
||||
public void setTracking(boolean trackingEnabled) {
|
||||
release();
|
||||
mBuilder.setTrackingEnabled(trackingEnabled);
|
||||
}
|
||||
|
||||
public void setClassificationType(int classificationType) {
|
||||
if (classificationType != mClassificationType) {
|
||||
release();
|
||||
mBuilder.setClassificationType(classificationType);
|
||||
mClassificationType = classificationType;
|
||||
}
|
||||
}
|
||||
|
||||
public void setLandmarkType(int landmarkType) {
|
||||
if (landmarkType != mLandmarkType) {
|
||||
release();
|
||||
mBuilder.setLandmarkType(landmarkType);
|
||||
mLandmarkType = landmarkType;
|
||||
}
|
||||
}
|
||||
|
||||
public void setMode(int mode) {
|
||||
if (mode != mMode) {
|
||||
release();
|
||||
mBuilder.setMode(mode);
|
||||
mMode = mode;
|
||||
}
|
||||
}
|
||||
|
||||
public void setTrackingEnabled(boolean tracking) {
|
||||
release();
|
||||
mBuilder.setTrackingEnabled(tracking);
|
||||
}
|
||||
|
||||
public void release() {
|
||||
releaseFaceDetector();
|
||||
mPreviousDimensions = null;
|
||||
}
|
||||
|
||||
// Lifecycle methods
|
||||
|
||||
private void releaseFaceDetector() {
|
||||
if (mFaceDetector != null) {
|
||||
mFaceDetector.release();
|
||||
mFaceDetector = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void createFaceDetector() {
|
||||
mFaceDetector = mBuilder.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package org.reactnative.facedetector;
|
||||
|
||||
import org.reactnative.camera.utils.ImageDimensions;
|
||||
import com.google.android.gms.vision.Frame;
|
||||
|
||||
/**
|
||||
* Wrapper around Frame allowing us to track Frame dimensions.
|
||||
* Tracking dimensions is used in ExpoFaceDetector to provide painless FaceDetector recreation
|
||||
* when image dimensions change.
|
||||
*/
|
||||
|
||||
public class ExpoFrame {
|
||||
private Frame mFrame;
|
||||
private ImageDimensions mDimensions;
|
||||
|
||||
public ExpoFrame(Frame frame, ImageDimensions dimensions) {
|
||||
mFrame = frame;
|
||||
mDimensions = dimensions;
|
||||
}
|
||||
|
||||
public Frame getFrame() {
|
||||
return mFrame;
|
||||
}
|
||||
|
||||
public ImageDimensions getDimensions() {
|
||||
return mDimensions;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package org.reactnative.facedetector;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.ImageFormat;
|
||||
|
||||
import org.reactnative.camera.utils.ImageDimensions;
|
||||
import com.google.android.gms.vision.Frame;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class ExpoFrameFactory {
|
||||
public static ExpoFrame buildFrame(byte[] bitmapData, int width, int height, int rotation) {
|
||||
Frame.Builder builder = new Frame.Builder();
|
||||
|
||||
ByteBuffer byteBuffer = ByteBuffer.wrap(bitmapData);
|
||||
builder.setImageData(byteBuffer, width, height, ImageFormat.NV21);
|
||||
|
||||
switch (rotation) {
|
||||
case 90:
|
||||
builder.setRotation(Frame.ROTATION_90);
|
||||
break;
|
||||
case 180:
|
||||
builder.setRotation(Frame.ROTATION_180);
|
||||
break;
|
||||
case 270:
|
||||
builder.setRotation(Frame.ROTATION_270);
|
||||
break;
|
||||
default:
|
||||
builder.setRotation(Frame.ROTATION_0);
|
||||
}
|
||||
|
||||
ImageDimensions dimensions = new ImageDimensions(width, height, rotation);
|
||||
|
||||
return new ExpoFrame(builder.build(), dimensions);
|
||||
}
|
||||
|
||||
public static ExpoFrame buildFrame(Bitmap bitmap) {
|
||||
Frame.Builder builder = new Frame.Builder();
|
||||
builder.setBitmap(bitmap);
|
||||
ImageDimensions dimensions = new ImageDimensions(bitmap.getWidth(), bitmap.getHeight());
|
||||
return new ExpoFrame(builder.build(), dimensions);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package org.reactnative.facedetector;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import org.reactnative.facedetector.tasks.FileFaceDetectionAsyncTask;
|
||||
import com.facebook.react.bridge.Promise;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||
import com.facebook.react.bridge.ReactMethod;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public class FaceDetectorModule extends ReactContextBaseJavaModule {
|
||||
private static final String TAG = "ExpoFaceDetector";
|
||||
// private ScopedContext mScopedContext;
|
||||
private static ReactApplicationContext mScopedContext;
|
||||
|
||||
public FaceDetectorModule(ReactApplicationContext reactContext) {
|
||||
super(reactContext);
|
||||
mScopedContext = reactContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return TAG;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Map<String, Object> getConstants() {
|
||||
return Collections.unmodifiableMap(new HashMap<String, Object>() {
|
||||
{
|
||||
put("Mode", getFaceDetectionModeConstants());
|
||||
put("Landmarks", getFaceDetectionLandmarksConstants());
|
||||
put("Classifications", getFaceDetectionClassificationsConstants());
|
||||
}
|
||||
|
||||
private Map<String, Object> getFaceDetectionModeConstants() {
|
||||
return Collections.unmodifiableMap(new HashMap<String, Object>() {
|
||||
{
|
||||
put("fast", ExpoFaceDetector.FAST_MODE);
|
||||
put("accurate", ExpoFaceDetector.ACCURATE_MODE);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private Map<String, Object> getFaceDetectionClassificationsConstants() {
|
||||
return Collections.unmodifiableMap(new HashMap<String, Object>() {
|
||||
{
|
||||
put("all", ExpoFaceDetector.ALL_CLASSIFICATIONS);
|
||||
put("none", ExpoFaceDetector.NO_CLASSIFICATIONS);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private Map<String, Object> getFaceDetectionLandmarksConstants() {
|
||||
return Collections.unmodifiableMap(new HashMap<String, Object>() {
|
||||
{
|
||||
put("all", ExpoFaceDetector.ALL_LANDMARKS);
|
||||
put("none", ExpoFaceDetector.NO_LANDMARKS);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void detectFaces(ReadableMap options, final Promise promise) {
|
||||
new FileFaceDetectionAsyncTask(mScopedContext, options, promise).execute();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
package org.reactnative.facedetector;
|
||||
|
||||
import android.graphics.PointF;
|
||||
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.google.android.gms.vision.face.Face;
|
||||
import com.google.android.gms.vision.face.Landmark;
|
||||
|
||||
public class FaceDetectorUtils {
|
||||
// All the landmarks reported by Google Mobile Vision in constants' order.
|
||||
// https://developers.google.com/android/reference/com/google/android/gms/vision/face/Landmark
|
||||
private static final String[] landmarkNames = {
|
||||
"bottomMouthPosition", "leftCheekPosition", "leftEarPosition", "leftEarTipPosition",
|
||||
"leftEyePosition", "leftMouthPosition", "noseBasePosition", "rightCheekPosition",
|
||||
"rightEarPosition", "rightEarTipPosition", "rightEyePosition", "rightMouthPosition"
|
||||
};
|
||||
|
||||
public static WritableMap serializeFace(Face face) {
|
||||
return serializeFace(face, 1, 1);
|
||||
}
|
||||
|
||||
public static WritableMap serializeFace(Face face, double scaleX, double scaleY) {
|
||||
WritableMap encodedFace = Arguments.createMap();
|
||||
|
||||
encodedFace.putInt("faceID", face.getId());
|
||||
encodedFace.putDouble("rollAngle", face.getEulerZ());
|
||||
encodedFace.putDouble("yawAngle", face.getEulerY());
|
||||
|
||||
if (face.getIsSmilingProbability() >= 0) {
|
||||
encodedFace.putDouble("smilingProbability", face.getIsSmilingProbability());
|
||||
}
|
||||
if (face.getIsLeftEyeOpenProbability() >= 0) {
|
||||
encodedFace.putDouble("leftEyeOpenProbability", face.getIsLeftEyeOpenProbability());
|
||||
}
|
||||
if (face.getIsRightEyeOpenProbability() >= 0) {
|
||||
encodedFace.putDouble("rightEyeOpenProbability", face.getIsRightEyeOpenProbability());
|
||||
}
|
||||
|
||||
for(Landmark landmark : face.getLandmarks()) {
|
||||
encodedFace.putMap(landmarkNames[landmark.getType()], mapFromPoint(landmark.getPosition(), scaleX, scaleY));
|
||||
}
|
||||
|
||||
WritableMap origin = Arguments.createMap();
|
||||
origin.putDouble("x", face.getPosition().x * scaleX);
|
||||
origin.putDouble("y", face.getPosition().y * scaleY);
|
||||
|
||||
WritableMap size = Arguments.createMap();
|
||||
size.putDouble("width", face.getWidth() * scaleX);
|
||||
size.putDouble("height", face.getHeight() * scaleY);
|
||||
|
||||
WritableMap bounds = Arguments.createMap();
|
||||
bounds.putMap("origin", origin);
|
||||
bounds.putMap("size", size);
|
||||
|
||||
encodedFace.putMap("bounds", bounds);
|
||||
|
||||
return encodedFace;
|
||||
}
|
||||
|
||||
public static WritableMap rotateFaceX(WritableMap face, int sourceWidth, double scaleX) {
|
||||
ReadableMap faceBounds = face.getMap("bounds");
|
||||
|
||||
ReadableMap oldOrigin = faceBounds.getMap("origin");
|
||||
WritableMap mirroredOrigin = positionMirroredHorizontally(oldOrigin, sourceWidth, scaleX);
|
||||
|
||||
double translateX = -faceBounds.getMap("size").getDouble("width");
|
||||
WritableMap translatedMirroredOrigin = positionTranslatedHorizontally(mirroredOrigin, translateX);
|
||||
|
||||
WritableMap newBounds = Arguments.createMap();
|
||||
newBounds.merge(faceBounds);
|
||||
newBounds.putMap("origin", translatedMirroredOrigin);
|
||||
|
||||
for (String landmarkName : landmarkNames) {
|
||||
ReadableMap landmark = face.hasKey(landmarkName) ? face.getMap(landmarkName) : null;
|
||||
if (landmark != null) {
|
||||
WritableMap mirroredPosition = positionMirroredHorizontally(landmark, sourceWidth, scaleX);
|
||||
face.putMap(landmarkName, mirroredPosition);
|
||||
}
|
||||
}
|
||||
|
||||
face.putMap("bounds", newBounds);
|
||||
|
||||
return face;
|
||||
}
|
||||
|
||||
public static WritableMap changeAnglesDirection(WritableMap face) {
|
||||
face.putDouble("rollAngle", (-face.getDouble("rollAngle") + 360) % 360);
|
||||
face.putDouble("yawAngle", (-face.getDouble("yawAngle") + 360) % 360);
|
||||
return face;
|
||||
}
|
||||
|
||||
public static WritableMap mapFromPoint(PointF point, double scaleX, double scaleY) {
|
||||
WritableMap map = Arguments.createMap();
|
||||
map.putDouble("x", point.x * scaleX);
|
||||
map.putDouble("y", point.y * scaleY);
|
||||
return map;
|
||||
}
|
||||
|
||||
public static WritableMap positionTranslatedHorizontally(ReadableMap position, double translateX) {
|
||||
WritableMap newPosition = Arguments.createMap();
|
||||
newPosition.merge(position);
|
||||
newPosition.putDouble("x", position.getDouble("x") + translateX);
|
||||
return newPosition;
|
||||
}
|
||||
|
||||
public static WritableMap positionMirroredHorizontally(ReadableMap position, int containerWidth, double scaleX) {
|
||||
WritableMap newPosition = Arguments.createMap();
|
||||
newPosition.merge(position);
|
||||
newPosition.putDouble("x", valueMirroredHorizontally(position.getDouble("x"), containerWidth, scaleX));
|
||||
return newPosition;
|
||||
}
|
||||
|
||||
public static double valueMirroredHorizontally(double elementX, int containerWidth, double scaleX) {
|
||||
double originalX = elementX / scaleX;
|
||||
double mirroredX = containerWidth - originalX;
|
||||
return mirroredX * scaleX;
|
||||
}
|
||||
}
|
||||
+153
@@ -0,0 +1,153 @@
|
||||
package org.reactnative.facedetector.tasks;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.media.ExifInterface;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.util.Log;
|
||||
import android.util.SparseArray;
|
||||
|
||||
import org.reactnative.facedetector.ExpoFaceDetector;
|
||||
import org.reactnative.facedetector.ExpoFrame;
|
||||
import org.reactnative.facedetector.ExpoFrameFactory;
|
||||
import org.reactnative.facedetector.FaceDetectorUtils;
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.bridge.Promise;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.facebook.react.bridge.WritableArray;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.google.android.gms.vision.Frame;
|
||||
import com.google.android.gms.vision.face.Face;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLDecoder;
|
||||
|
||||
public class FileFaceDetectionAsyncTask extends AsyncTask<Void, Void, SparseArray<Face>> {
|
||||
private static final String ERROR_TAG = "E_FACE_DETECTION_FAILED";
|
||||
|
||||
private static final String MODE_OPTION_KEY = "mode";
|
||||
private static final String DETECT_LANDMARKS_OPTION_KEY = "detectLandmarks";
|
||||
private static final String RUN_CLASSIFICATIONS_OPTION_KEY = "runClassifications";
|
||||
|
||||
private String mUri;
|
||||
private String mPath;
|
||||
private Promise mPromise;
|
||||
private int mWidth = 0;
|
||||
private int mHeight = 0;
|
||||
private Context mContext;
|
||||
private ReadableMap mOptions;
|
||||
private int mOrientation = ExifInterface.ORIENTATION_UNDEFINED;
|
||||
private ExpoFaceDetector mExpoFaceDetector;
|
||||
|
||||
public FileFaceDetectionAsyncTask(Context context, ReadableMap options, Promise promise) {
|
||||
mUri = options.getString("uri");
|
||||
mPromise = promise;
|
||||
mOptions = options;
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
if (mUri == null) {
|
||||
mPromise.reject(ERROR_TAG, "You have to provide an URI of an image.");
|
||||
cancel(true);
|
||||
return;
|
||||
}
|
||||
|
||||
Uri uri = Uri.parse(mUri);
|
||||
mPath = uri.getPath();
|
||||
|
||||
if (mPath == null) {
|
||||
mPromise.reject(ERROR_TAG, "Invalid URI provided: `" + mUri + "`.");
|
||||
cancel(true);
|
||||
return;
|
||||
}
|
||||
|
||||
// We have to check if the requested image is in a directory safely accessible by our app.
|
||||
boolean fileIsInSafeDirectories =
|
||||
mPath.startsWith(mContext.getCacheDir().getPath()) || mPath.startsWith(mContext.getFilesDir().getPath());
|
||||
|
||||
if (!fileIsInSafeDirectories) {
|
||||
mPromise.reject(ERROR_TAG, "The image has to be in the local app's directories.");
|
||||
cancel(true);
|
||||
return;
|
||||
}
|
||||
|
||||
if(!new File(mPath).exists()) {
|
||||
mPromise.reject(ERROR_TAG, "The file does not exist. Given path: `" + mPath + "`.");
|
||||
cancel(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SparseArray<Face> doInBackground(Void... voids) {
|
||||
if (isCancelled()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
mExpoFaceDetector = detectorForOptions(mOptions, mContext);
|
||||
Bitmap bitmap = BitmapFactory.decodeFile(mPath);
|
||||
mWidth = bitmap.getWidth();
|
||||
mHeight = bitmap.getHeight();
|
||||
|
||||
try {
|
||||
ExifInterface exif = new ExifInterface(mPath);
|
||||
mOrientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_UNDEFINED);
|
||||
} catch (IOException e) {
|
||||
Log.e(ERROR_TAG, "Reading orientation from file `" + mPath + "` failed.", e);
|
||||
}
|
||||
|
||||
ExpoFrame frame = ExpoFrameFactory.buildFrame(bitmap);
|
||||
return mExpoFaceDetector.detect(frame);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(SparseArray<Face> faces) {
|
||||
super.onPostExecute(faces);
|
||||
WritableMap result = Arguments.createMap();
|
||||
WritableArray facesArray = Arguments.createArray();
|
||||
|
||||
for(int i = 0; i < faces.size(); i++) {
|
||||
Face face = faces.valueAt(i);
|
||||
WritableMap encodedFace = FaceDetectorUtils.serializeFace(face);
|
||||
encodedFace.putDouble("yawAngle", (-encodedFace.getDouble("yawAngle") + 360) % 360);
|
||||
encodedFace.putDouble("rollAngle", (-encodedFace.getDouble("rollAngle") + 360) % 360);
|
||||
facesArray.pushMap(encodedFace);
|
||||
}
|
||||
|
||||
result.putArray("faces", facesArray);
|
||||
|
||||
WritableMap image = Arguments.createMap();
|
||||
image.putInt("width", mWidth);
|
||||
image.putInt("height", mHeight);
|
||||
image.putInt("orientation", mOrientation);
|
||||
image.putString("uri", mUri);
|
||||
result.putMap("image", image);
|
||||
|
||||
mExpoFaceDetector.release();
|
||||
mPromise.resolve(result);
|
||||
}
|
||||
|
||||
private static ExpoFaceDetector detectorForOptions(ReadableMap options, Context context) {
|
||||
ExpoFaceDetector detector = new ExpoFaceDetector(context);
|
||||
detector.setTrackingEnabled(false);
|
||||
|
||||
if(options.hasKey(MODE_OPTION_KEY)) {
|
||||
detector.setMode(options.getInt(MODE_OPTION_KEY));
|
||||
}
|
||||
|
||||
if(options.hasKey(RUN_CLASSIFICATIONS_OPTION_KEY)) {
|
||||
detector.setClassificationType(options.getInt(RUN_CLASSIFICATIONS_OPTION_KEY));
|
||||
}
|
||||
|
||||
if(options.hasKey(DETECT_LANDMARKS_OPTION_KEY)) {
|
||||
detector.setLandmarkType(options.getInt(DETECT_LANDMARKS_OPTION_KEY));
|
||||
}
|
||||
|
||||
return detector;
|
||||
}
|
||||
}
|
||||
+280
@@ -0,0 +1,280 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
DeviceEventEmitter, // android
|
||||
NativeAppEventEmitter, // ios
|
||||
NativeModules,
|
||||
Platform,
|
||||
StyleSheet,
|
||||
requireNativeComponent,
|
||||
ViewPropTypes,
|
||||
} from 'react-native';
|
||||
|
||||
const CameraManager = NativeModules.CameraManager || NativeModules.CameraModule;
|
||||
const CAMERA_REF = 'camera';
|
||||
|
||||
function convertNativeProps(props) {
|
||||
const newProps = { ...props };
|
||||
if (typeof props.aspect === 'string') {
|
||||
newProps.aspect = Camera.constants.Aspect[props.aspect];
|
||||
}
|
||||
|
||||
if (typeof props.flashMode === 'string') {
|
||||
newProps.flashMode = Camera.constants.FlashMode[props.flashMode];
|
||||
}
|
||||
|
||||
if (typeof props.orientation === 'string') {
|
||||
newProps.orientation = Camera.constants.Orientation[props.orientation];
|
||||
}
|
||||
|
||||
if (typeof props.torchMode === 'string') {
|
||||
newProps.torchMode = Camera.constants.TorchMode[props.torchMode];
|
||||
}
|
||||
|
||||
if (typeof props.type === 'string') {
|
||||
newProps.type = Camera.constants.Type[props.type];
|
||||
}
|
||||
|
||||
if (typeof props.captureQuality === 'string') {
|
||||
newProps.captureQuality = Camera.constants.CaptureQuality[props.captureQuality];
|
||||
}
|
||||
|
||||
if (typeof props.captureMode === 'string') {
|
||||
newProps.captureMode = Camera.constants.CaptureMode[props.captureMode];
|
||||
}
|
||||
|
||||
if (typeof props.captureTarget === 'string') {
|
||||
newProps.captureTarget = Camera.constants.CaptureTarget[props.captureTarget];
|
||||
}
|
||||
|
||||
// do not register barCodeTypes if no barcode listener
|
||||
if (typeof props.onBarCodeRead !== 'function') {
|
||||
newProps.barCodeTypes = [];
|
||||
}
|
||||
|
||||
newProps.barcodeScannerEnabled = typeof props.onBarCodeRead === 'function';
|
||||
|
||||
return newProps;
|
||||
}
|
||||
|
||||
export default class Camera extends Component {
|
||||
static constants = {
|
||||
Aspect: CameraManager.Aspect,
|
||||
BarCodeType: CameraManager.BarCodeType,
|
||||
Type: CameraManager.Type,
|
||||
CaptureMode: CameraManager.CaptureMode,
|
||||
CaptureTarget: CameraManager.CaptureTarget,
|
||||
CaptureQuality: CameraManager.CaptureQuality,
|
||||
Orientation: CameraManager.Orientation,
|
||||
FlashMode: CameraManager.FlashMode,
|
||||
TorchMode: CameraManager.TorchMode,
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
...ViewPropTypes,
|
||||
aspect: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
captureAudio: PropTypes.bool,
|
||||
captureMode: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
captureQuality: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
captureTarget: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
defaultOnFocusComponent: PropTypes.bool,
|
||||
flashMode: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
keepAwake: PropTypes.bool,
|
||||
onBarCodeRead: PropTypes.func,
|
||||
barcodeScannerEnabled: PropTypes.bool,
|
||||
onFocusChanged: PropTypes.func,
|
||||
onZoomChanged: PropTypes.func,
|
||||
mirrorImage: PropTypes.bool,
|
||||
fixOrientation: PropTypes.bool,
|
||||
barCodeTypes: PropTypes.array,
|
||||
orientation: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
playSoundOnCapture: PropTypes.bool,
|
||||
torchMode: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
type: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
aspect: CameraManager.Aspect.fill,
|
||||
type: CameraManager.Type.back,
|
||||
orientation: CameraManager.Orientation.auto,
|
||||
fixOrientation: false,
|
||||
captureAudio: false,
|
||||
captureMode: CameraManager.CaptureMode.still,
|
||||
captureTarget: CameraManager.CaptureTarget.cameraRoll,
|
||||
captureQuality: CameraManager.CaptureQuality.high,
|
||||
defaultOnFocusComponent: true,
|
||||
flashMode: CameraManager.FlashMode.off,
|
||||
playSoundOnCapture: true,
|
||||
torchMode: CameraManager.TorchMode.off,
|
||||
mirrorImage: false,
|
||||
barCodeTypes: Object.values(CameraManager.BarCodeType),
|
||||
};
|
||||
|
||||
static checkDeviceAuthorizationStatus = CameraManager.checkDeviceAuthorizationStatus;
|
||||
static checkVideoAuthorizationStatus = CameraManager.checkVideoAuthorizationStatus;
|
||||
static checkAudioAuthorizationStatus = CameraManager.checkAudioAuthorizationStatus;
|
||||
|
||||
setNativeProps(props) {
|
||||
// eslint-disable-next-line
|
||||
this.refs[CAMERA_REF].setNativeProps(props);
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
isAuthorized: false,
|
||||
isRecording: false,
|
||||
};
|
||||
}
|
||||
|
||||
async componentWillMount() {
|
||||
this._addOnBarCodeReadListener();
|
||||
|
||||
let { captureMode } = convertNativeProps({ captureMode: this.props.captureMode });
|
||||
let hasVideoAndAudio =
|
||||
this.props.captureAudio && captureMode === Camera.constants.CaptureMode.video;
|
||||
let check = hasVideoAndAudio
|
||||
? Camera.checkDeviceAuthorizationStatus
|
||||
: Camera.checkVideoAuthorizationStatus;
|
||||
|
||||
if (check) {
|
||||
const isAuthorized = await check();
|
||||
this.setState({ isAuthorized });
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this._removeOnBarCodeReadListener();
|
||||
|
||||
if (this.state.isRecording) {
|
||||
this.stopCapture();
|
||||
}
|
||||
}
|
||||
|
||||
componentWillReceiveProps(newProps) {
|
||||
const { onBarCodeRead } = this.props;
|
||||
if (onBarCodeRead !== newProps.onBarCodeRead) {
|
||||
this._addOnBarCodeReadListener(newProps);
|
||||
}
|
||||
}
|
||||
|
||||
_addOnBarCodeReadListener(props) {
|
||||
const { onBarCodeRead } = props || this.props;
|
||||
this._removeOnBarCodeReadListener();
|
||||
if (onBarCodeRead) {
|
||||
this.cameraBarCodeReadListener = Platform.select({
|
||||
ios: NativeAppEventEmitter.addListener('CameraBarCodeRead', this._onBarCodeRead),
|
||||
android: DeviceEventEmitter.addListener('CameraBarCodeReadAndroid', this._onBarCodeRead),
|
||||
});
|
||||
}
|
||||
}
|
||||
_removeOnBarCodeReadListener() {
|
||||
const listener = this.cameraBarCodeReadListener;
|
||||
if (listener) {
|
||||
listener.remove();
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
// TODO - style is not used, figure it out why
|
||||
// eslint-disable-next-line
|
||||
const style = [styles.base, this.props.style];
|
||||
const nativeProps = convertNativeProps(this.props);
|
||||
|
||||
return <RCTCamera ref={CAMERA_REF} {...nativeProps} />;
|
||||
}
|
||||
|
||||
_onBarCodeRead = data => {
|
||||
if (this.props.onBarCodeRead) {
|
||||
this.props.onBarCodeRead(data);
|
||||
}
|
||||
};
|
||||
|
||||
capture(options) {
|
||||
const props = convertNativeProps(this.props);
|
||||
options = {
|
||||
audio: props.captureAudio,
|
||||
barCodeTypes: props.barCodeTypes,
|
||||
mode: props.captureMode,
|
||||
playSoundOnCapture: props.playSoundOnCapture,
|
||||
target: props.captureTarget,
|
||||
quality: props.captureQuality,
|
||||
type: props.type,
|
||||
title: '',
|
||||
description: '',
|
||||
mirrorImage: props.mirrorImage,
|
||||
fixOrientation: props.fixOrientation,
|
||||
...options,
|
||||
};
|
||||
|
||||
if (options.mode === Camera.constants.CaptureMode.video) {
|
||||
options.totalSeconds = options.totalSeconds > -1 ? options.totalSeconds : -1;
|
||||
options.preferredTimeScale = options.preferredTimeScale || 30;
|
||||
this.setState({ isRecording: true });
|
||||
}
|
||||
|
||||
return CameraManager.capture(options);
|
||||
}
|
||||
|
||||
startPreview() {
|
||||
if (Platform.OS === 'android') {
|
||||
const props = convertNativeProps(this.props);
|
||||
CameraManager.startPreview({
|
||||
type: props.type,
|
||||
});
|
||||
} else {
|
||||
CameraManager.startPreview();
|
||||
}
|
||||
}
|
||||
|
||||
stopPreview() {
|
||||
if (Platform.OS === 'android') {
|
||||
const props = convertNativeProps(this.props);
|
||||
CameraManager.stopPreview({
|
||||
type: props.type,
|
||||
});
|
||||
} else {
|
||||
CameraManager.stopPreview();
|
||||
}
|
||||
}
|
||||
|
||||
stopCapture() {
|
||||
if (this.state.isRecording) {
|
||||
this.setState({ isRecording: false });
|
||||
return CameraManager.stopCapture();
|
||||
}
|
||||
return Promise.resolve('Not Recording.');
|
||||
}
|
||||
|
||||
getFOV() {
|
||||
return CameraManager.getFOV();
|
||||
}
|
||||
|
||||
hasFlash() {
|
||||
if (Platform.OS === 'android') {
|
||||
const props = convertNativeProps(this.props);
|
||||
return CameraManager.hasFlash({
|
||||
type: props.type,
|
||||
});
|
||||
}
|
||||
return CameraManager.hasFlash();
|
||||
}
|
||||
}
|
||||
|
||||
export const constants = Camera.constants;
|
||||
|
||||
const RCTCamera = requireNativeComponent('RCTCamera', Camera, {
|
||||
nativeOnly: {
|
||||
testID: true,
|
||||
renderToHardwareTextureAndroid: true,
|
||||
accessibilityLabel: true,
|
||||
importantForAccessibility: true,
|
||||
accessibilityLiveRegion: true,
|
||||
accessibilityComponentType: true,
|
||||
onLayout: true,
|
||||
},
|
||||
});
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
base: {},
|
||||
});
|
||||
+2
-279
@@ -1,280 +1,3 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
DeviceEventEmitter, // android
|
||||
NativeAppEventEmitter, // ios
|
||||
NativeModules,
|
||||
Platform,
|
||||
StyleSheet,
|
||||
requireNativeComponent,
|
||||
ViewPropTypes,
|
||||
} from 'react-native';
|
||||
import Camera from './Camera';
|
||||
|
||||
const CameraManager = NativeModules.CameraManager || NativeModules.CameraModule;
|
||||
const CAMERA_REF = 'camera';
|
||||
|
||||
function convertNativeProps(props) {
|
||||
const newProps = { ...props };
|
||||
if (typeof props.aspect === 'string') {
|
||||
newProps.aspect = Camera.constants.Aspect[props.aspect];
|
||||
}
|
||||
|
||||
if (typeof props.flashMode === 'string') {
|
||||
newProps.flashMode = Camera.constants.FlashMode[props.flashMode];
|
||||
}
|
||||
|
||||
if (typeof props.orientation === 'string') {
|
||||
newProps.orientation = Camera.constants.Orientation[props.orientation];
|
||||
}
|
||||
|
||||
if (typeof props.torchMode === 'string') {
|
||||
newProps.torchMode = Camera.constants.TorchMode[props.torchMode];
|
||||
}
|
||||
|
||||
if (typeof props.type === 'string') {
|
||||
newProps.type = Camera.constants.Type[props.type];
|
||||
}
|
||||
|
||||
if (typeof props.captureQuality === 'string') {
|
||||
newProps.captureQuality = Camera.constants.CaptureQuality[props.captureQuality];
|
||||
}
|
||||
|
||||
if (typeof props.captureMode === 'string') {
|
||||
newProps.captureMode = Camera.constants.CaptureMode[props.captureMode];
|
||||
}
|
||||
|
||||
if (typeof props.captureTarget === 'string') {
|
||||
newProps.captureTarget = Camera.constants.CaptureTarget[props.captureTarget];
|
||||
}
|
||||
|
||||
// do not register barCodeTypes if no barcode listener
|
||||
if (typeof props.onBarCodeRead !== 'function') {
|
||||
newProps.barCodeTypes = [];
|
||||
}
|
||||
|
||||
newProps.barcodeScannerEnabled = typeof props.onBarCodeRead === 'function';
|
||||
|
||||
return newProps;
|
||||
}
|
||||
|
||||
export default class Camera extends Component {
|
||||
static constants = {
|
||||
Aspect: CameraManager.Aspect,
|
||||
BarCodeType: CameraManager.BarCodeType,
|
||||
Type: CameraManager.Type,
|
||||
CaptureMode: CameraManager.CaptureMode,
|
||||
CaptureTarget: CameraManager.CaptureTarget,
|
||||
CaptureQuality: CameraManager.CaptureQuality,
|
||||
Orientation: CameraManager.Orientation,
|
||||
FlashMode: CameraManager.FlashMode,
|
||||
TorchMode: CameraManager.TorchMode,
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
...ViewPropTypes,
|
||||
aspect: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
captureAudio: PropTypes.bool,
|
||||
captureMode: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
captureQuality: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
captureTarget: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
defaultOnFocusComponent: PropTypes.bool,
|
||||
flashMode: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
keepAwake: PropTypes.bool,
|
||||
onBarCodeRead: PropTypes.func,
|
||||
barcodeScannerEnabled: PropTypes.bool,
|
||||
onFocusChanged: PropTypes.func,
|
||||
onZoomChanged: PropTypes.func,
|
||||
mirrorImage: PropTypes.bool,
|
||||
fixOrientation: PropTypes.bool,
|
||||
barCodeTypes: PropTypes.array,
|
||||
orientation: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
playSoundOnCapture: PropTypes.bool,
|
||||
torchMode: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
type: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
aspect: CameraManager.Aspect.fill,
|
||||
type: CameraManager.Type.back,
|
||||
orientation: CameraManager.Orientation.auto,
|
||||
fixOrientation: false,
|
||||
captureAudio: false,
|
||||
captureMode: CameraManager.CaptureMode.still,
|
||||
captureTarget: CameraManager.CaptureTarget.cameraRoll,
|
||||
captureQuality: CameraManager.CaptureQuality.high,
|
||||
defaultOnFocusComponent: true,
|
||||
flashMode: CameraManager.FlashMode.off,
|
||||
playSoundOnCapture: true,
|
||||
torchMode: CameraManager.TorchMode.off,
|
||||
mirrorImage: false,
|
||||
barCodeTypes: Object.values(CameraManager.BarCodeType),
|
||||
};
|
||||
|
||||
static checkDeviceAuthorizationStatus = CameraManager.checkDeviceAuthorizationStatus;
|
||||
static checkVideoAuthorizationStatus = CameraManager.checkVideoAuthorizationStatus;
|
||||
static checkAudioAuthorizationStatus = CameraManager.checkAudioAuthorizationStatus;
|
||||
|
||||
setNativeProps(props) {
|
||||
// eslint-disable-next-line
|
||||
this.refs[CAMERA_REF].setNativeProps(props);
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
isAuthorized: false,
|
||||
isRecording: false,
|
||||
};
|
||||
}
|
||||
|
||||
async componentWillMount() {
|
||||
this._addOnBarCodeReadListener();
|
||||
|
||||
let { captureMode } = convertNativeProps({ captureMode: this.props.captureMode });
|
||||
let hasVideoAndAudio =
|
||||
this.props.captureAudio && captureMode === Camera.constants.CaptureMode.video;
|
||||
let check = hasVideoAndAudio
|
||||
? Camera.checkDeviceAuthorizationStatus
|
||||
: Camera.checkVideoAuthorizationStatus;
|
||||
|
||||
if (check) {
|
||||
const isAuthorized = await check();
|
||||
this.setState({ isAuthorized });
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this._removeOnBarCodeReadListener();
|
||||
|
||||
if (this.state.isRecording) {
|
||||
this.stopCapture();
|
||||
}
|
||||
}
|
||||
|
||||
componentWillReceiveProps(newProps) {
|
||||
const { onBarCodeRead } = this.props;
|
||||
if (onBarCodeRead !== newProps.onBarCodeRead) {
|
||||
this._addOnBarCodeReadListener(newProps);
|
||||
}
|
||||
}
|
||||
|
||||
_addOnBarCodeReadListener(props) {
|
||||
const { onBarCodeRead } = props || this.props;
|
||||
this._removeOnBarCodeReadListener();
|
||||
if (onBarCodeRead) {
|
||||
this.cameraBarCodeReadListener = Platform.select({
|
||||
ios: NativeAppEventEmitter.addListener('CameraBarCodeRead', this._onBarCodeRead),
|
||||
android: DeviceEventEmitter.addListener('CameraBarCodeReadAndroid', this._onBarCodeRead),
|
||||
});
|
||||
}
|
||||
}
|
||||
_removeOnBarCodeReadListener() {
|
||||
const listener = this.cameraBarCodeReadListener;
|
||||
if (listener) {
|
||||
listener.remove();
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
// TODO - style is not used, figure it out why
|
||||
// eslint-disable-next-line
|
||||
const style = [styles.base, this.props.style];
|
||||
const nativeProps = convertNativeProps(this.props);
|
||||
|
||||
return <RCTCamera ref={CAMERA_REF} {...nativeProps} />;
|
||||
}
|
||||
|
||||
_onBarCodeRead = data => {
|
||||
if (this.props.onBarCodeRead) {
|
||||
this.props.onBarCodeRead(data);
|
||||
}
|
||||
};
|
||||
|
||||
capture(options) {
|
||||
const props = convertNativeProps(this.props);
|
||||
options = {
|
||||
audio: props.captureAudio,
|
||||
barCodeTypes: props.barCodeTypes,
|
||||
mode: props.captureMode,
|
||||
playSoundOnCapture: props.playSoundOnCapture,
|
||||
target: props.captureTarget,
|
||||
quality: props.captureQuality,
|
||||
type: props.type,
|
||||
title: '',
|
||||
description: '',
|
||||
mirrorImage: props.mirrorImage,
|
||||
fixOrientation: props.fixOrientation,
|
||||
...options,
|
||||
};
|
||||
|
||||
if (options.mode === Camera.constants.CaptureMode.video) {
|
||||
options.totalSeconds = options.totalSeconds > -1 ? options.totalSeconds : -1;
|
||||
options.preferredTimeScale = options.preferredTimeScale || 30;
|
||||
this.setState({ isRecording: true });
|
||||
}
|
||||
|
||||
return CameraManager.capture(options);
|
||||
}
|
||||
|
||||
startPreview() {
|
||||
if (Platform.OS === 'android') {
|
||||
const props = convertNativeProps(this.props);
|
||||
CameraManager.startPreview({
|
||||
type: props.type,
|
||||
});
|
||||
} else {
|
||||
CameraManager.startPreview();
|
||||
}
|
||||
}
|
||||
|
||||
stopPreview() {
|
||||
if (Platform.OS === 'android') {
|
||||
const props = convertNativeProps(this.props);
|
||||
CameraManager.stopPreview({
|
||||
type: props.type,
|
||||
});
|
||||
} else {
|
||||
CameraManager.stopPreview();
|
||||
}
|
||||
}
|
||||
|
||||
stopCapture() {
|
||||
if (this.state.isRecording) {
|
||||
this.setState({ isRecording: false });
|
||||
return CameraManager.stopCapture();
|
||||
}
|
||||
return Promise.resolve('Not Recording.');
|
||||
}
|
||||
|
||||
getFOV() {
|
||||
return CameraManager.getFOV();
|
||||
}
|
||||
|
||||
hasFlash() {
|
||||
if (Platform.OS === 'android') {
|
||||
const props = convertNativeProps(this.props);
|
||||
return CameraManager.hasFlash({
|
||||
type: props.type,
|
||||
});
|
||||
}
|
||||
return CameraManager.hasFlash();
|
||||
}
|
||||
}
|
||||
|
||||
export const constants = Camera.constants;
|
||||
|
||||
const RCTCamera = requireNativeComponent('RCTCamera', Camera, {
|
||||
nativeOnly: {
|
||||
testID: true,
|
||||
renderToHardwareTextureAndroid: true,
|
||||
accessibilityLabel: true,
|
||||
importantForAccessibility: true,
|
||||
accessibilityLiveRegion: true,
|
||||
accessibilityComponentType: true,
|
||||
onLayout: true,
|
||||
},
|
||||
});
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
base: {},
|
||||
});
|
||||
export default Camera;
|
||||
|
||||
Reference in New Issue
Block a user