- support capture/retake mode on the full screen camera on Android

- delete the temp file (created in capture/retake) when canceled
This commit is contained in:
Artal Druk 2017-03-22 18:40:35 +02:00
parent 9ca33bfdc8
commit 4fe138fe2a
6 changed files with 298 additions and 170 deletions

View File

@ -0,0 +1,235 @@
package com.wix.RNCameraKit;
import android.content.Context;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.hardware.Camera;
import android.net.Uri;
import android.os.AsyncTask;
import android.provider.MediaStore;
import android.support.annotation.Nullable;
import android.util.Log;
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.Arguments;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.WritableMap;
import com.wix.RNCameraKit.camera.CameraViewManager;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import static com.facebook.react.common.ReactConstants.TAG;
public class SaveImageTask extends AsyncTask<byte[], Void, Void> {
private final Context context;
private final Promise promise;
private boolean saveToCameraRoll;
private String bitmapUrl = null;
public SaveImageTask(Context context, Promise promise, boolean saveToCameraRoll) {
this.context = context;
this.promise = promise;
this.saveToCameraRoll = saveToCameraRoll;
}
public SaveImageTask(String bitmapUrl, Context context, Promise promise, boolean saveToCameraRoll) {
this(context, promise, saveToCameraRoll);
this.bitmapUrl = bitmapUrl;
if (this.bitmapUrl != null) {
this.bitmapUrl = this.bitmapUrl.replace("file://","");
}
}
private Bitmap getImageBitmap(byte[]... data) {
Bitmap image;
if (bitmapUrl != null) {
FileInputStream fis;
File imageFile = new File(bitmapUrl);
try {
fis = new FileInputStream(imageFile);
image = BitmapFactory.decodeStream(fis);
fis.close();
} catch (IOException e) {
e.printStackTrace();
image = null;
}
if (imageFile.exists()) {
imageFile.delete();
}
}
else {
byte[] rawImageData = data[0];
image = decodeAndRotateIfNeeded(rawImageData);
}
return image;
}
@Override
protected Void doInBackground(byte[]... data) {
Bitmap image = getImageBitmap(data);
if (image == null) {
promise.reject("CameraKit", "failed to get Bitmap image");
return null;
}
WritableMap imageInfo = saveToCameraRoll ? saveToMediaStore(image) : saveTempImageFile(image);
if (imageInfo == null)
promise.reject("CameraKit", "failed to save image to MediaStore");
else {
promise.resolve(imageInfo);
CameraViewManager.reconnect();
}
return null;
}
private WritableMap createImageInfo(String filePath, String id, String fileName, long fileSize) {
WritableMap imageInfo = Arguments.createMap();
imageInfo.putString("uri", filePath);
imageInfo.putString("id", id);
imageInfo.putString("name", fileName);
imageInfo.putInt("size", (int) fileSize);
return imageInfo;
}
private WritableMap saveToMediaStore(Bitmap image) {
try {
String fileUri = MediaStore.Images.Media.insertImage(context.getContentResolver(), image, System.currentTimeMillis() + "", "");
Cursor cursor = context.getContentResolver().query(Uri.parse(fileUri), new String[]{
MediaStore.Images.ImageColumns.DATA,
MediaStore.Images.ImageColumns.DISPLAY_NAME
}, null, null, null);
cursor.moveToFirst();
int pathIndex = cursor.getColumnIndexOrThrow(MediaStore.Images.ImageColumns.DATA);
int nameIndex = cursor.getColumnIndexOrThrow(MediaStore.Images.ImageColumns.DISPLAY_NAME);
String filePath = cursor.getString(pathIndex);
String fileName = cursor.getString(nameIndex);
long fileSize = new File(filePath).length();
cursor.close();
return createImageInfo(filePath, filePath, fileName, fileSize);
} catch (Exception e) {
return null;
}
}
private Bitmap decodeAndRotateIfNeeded(byte[] rawImageData) {
Matrix bitmapMatrix = getRotationMatrix(rawImageData);
Bitmap image = BitmapFactory.decodeByteArray(rawImageData, 0, rawImageData.length);
if (bitmapMatrix.isIdentity())
return image;
else
return rotateImage(image, bitmapMatrix);
}
private Bitmap rotateImage(Bitmap image, Matrix bitmapMatrix) {
return Bitmap.createBitmap(image, 0, 0, image.getWidth(), image.getHeight(), bitmapMatrix, false);
}
private Matrix getRotationMatrix(byte[] rawImageData) {
try {
return tryGetRotationMatrix(rawImageData);
} catch (Exception e) {
return new Matrix();
}
}
private Matrix tryGetRotationMatrix(byte[] rawImageData) throws ImageProcessingException, IOException, MetadataException {
Matrix matrix = new Matrix();
Metadata metadata = readMetadata(rawImageData);
final ExifIFD0Directory exifIFD0Directory = metadata.getFirstDirectoryOfType(ExifIFD0Directory.class);
boolean hasOrientation = exifIFD0Directory.containsTag(ExifIFD0Directory.TAG_ORIENTATION);
if (hasOrientation) {
final int exifOrientation = exifIFD0Directory.getInt(ExifIFD0Directory.TAG_ORIENTATION);
boolean isFacingFront = CameraViewManager.getCameraInfo().facing == Camera.CameraInfo.CAMERA_FACING_FRONT;
convertExifOrientationToMatrix(matrix, exifOrientation, isFacingFront);
}
return matrix;
}
private void convertExifOrientationToMatrix(Matrix matrix, int exifOrientation, boolean isCameraFacingFront) {
switch (exifOrientation) {
case 1:
break; // top left
case 2:
matrix.postScale(-1, 1);
break; // top right
case 3:
matrix.postRotate(180);
break; // bottom right
case 4:
matrix.postRotate(180);
matrix.postScale(-1, 1);
break; // bottom left
case 5:
matrix.postRotate(90);
matrix.postScale(-1, 1);
break; // left top
case 6:
matrix.postRotate(90);
break; // right top
case 7:
matrix.postRotate(270);
matrix.postScale(-1, 1);
break; // right bottom
case 8:
matrix.postRotate(270);
break; // left bottom
default:
break; // Unknown
}
if (isCameraFacingFront) {
matrix.postRotate(180);
}
}
private Metadata readMetadata(byte[] rawImageData) throws ImageProcessingException, IOException {
Metadata metadata = null;
ByteArrayInputStream inputStream = null;
BufferedInputStream bufferedInputStream = null;
try {
inputStream = new ByteArrayInputStream(rawImageData);
bufferedInputStream = new BufferedInputStream(inputStream);
metadata = ImageMetadataReader.readMetadata(bufferedInputStream, rawImageData.length);
} finally {
if (bufferedInputStream != null) bufferedInputStream.close();
if (inputStream != null) inputStream.close();
}
return metadata;
}
@Nullable
private WritableMap saveTempImageFile(Bitmap image) {
File imageFile;
FileOutputStream outputStream;
Long tsLong = System.currentTimeMillis()/1000;
String fileName = "temp_Image_" + tsLong.toString() + ".jpg";
try {
imageFile = new File(context.getCacheDir(), fileName);
if (imageFile.exists()) {
imageFile.delete();
}
outputStream = new FileOutputStream(imageFile);
image.compress(Bitmap.CompressFormat.JPEG, 100, outputStream);
outputStream.close();
} catch (IOException e) {
Log.d(TAG, "Error accessing file: " + e.getMessage());
imageFile = null;
}
return (imageFile != null) ? createImageInfo(Uri.fromFile(imageFile).toString(), imageFile.getAbsolutePath(), fileName, imageFile.length()) : null;
}
}

