mirror of
https://github.com/status-im/react-native-camera.git
synced 2025-02-24 17:58:20 +00:00
(android): android performance improvements (#644)
* removed re-saving of output file * moved image processing onto an async task to allow camera to be used while processing is running
This commit is contained in:
parent
98de15cf9a
commit
9fd9d1f8b1
@ -0,0 +1,285 @@
|
|||||||
|
package com.lwansbrough.RCTCamera;
|
||||||
|
|
||||||
|
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.FileInputStream;
|
||||||
|
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);
|
||||||
|
rotate(exifOrientation);
|
||||||
|
}
|
||||||
|
} catch (ImageProcessingException | IOException | MetadataException e) {
|
||||||
|
throw new ImageMutationFailedException("failed to fix orientation", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void rotate(int exifOrientation) throws ImageMutationFailedException {
|
||||||
|
final Matrix bitmapMatrix = new Matrix();
|
||||||
|
switch (exifOrientation) {
|
||||||
|
case 1:
|
||||||
|
break;
|
||||||
|
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() {
|
||||||
|
return Base64.encodeToString(toBytes(currentRepresentation), Base64.DEFAULT);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void writeDataToFile(File file, ReadableMap options) throws IOException {
|
||||||
|
FileOutputStream fos = new FileOutputStream(file);
|
||||||
|
fos.write(toBytes(currentRepresentation));
|
||||||
|
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[] toBytes(Bitmap image) {
|
||||||
|
byte[] result = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
result = toJpeg(image, 85);
|
||||||
|
} catch (OutOfMemoryError e) {
|
||||||
|
try {
|
||||||
|
result = toJpeg(image, 70);
|
||||||
|
} catch (OutOfMemoryError e2) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -6,23 +6,16 @@
|
|||||||
package com.lwansbrough.RCTCamera;
|
package com.lwansbrough.RCTCamera;
|
||||||
|
|
||||||
import android.content.ContentValues;
|
import android.content.ContentValues;
|
||||||
import android.graphics.Bitmap;
|
|
||||||
import android.graphics.BitmapFactory;
|
|
||||||
import android.graphics.Matrix;
|
|
||||||
import android.hardware.Camera;
|
import android.hardware.Camera;
|
||||||
import android.media.*;
|
import android.media.*;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
import android.os.AsyncTask;
|
||||||
import android.os.Environment;
|
import android.os.Environment;
|
||||||
import android.provider.MediaStore;
|
import android.provider.MediaStore;
|
||||||
import android.util.Base64;
|
import android.util.Base64;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.Surface;
|
import android.view.Surface;
|
||||||
|
|
||||||
import com.drew.imaging.ImageMetadataReader;
|
|
||||||
import com.drew.imaging.ImageProcessingException;
|
|
||||||
import com.drew.metadata.Metadata;
|
|
||||||
import com.drew.metadata.MetadataException;
|
|
||||||
import com.drew.metadata.exif.ExifIFD0Directory;
|
|
||||||
import com.facebook.react.bridge.LifecycleEventListener;
|
import com.facebook.react.bridge.LifecycleEventListener;
|
||||||
import com.facebook.react.bridge.Promise;
|
import com.facebook.react.bridge.Promise;
|
||||||
import com.facebook.react.bridge.ReactApplicationContext;
|
import com.facebook.react.bridge.ReactApplicationContext;
|
||||||
@ -489,130 +482,6 @@ public class RCTCameraModule extends ReactContextBaseJavaModule
|
|||||||
return byteArray;
|
return byteArray;
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] saveImage(Bitmap image) {
|
|
||||||
byte[] result = null;
|
|
||||||
|
|
||||||
try {
|
|
||||||
result = compress(image, 85);
|
|
||||||
} catch (OutOfMemoryError e) {
|
|
||||||
try {
|
|
||||||
result = compress(image, 70);
|
|
||||||
} catch (OutOfMemoryError e2) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private byte[] mirrorImage(byte[] data) {
|
|
||||||
Bitmap photo = toBitmap(data);
|
|
||||||
|
|
||||||
Matrix m = new Matrix();
|
|
||||||
m.preScale(-1, 1);
|
|
||||||
Bitmap mirroredImage = Bitmap.createBitmap(photo, 0, 0, photo.getWidth(), photo.getHeight(), m, false);
|
|
||||||
|
|
||||||
return saveImage(mirroredImage);
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private byte[] rotate(byte[] data, int exifOrientation) {
|
|
||||||
final Matrix bitmapMatrix = new Matrix();
|
|
||||||
switch(exifOrientation)
|
|
||||||
{
|
|
||||||
case 1:
|
|
||||||
break;
|
|
||||||
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 decodedBitmap = toBitmap(data);
|
|
||||||
Bitmap transformedBitmap = Bitmap.createBitmap(
|
|
||||||
decodedBitmap, 0, 0, decodedBitmap.getWidth(), decodedBitmap.getHeight(), bitmapMatrix, false
|
|
||||||
);
|
|
||||||
|
|
||||||
return saveImage(transformedBitmap);
|
|
||||||
}
|
|
||||||
|
|
||||||
private byte[] fixOrientation(byte[] data) {
|
|
||||||
try {
|
|
||||||
final Metadata metadata = ImageMetadataReader.readMetadata(
|
|
||||||
new BufferedInputStream(new ByteArrayInputStream(data)), data.length
|
|
||||||
);
|
|
||||||
|
|
||||||
final ExifIFD0Directory exifIFD0Directory = metadata.getFirstDirectoryOfType(ExifIFD0Directory.class);
|
|
||||||
if (exifIFD0Directory == null) {
|
|
||||||
return data;
|
|
||||||
} else if (exifIFD0Directory.containsTag(ExifIFD0Directory.TAG_ORIENTATION)) {
|
|
||||||
final int exifOrientation = exifIFD0Directory.getInt(ExifIFD0Directory.TAG_ORIENTATION);
|
|
||||||
return rotate(data, exifOrientation);
|
|
||||||
}
|
|
||||||
return data;
|
|
||||||
} catch (IOException | ImageProcessingException | MetadataException e) {
|
|
||||||
Log.e(TAG, "fail to fix orientation", e);
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void rewriteOrientation(String path) {
|
|
||||||
try {
|
|
||||||
ExifInterface exif = new ExifInterface(path);
|
|
||||||
exif.setAttribute(ExifInterface.TAG_ORIENTATION, String.valueOf(ExifInterface.ORIENTATION_NORMAL));
|
|
||||||
exif.saveAttributes();
|
|
||||||
} catch (IOException e) {
|
|
||||||
Log.e(TAG, "failed to save exif data", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private byte[] compress(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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@ReactMethod
|
@ReactMethod
|
||||||
public void capture(final ReadableMap options, final Promise promise) {
|
public void capture(final ReadableMap options, final Promise promise) {
|
||||||
int orientation = options.hasKey("orientation") ? options.getInt("orientation") : RCTCamera.getInstance().getOrientation();
|
int orientation = options.hasKey("orientation") ? options.getInt("orientation") : RCTCamera.getInstance().getOrientation();
|
||||||
@ -662,87 +531,16 @@ public class RCTCameraModule extends ReactContextBaseJavaModule
|
|||||||
|
|
||||||
Camera.PictureCallback captureCallback = new Camera.PictureCallback() {
|
Camera.PictureCallback captureCallback = new Camera.PictureCallback() {
|
||||||
@Override
|
@Override
|
||||||
public void onPictureTaken(byte[] data, Camera camera) {
|
public void onPictureTaken(final byte[] data, Camera camera) {
|
||||||
|
|
||||||
if (shouldMirror) {
|
|
||||||
data = mirrorImage(data);
|
|
||||||
if (data == null) {
|
|
||||||
promise.reject("Error mirroring image");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
data = fixOrientation(data);
|
|
||||||
|
|
||||||
camera.stopPreview();
|
camera.stopPreview();
|
||||||
camera.startPreview();
|
camera.startPreview();
|
||||||
|
|
||||||
switch (options.getInt("target")) {
|
AsyncTask.execute(new Runnable() {
|
||||||
case RCT_CAMERA_CAPTURE_TARGET_MEMORY:
|
@Override
|
||||||
String encoded = Base64.encodeToString(data, Base64.DEFAULT);
|
public void run() {
|
||||||
WritableMap response = new WritableNativeMap();
|
processImage(new MutableImage(data), shouldMirror, options, promise);
|
||||||
response.putString("data", encoded);
|
|
||||||
promise.resolve(response);
|
|
||||||
break;
|
|
||||||
case RCT_CAMERA_CAPTURE_TARGET_CAMERA_ROLL: {
|
|
||||||
File cameraRollFile = getOutputCameraRollFile(MEDIA_TYPE_IMAGE);
|
|
||||||
if (cameraRollFile == null) {
|
|
||||||
promise.reject("Error creating media file.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Throwable error = writeDataToFile(data, cameraRollFile);
|
|
||||||
if (error != null) {
|
|
||||||
promise.reject(error);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
writeLocationExifData(cameraRollFile, options);
|
|
||||||
rewriteOrientation(cameraRollFile.getAbsolutePath());
|
|
||||||
addToMediaStore(cameraRollFile.getAbsolutePath());
|
|
||||||
|
|
||||||
resolve(cameraRollFile, promise);
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
case RCT_CAMERA_CAPTURE_TARGET_DISK: {
|
});
|
||||||
File pictureFile = getOutputMediaFile(MEDIA_TYPE_IMAGE);
|
|
||||||
if (pictureFile == null) {
|
|
||||||
promise.reject("Error creating media file.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Throwable error = writeDataToFile(data, pictureFile);
|
|
||||||
if (error != null) {
|
|
||||||
promise.reject(error);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
writeLocationExifData(pictureFile, options);
|
|
||||||
rewriteOrientation(pictureFile.getAbsolutePath());
|
|
||||||
|
|
||||||
resolve(pictureFile, promise);
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case RCT_CAMERA_CAPTURE_TARGET_TEMP: {
|
|
||||||
File tempFile = getTempMediaFile(MEDIA_TYPE_IMAGE);
|
|
||||||
if (tempFile == null) {
|
|
||||||
promise.reject("Error creating media file.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Throwable error = writeDataToFile(data, tempFile);
|
|
||||||
if (error != null) {
|
|
||||||
promise.reject(error);
|
|
||||||
}
|
|
||||||
|
|
||||||
writeLocationExifData(tempFile, options);
|
|
||||||
rewriteOrientation(tempFile.getAbsolutePath());
|
|
||||||
|
|
||||||
resolve(tempFile, promise);
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mSafeToCapture = true;
|
mSafeToCapture = true;
|
||||||
}
|
}
|
||||||
@ -758,26 +556,88 @@ public class RCTCameraModule extends ReactContextBaseJavaModule
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void writeLocationExifData(File cameraRollFile, ReadableMap options) {
|
/**
|
||||||
if(!options.hasKey("metadata"))
|
* synchronized in order to prevent the user crashing the app by taking many photos and them all being processed
|
||||||
return;
|
* concurrently which would blow the memory (esp on smaller devices), and slow things down.
|
||||||
|
*/
|
||||||
ReadableMap metadata = options.getMap("metadata");
|
private synchronized void processImage(MutableImage mutableImage, Boolean shouldMirror, ReadableMap options, Promise promise) {
|
||||||
if (!metadata.hasKey("location"))
|
if (shouldMirror) {
|
||||||
return;
|
try {
|
||||||
|
mutableImage.mirrorImage();
|
||||||
ReadableMap location = metadata.getMap("location");
|
} catch (MutableImage.ImageMutationFailedException e) {
|
||||||
if(!location.hasKey("coords"))
|
promise.reject("Error mirroring image", e);
|
||||||
return;
|
}
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
ReadableMap coords = location.getMap("coords");
|
mutableImage.fixOrientation();
|
||||||
double latitude = coords.getDouble("latitude");
|
} catch (MutableImage.ImageMutationFailedException e) {
|
||||||
double longitude = coords.getDouble("longitude");
|
promise.reject("Error mirroring image", e);
|
||||||
|
}
|
||||||
|
|
||||||
GPS.writeExifData(cameraRollFile, latitude, longitude);
|
switch (options.getInt("target")) {
|
||||||
} catch (IOException e) {
|
case RCT_CAMERA_CAPTURE_TARGET_MEMORY:
|
||||||
Log.e(TAG, "Couldn't write location data", e);
|
String encoded = mutableImage.toBase64();
|
||||||
|
WritableMap response = new WritableNativeMap();
|
||||||
|
response.putString("data", encoded);
|
||||||
|
promise.resolve(response);
|
||||||
|
break;
|
||||||
|
case RCT_CAMERA_CAPTURE_TARGET_CAMERA_ROLL: {
|
||||||
|
File cameraRollFile = getOutputCameraRollFile(MEDIA_TYPE_IMAGE);
|
||||||
|
if (cameraRollFile == null) {
|
||||||
|
promise.reject("Error creating media file.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
mutableImage.writeDataToFile(cameraRollFile, options);
|
||||||
|
} catch (IOException e) {
|
||||||
|
promise.reject("failed to save image file", e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
addToMediaStore(cameraRollFile.getAbsolutePath());
|
||||||
|
|
||||||
|
resolve(cameraRollFile, promise);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case RCT_CAMERA_CAPTURE_TARGET_DISK: {
|
||||||
|
File pictureFile = getOutputMediaFile(MEDIA_TYPE_IMAGE);
|
||||||
|
if (pictureFile == null) {
|
||||||
|
promise.reject("Error creating media file.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
mutableImage.writeDataToFile(pictureFile, options);
|
||||||
|
} catch (IOException e) {
|
||||||
|
promise.reject("failed to save image file", e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve(pictureFile, promise);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case RCT_CAMERA_CAPTURE_TARGET_TEMP: {
|
||||||
|
File tempFile = getTempMediaFile(MEDIA_TYPE_IMAGE);
|
||||||
|
if (tempFile == null) {
|
||||||
|
promise.reject("Error creating media file.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
mutableImage.writeDataToFile(tempFile, options);
|
||||||
|
} catch (IOException e) {
|
||||||
|
promise.reject("failed to save image file", e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve(tempFile, promise);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -802,20 +662,6 @@ public class RCTCameraModule extends ReactContextBaseJavaModule
|
|||||||
promise.resolve(null != flashModes && !flashModes.isEmpty());
|
promise.resolve(null != flashModes && !flashModes.isEmpty());
|
||||||
}
|
}
|
||||||
|
|
||||||
private Throwable writeDataToFile(byte[] data, File file) {
|
|
||||||
try {
|
|
||||||
FileOutputStream fos = new FileOutputStream(file);
|
|
||||||
fos.write(data);
|
|
||||||
fos.close();
|
|
||||||
} catch (FileNotFoundException e) {
|
|
||||||
return e;
|
|
||||||
} catch (IOException e) {
|
|
||||||
return e;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private File getOutputMediaFile(int type) {
|
private File getOutputMediaFile(int type) {
|
||||||
// Get environment directory type id from requested media type.
|
// Get environment directory type id from requested media type.
|
||||||
String environmentDirectoryType;
|
String environmentDirectoryType;
|
||||||
@ -890,7 +736,6 @@ public class RCTCameraModule extends ReactContextBaseJavaModule
|
|||||||
MediaScannerConnection.scanFile(_reactContext, new String[] { path }, null, null);
|
MediaScannerConnection.scanFile(_reactContext, new String[] { path }, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* LifecycleEventListener overrides
|
* LifecycleEventListener overrides
|
||||||
*/
|
*/
|
||||||
@ -935,42 +780,4 @@ public class RCTCameraModule extends ReactContextBaseJavaModule
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class GPS {
|
|
||||||
public static void writeExifData(File targetFile, double latitude, double longitude) throws IOException {
|
|
||||||
ExifInterface exif = new ExifInterface(targetFile.getAbsolutePath());
|
|
||||||
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));
|
|
||||||
exif.saveAttributes();
|
|
||||||
}
|
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user