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:
Sibelius Seraphini
2017-12-31 13:49:39 -02:00
parent b263bef289
commit ffc74e5e6e
30 changed files with 2895 additions and 285 deletions
+17 -6
View File
@@ -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'
}
Binary file not shown.
+6
View File
@@ -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
Vendored Executable
+160
View File
@@ -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 "$@"
+90
View File
@@ -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));
}
}
@@ -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;
}
}
@@ -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
View File
@@ -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
View File
@@ -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;