View File

@ -10,6 +10,7 @@ import com.facebook.react.bridge.ReactMethod;
import com.wix.RNCameraKit.camera.commands.Capture;
import com.wix.RNCameraKit.camera.permission.CameraPermission;
public class CameraModule extends ReactContextBaseJavaModule {
private final CameraPermission cameraPermission;
@ -106,7 +107,7 @@ public class CameraModule extends ReactContextBaseJavaModule {
@ReactMethod
public void capture(boolean saveToCameraRoll, final Promise promise) {
new Capture(getReactApplicationContext()).execute(promise);
new Capture(getReactApplicationContext(), saveToCameraRoll).execute(promise);
}
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {

View File

@ -1,36 +1,21 @@
package com.wix.RNCameraKit.camera.commands;
import android.content.Context;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.hardware.Camera;
import android.net.Uri;
import android.os.AsyncTask;
import android.provider.MediaStore;
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.Arguments;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.WritableMap;
import com.wix.RNCameraKit.camera.CameraViewManager;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import com.wix.RNCameraKit.camera.CameraViewManager;
import com.wix.RNCameraKit.SaveImageTask;
public class Capture implements Command {
private final Context context;
private boolean saveToCameraRoll;
public Capture(Context context) {
public Capture(Context context, boolean saveToCameraRoll) {
this.context = context;
this.saveToCameraRoll = saveToCameraRoll;
}
@Override
@ -47,143 +32,8 @@ public class Capture implements Command {
@Override
public void onPictureTaken(byte[] data, Camera camera) {
camera.stopPreview();
new SaveImageTask(promise).execute(data);
new SaveImageTask(context, promise, saveToCameraRoll).execute(data);
}
});
}
private class SaveImageTask extends AsyncTask<byte[], Void, Void> {
private final Promise promise;
private SaveImageTask(Promise promise) {
this.promise = promise;
}
@Override
protected Void doInBackground(byte[]... data) {
byte[] rawImageData = data[0];
Bitmap image = decodeAndRotateIfNeeded(rawImageData);
WritableMap imageInfo = saveToMediaStore(image);
if (imageInfo == null)
promise.reject("CameraKit", "failed to save image to MediaStore");
else {
promise.resolve(imageInfo);
CameraViewManager.reconnect();
}
return null;
}
private WritableMap saveToMediaStore(Bitmap image) {
try {
String fileUri = MediaStore.Images.Media.insertImage(context.getContentResolver(), image, System.currentTimeMillis() + "", "");
Cursor cursor = context.getContentResolver().query(Uri.parse(fileUri), new String[]{
MediaStore.Images.ImageColumns.DATA,
MediaStore.Images.ImageColumns.DISPLAY_NAME
}, null, null, null);
cursor.moveToFirst();
int pathIndex = cursor.getColumnIndexOrThrow(MediaStore.Images.ImageColumns.DATA);
int nameIndex = cursor.getColumnIndexOrThrow(MediaStore.Images.ImageColumns.DISPLAY_NAME);
String filePath = cursor.getString(pathIndex);
String fileName = cursor.getString(nameIndex);
long fileSize = new File(filePath).length();
cursor.close();
WritableMap imageInfo = Arguments.createMap();
imageInfo.putString("uri", filePath);
imageInfo.putString("id", filePath);
imageInfo.putString("name", fileName);
imageInfo.putInt("size", (int) fileSize);
return imageInfo;
} catch (Exception e) {
return null;
}
}
private Bitmap decodeAndRotateIfNeeded(byte[] rawImageData) {
Matrix bitmapMatrix = getRotationMatrix(rawImageData);
Bitmap image = BitmapFactory.decodeByteArray(rawImageData, 0, rawImageData.length);
if (bitmapMatrix.isIdentity())
return image;
else
return rotateImage(image, bitmapMatrix);
}
private Bitmap rotateImage(Bitmap image, Matrix bitmapMatrix) {
return Bitmap.createBitmap(image, 0, 0, image.getWidth(), image.getHeight(), bitmapMatrix, false);
}
private Matrix getRotationMatrix(byte[] rawImageData) {
try {
return tryGetRotationMatrix(rawImageData);
} catch (Exception e) {
return new Matrix();
}
}
private Matrix tryGetRotationMatrix(byte[] rawImageData) throws ImageProcessingException, IOException, MetadataException {
Matrix matrix = new Matrix();
Metadata metadata = readMetadata(rawImageData);
final ExifIFD0Directory exifIFD0Directory = metadata.getFirstDirectoryOfType(ExifIFD0Directory.class);
boolean hasOrientation = exifIFD0Directory.containsTag(ExifIFD0Directory.TAG_ORIENTATION);
if (hasOrientation) {
final int exifOrientation = exifIFD0Directory.getInt(ExifIFD0Directory.TAG_ORIENTATION);
boolean isFacingFront = CameraViewManager.getCameraInfo().facing == Camera.CameraInfo.CAMERA_FACING_FRONT;
convertExifOrientationToMatrix(matrix, exifOrientation, isFacingFront);
}
return matrix;
}
private void convertExifOrientationToMatrix(Matrix matrix, int exifOrientation, boolean isCameraFacingFront) {
switch (exifOrientation) {
case 1:
break; // top left
case 2:
matrix.postScale(-1, 1);
break; // top right
case 3:
matrix.postRotate(180);
break; // bottom right
case 4:
matrix.postRotate(180);
matrix.postScale(-1, 1);
break; // bottom left
case 5:
matrix.postRotate(90);
matrix.postScale(-1, 1);
break; // left top
case 6:
matrix.postRotate(90);
break; // right top
case 7:
matrix.postRotate(270);
matrix.postScale(-1, 1);
break; // right bottom
case 8:
matrix.postRotate(270);
break; // left bottom
default:
break; // Unknown
}
if (isCameraFacingFront) {
matrix.postRotate(180);
}
}
private Metadata readMetadata(byte[] rawImageData) throws ImageProcessingException, IOException {
Metadata metadata = null;
ByteArrayInputStream inputStream = null;
BufferedInputStream bufferedInputStream = null;
try {
inputStream = new ByteArrayInputStream(rawImageData);
bufferedInputStream = new BufferedInputStream(inputStream);
metadata = ImageMetadataReader.readMetadata(bufferedInputStream, rawImageData.length);
} finally {
if (bufferedInputStream != null) bufferedInputStream.close();
if (inputStream != null) inputStream.close();
}
return metadata;
}
}
}

View File

@ -2,6 +2,7 @@ package com.wix.RNCameraKit.gallery;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.provider.MediaStore;
import android.support.annotation.NonNull;
@ -14,8 +15,12 @@ import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableMap;
import com.wix.RNCameraKit.SaveImageTask;
import com.wix.RNCameraKit.gallery.permission.StoragePermission;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Collection;
import java.util.HashMap;
@ -235,4 +240,25 @@ public class NativeGalleryModule extends ReactContextBaseJavaModule {
ret.putArray("images", arr);
promise.resolve(ret);
}
@ReactMethod
public void saveImageURLToCameraRoll(String imageUrl, final Promise promise) {
new SaveImageTask(imageUrl, getReactApplicationContext(), promise, true).execute();
}
@ReactMethod
public void deleteTempImage(String imageUrl, final Promise promise) {
boolean success = true;
String imagePath = imageUrl.replace("file://","");
File imageFile = new File(imagePath);
if (imageFile.exists()) {
success = imageFile.delete();
}
if(promise != null) {
WritableMap result = Arguments.createMap();
result.putBoolean("success", success);
promise.resolve(result);
}
}
}

View File

@ -350,4 +350,24 @@ RCT_EXPORT_METHOD(saveImageURLToCameraRoll:(NSString*)imageURL
}];
}
RCT_EXPORT_METHOD(deleteTempImage:(NSString*)tempImageURL
resolve:(RCTPromiseResolveBlock)resolve
reject:(RCTPromiseRejectBlock)reject) {
NSError *error;
NSFileManager *defaultFileManager = [NSFileManager defaultManager];
NSMutableDictionary *result = [NSMutableDictionary dictionaryWithDictionary:@{@"success": @(YES)}];
tempImageURL = [tempImageURL stringByReplacingOccurrencesOfString:@"file://" withString:@""];
if([defaultFileManager fileExistsAtPath:tempImageURL]) {
BOOL success = [[NSFileManager defaultManager] removeItemAtPath:tempImageURL error:&error];
result[@"success"] = @(success);
if(error) {
result[@"error"] = [error description];
}
}
if(resolve) {
resolve(result);
}
}
@end

View File

@ -12,7 +12,7 @@ import _ from 'lodash';
import CameraKitCamera from './../CameraKitCamera';
const IsIOS = Platform.OS === 'ios';
const CKGallery = IsIOS ? NativeModules.CKGalleryManager : null;
const GalleryManager = IsIOS ? NativeModules.CKGalleryManager : NativeModules.NativeGalleryModule;
const FLASH_MODE_AUTO = 'auto';
const FLASH_MODE_ON = 'on';
@ -129,12 +129,12 @@ export default class CameraScreenBase extends Component {
{
this.isCaptureRetakeMode() ?
<Image
style={{ flex: 1, justifyContent: 'flex-end'}}
style={{flex: 1, justifyContent: 'flex-end'}}
source={{uri: this.state.imageCaptured.uri}}
/> :
<CameraKitCamera
ref={(cam) => this.camera = cam}
style={{ flex: 1, justifyContent: 'flex-end' }}
style={{flex: 1, justifyContent: 'flex-end'}}
cameraOptions={this.state.cameraOptions}
/>
}
@ -201,19 +201,15 @@ export default class CameraScreenBase extends Component {
const captureRetakeMode = this.isCaptureRetakeMode();
if (captureRetakeMode) {
if(type === 'left') {
GalleryManager.deleteTempImage(this.state.imageCaptured.uri);
this.setState({imageCaptured: undefined});
}
if(type === 'right') {
if(CKGallery !== null) {
const result = await CKGallery.saveImageURLToCameraRoll(this.state.imageCaptured.uri);
const savedImage = {...this.state.imageCaptured, id: result.id};
this.setState({imageCaptured: undefined, captureImages: _.concat(this.state.captureImages, savedImage)}, () => {
this.sendBottomButtonPressedAction(type, captureRetakeMode);
});
} else {
console.warn('Not implemented on Android yet');
else if(type === 'right') {
const result = await GalleryManager.saveImageURLToCameraRoll(this.state.imageCaptured.uri);
const savedImage = {...this.state.imageCaptured, id: result.id};
this.setState({imageCaptured: undefined, captureImages: _.concat(this.state.captureImages, savedImage)}, () => {
this.sendBottomButtonPressedAction(type, captureRetakeMode);
}
});
}
} else {
this.sendBottomButtonPressedAction(type, captureRetakeMode);