feat(exif): Implement writeExif for iOS, Android improvements (#2577)
* - Improve Android code so skipProcessing is not needed, the code is more in line with iOS, and is "fast" by default. This means that skipProcessing is no longer needed (nor used), and adding additional options will "slow down" the capture as expected, rather than having always a lot of processing. This shouldn't be a breaking change. - document the writeExif option, and implement it for iOS as well. * Release CF object which could cause a memleak
This commit is contained in:
parent
7fa631f32d
commit
aa22fd1fff
@ -671,7 +671,19 @@ class Camera1 extends CameraViewImpl implements MediaRecorder.OnInfoListener,
|
||||
mCamera.setParameters(mCameraParameters);
|
||||
}
|
||||
catch(RuntimeException e ) {
|
||||
Log.e("CAMERA_1::", "setParameters failed", e);
|
||||
Log.e("CAMERA_1::", "setParameters rotation failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
// set quality on capture since we might not process the image bitmap if not needed now.
|
||||
// This also achieves a much faster JPEG compression speed since it's done on the hardware
|
||||
if(options.hasKey("quality")){
|
||||
mCameraParameters.setJpegQuality((int) (options.getDouble("quality") * 100));
|
||||
try{
|
||||
mCamera.setParameters(mCameraParameters);
|
||||
}
|
||||
catch(RuntimeException e ) {
|
||||
Log.e("CAMERA_1::", "setParameters quality failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1254,6 +1254,13 @@ class Camera2 extends CameraViewImpl implements MediaRecorder.OnInfoListener, Me
|
||||
break;
|
||||
}
|
||||
captureRequestBuilder.set(CaptureRequest.JPEG_ORIENTATION, getOutputRotation());
|
||||
|
||||
|
||||
if(mCaptureCallback.getOptions().hasKey("quality")){
|
||||
int quality = (int) (mCaptureCallback.getOptions().getDouble("quality") * 100);
|
||||
captureRequestBuilder.set(CaptureRequest.JPEG_QUALITY, (byte)quality);
|
||||
}
|
||||
|
||||
captureRequestBuilder.set(CaptureRequest.SCALER_CROP_REGION, mPreviewRequestBuilder.get(CaptureRequest.SCALER_CROP_REGION));
|
||||
// Stop preview and capture a still picture.
|
||||
mCaptureSession.stopRepeating();
|
||||
|
||||
@ -11,6 +11,7 @@ import android.view.ViewGroup;
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.bridge.ReactContext;
|
||||
import com.facebook.react.bridge.ReadableMapKeySetIterator;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.facebook.react.bridge.WritableArray;
|
||||
import com.facebook.react.uimanager.UIManagerModule;
|
||||
@ -373,7 +374,7 @@ public class RNCameraViewHelper {
|
||||
return exifMap;
|
||||
}
|
||||
|
||||
public static void setExifData(ExifInterface exifInterface, WritableMap exifMap) {
|
||||
public static void setExifData(ExifInterface exifInterface, ReadableMap exifMap) {
|
||||
for (String[] tagInfo : exifTags) {
|
||||
String name = tagInfo[1];
|
||||
if (exifMap.hasKey(name)) {
|
||||
@ -394,15 +395,27 @@ public class RNCameraViewHelper {
|
||||
}
|
||||
}
|
||||
|
||||
if (exifMap.hasKey(ExifInterface.TAG_GPS_LATITUDE) &&
|
||||
exifMap.hasKey(ExifInterface.TAG_GPS_LONGITUDE) &&
|
||||
exifMap.hasKey(ExifInterface.TAG_GPS_ALTITUDE)) {
|
||||
if (exifMap.hasKey(ExifInterface.TAG_GPS_LATITUDE) && exifMap.hasKey(ExifInterface.TAG_GPS_LONGITUDE)) {
|
||||
exifInterface.setLatLong(exifMap.getDouble(ExifInterface.TAG_GPS_LATITUDE),
|
||||
exifMap.getDouble(ExifInterface.TAG_GPS_LONGITUDE));
|
||||
}
|
||||
if(exifMap.hasKey(ExifInterface.TAG_GPS_ALTITUDE)){
|
||||
exifInterface.setAltitude(exifMap.getDouble(ExifInterface.TAG_GPS_ALTITUDE));
|
||||
}
|
||||
}
|
||||
|
||||
// clears exif values in place
|
||||
public static void clearExifData(ExifInterface exifInterface) {
|
||||
for (String[] tagInfo : exifTags) {
|
||||
exifInterface.setAttribute(tagInfo[1], null);
|
||||
}
|
||||
|
||||
// these are not part of our tag list, remove by hand
|
||||
exifInterface.setAttribute(ExifInterface.TAG_GPS_LATITUDE, null);
|
||||
exifInterface.setAttribute(ExifInterface.TAG_GPS_LONGITUDE, null);
|
||||
exifInterface.setAttribute(ExifInterface.TAG_GPS_ALTITUDE, null);
|
||||
}
|
||||
|
||||
public static Bitmap generateSimulatorPhoto(int width, int height) {
|
||||
Bitmap fakePhoto = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
|
||||
Canvas canvas = new Canvas(fakePhoto);
|
||||
|
||||
@ -27,10 +27,10 @@ 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 Bitmap mBitmap;
|
||||
private byte[] mImageData;
|
||||
private ReadableMap mOptions;
|
||||
private File mCacheDirectory;
|
||||
private Bitmap mBitmap;
|
||||
private int mDeviceOrientation;
|
||||
private PictureSavedDelegate mPictureSavedDelegate;
|
||||
|
||||
@ -47,115 +47,109 @@ public class ResolveTakenPictureAsyncTask extends AsyncTask<Void, Void, Writable
|
||||
return (int) (mOptions.getDouble("quality") * 100);
|
||||
}
|
||||
|
||||
// loads bitmap only if necessary
|
||||
private void loadBitmap() throws IOException {
|
||||
if(mBitmap == null){
|
||||
mBitmap = BitmapFactory.decodeByteArray(mImageData, 0, mImageData.length);
|
||||
}
|
||||
if(mBitmap == null){
|
||||
throw new IOException("Failed to decode Image Bitmap");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected WritableMap doInBackground(Void... voids) {
|
||||
WritableMap response = Arguments.createMap();
|
||||
ByteArrayInputStream inputStream = null;
|
||||
ExifInterface exifInterface = null;
|
||||
WritableMap exifData = null;
|
||||
ReadableMap exifExtraData = null;
|
||||
|
||||
boolean orientationChanged = false;
|
||||
|
||||
response.putInt("deviceOrientation", mDeviceOrientation);
|
||||
response.putInt("pictureOrientation", mOptions.hasKey("orientation") ? mOptions.getInt("orientation") : mDeviceOrientation);
|
||||
|
||||
if (mOptions.hasKey("skipProcessing")) {
|
||||
try {
|
||||
// Prepare file output
|
||||
File imageFile = new File(RNFileUtils.getOutputFilePath(mCacheDirectory, ".jpg"));
|
||||
imageFile.createNewFile();
|
||||
FileOutputStream fOut = new FileOutputStream(imageFile);
|
||||
|
||||
// Save byte array (it is already a JPEG)
|
||||
fOut.write(mImageData);
|
||||
try{
|
||||
// this replaces the skipProcessing flag, we will process only if needed, and in
|
||||
// an orderly manner, so that skipProcessing is the default behaviour if no options are given
|
||||
// and this behaves more like the iOS version.
|
||||
// We will load all data lazily only when needed.
|
||||
|
||||
// get image size
|
||||
if (mBitmap == null) {
|
||||
mBitmap = BitmapFactory.decodeByteArray(mImageData, 0, mImageData.length);
|
||||
// this should not incurr in any overhead if not read/used
|
||||
inputStream = new ByteArrayInputStream(mImageData);
|
||||
|
||||
|
||||
// Rotate the bitmap to the proper orientation if requested
|
||||
if(mOptions.hasKey("fixOrientation") && mOptions.getBoolean("fixOrientation")){
|
||||
|
||||
exifInterface = new ExifInterface(inputStream);
|
||||
|
||||
// Get orientation of the image from mImageData via inputStream
|
||||
int orientation = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_UNDEFINED);
|
||||
|
||||
if(orientation != ExifInterface.ORIENTATION_UNDEFINED){
|
||||
loadBitmap();
|
||||
mBitmap = rotateBitmap(mBitmap, getImageRotation(orientation));
|
||||
orientationChanged = true;
|
||||
}
|
||||
if(mBitmap == null){
|
||||
throw new IOException("Failed to decode Image bitmap.");
|
||||
}
|
||||
|
||||
response.putInt("width", mBitmap.getWidth());
|
||||
response.putInt("height", mBitmap.getHeight());
|
||||
|
||||
// Return file system URI
|
||||
String fileUri = Uri.fromFile(imageFile).toString();
|
||||
response.putString("uri", fileUri);
|
||||
|
||||
} catch (Resources.NotFoundException e) {
|
||||
response = null; // do not resolve
|
||||
mPromise.reject(ERROR_TAG, "Documents directory of the app could not be found.", e);
|
||||
e.printStackTrace();
|
||||
} catch (IOException e) {
|
||||
response = null; // do not resolve
|
||||
mPromise.reject(ERROR_TAG, "An unknown I/O exception has occurred.", e);
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
if (mOptions.hasKey("width")) {
|
||||
loadBitmap();
|
||||
mBitmap = resizeBitmap(mBitmap, mOptions.getInt("width"));
|
||||
}
|
||||
|
||||
// we need the stream only for photos from a device
|
||||
if (mBitmap == null) {
|
||||
mBitmap = BitmapFactory.decodeByteArray(mImageData, 0, mImageData.length);
|
||||
inputStream = new ByteArrayInputStream(mImageData);
|
||||
}
|
||||
if (mOptions.hasKey("mirrorImage") && mOptions.getBoolean("mirrorImage")) {
|
||||
loadBitmap();
|
||||
mBitmap = flipHorizontally(mBitmap);
|
||||
}
|
||||
|
||||
try {
|
||||
WritableMap fileExifData = null;
|
||||
|
||||
if (inputStream != null) {
|
||||
ExifInterface exifInterface = new ExifInterface(inputStream);
|
||||
// Get orientation of the image from mImageData via inputStream
|
||||
int orientation = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION,
|
||||
ExifInterface.ORIENTATION_UNDEFINED);
|
||||
// EXIF code - we will adjust exif info later if we manipulated the bitmap
|
||||
boolean writeExifToResponse = mOptions.hasKey("exif") && mOptions.getBoolean("exif");
|
||||
|
||||
// Rotate the bitmap to the proper orientation if needed
|
||||
boolean fixOrientation = mOptions.hasKey("fixOrientation")
|
||||
&& mOptions.getBoolean("fixOrientation")
|
||||
&& orientation != ExifInterface.ORIENTATION_UNDEFINED;
|
||||
if (fixOrientation) {
|
||||
mBitmap = rotateBitmap(mBitmap, getImageRotation(orientation));
|
||||
// default to true if not provided so it is consistent with iOS and with what happens if no
|
||||
// processing is done and the image is saved as is.
|
||||
boolean writeExifToFile = true;
|
||||
|
||||
if (mOptions.hasKey("writeExif")) {
|
||||
switch (mOptions.getType("writeExif")) {
|
||||
case Boolean:
|
||||
writeExifToFile = mOptions.getBoolean("writeExif");
|
||||
break;
|
||||
case Map:
|
||||
exifExtraData = mOptions.getMap("writeExif");
|
||||
writeExifToFile = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (mOptions.hasKey("width")) {
|
||||
mBitmap = resizeBitmap(mBitmap, mOptions.getInt("width"));
|
||||
}
|
||||
// Read Exif data if needed
|
||||
if (writeExifToResponse || writeExifToFile) {
|
||||
|
||||
if (mOptions.hasKey("mirrorImage") && mOptions.getBoolean("mirrorImage")) {
|
||||
mBitmap = flipHorizontally(mBitmap);
|
||||
}
|
||||
|
||||
WritableMap exifData = null;
|
||||
ReadableMap exifExtraData = null;
|
||||
boolean writeExifToResponse = mOptions.hasKey("exif") && mOptions.getBoolean("exif");
|
||||
boolean writeExifToFile = false;
|
||||
if (mOptions.hasKey("writeExif")) {
|
||||
switch (mOptions.getType("writeExif")) {
|
||||
case Boolean:
|
||||
writeExifToFile = mOptions.getBoolean("writeExif");
|
||||
break;
|
||||
case Map:
|
||||
exifExtraData = mOptions.getMap("writeExif");
|
||||
writeExifToFile = true;
|
||||
break;
|
||||
// if we manipulated the image, or need to add extra data, or need to add it to the response,
|
||||
// then we need to load the actual exif data.
|
||||
// Otherwise we can just use w/e exif data we have right now in our byte array
|
||||
if(mBitmap != null || exifExtraData != null || writeExifToResponse){
|
||||
if(exifInterface == null){
|
||||
exifInterface = new ExifInterface(inputStream);
|
||||
}
|
||||
}
|
||||
|
||||
// Read Exif data if needed
|
||||
if (writeExifToResponse || writeExifToFile) {
|
||||
exifData = RNCameraViewHelper.getExifData(exifInterface);
|
||||
|
||||
if(exifExtraData != null){
|
||||
exifData.merge(exifExtraData);
|
||||
}
|
||||
}
|
||||
|
||||
// Write Exif data to output file if requested
|
||||
if (writeExifToFile) {
|
||||
fileExifData = Arguments.createMap();
|
||||
fileExifData.merge(exifData);
|
||||
fileExifData.putInt("width", mBitmap.getWidth());
|
||||
fileExifData.putInt("height", mBitmap.getHeight());
|
||||
if (fixOrientation) {
|
||||
fileExifData.putInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL);
|
||||
}
|
||||
if (exifExtraData != null) {
|
||||
fileExifData.merge(exifExtraData);
|
||||
// if we did anything to the bitmap, adjust exif
|
||||
if(mBitmap != null){
|
||||
exifData.putInt("width", mBitmap.getWidth());
|
||||
exifData.putInt("height", mBitmap.getHeight());
|
||||
|
||||
if(orientationChanged){
|
||||
exifData.putInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL);
|
||||
}
|
||||
}
|
||||
|
||||
@ -165,47 +159,108 @@ public class ResolveTakenPictureAsyncTask extends AsyncTask<Void, Void, Writable
|
||||
}
|
||||
}
|
||||
|
||||
// Upon rotating, write the image's dimensions to the response
|
||||
response.putInt("width", mBitmap.getWidth());
|
||||
response.putInt("height", mBitmap.getHeight());
|
||||
|
||||
// Cache compressed image in imageStream
|
||||
ByteArrayOutputStream imageStream = new ByteArrayOutputStream();
|
||||
mBitmap.compress(Bitmap.CompressFormat.JPEG, getQuality(), imageStream);
|
||||
|
||||
// Write compressed image to file in cache directory unless otherwise specified
|
||||
if (!mOptions.hasKey("doNotSave") || !mOptions.getBoolean("doNotSave")) {
|
||||
String filePath = writeStreamToFile(imageStream);
|
||||
if (fileExifData != null) {
|
||||
ExifInterface fileExifInterface = new ExifInterface(filePath);
|
||||
RNCameraViewHelper.setExifData(fileExifInterface, fileExifData);
|
||||
fileExifInterface.saveAttributes();
|
||||
// final processing
|
||||
// Based on whether or not we loaded the full bitmap into memory, final processing differs
|
||||
if(mBitmap == null){
|
||||
|
||||
// set response dimensions. If we haven't read our bitmap, get it efficiently
|
||||
// without loading the actual bitmap into memory
|
||||
BitmapFactory.Options options = new BitmapFactory.Options();
|
||||
options.inJustDecodeBounds = true;
|
||||
BitmapFactory.decodeByteArray(mImageData, 0, mImageData.length, options);
|
||||
if(options != null){
|
||||
response.putInt("width", options.outWidth);
|
||||
response.putInt("height", options.outHeight);
|
||||
}
|
||||
File imageFile = new File(filePath);
|
||||
String fileUri = Uri.fromFile(imageFile).toString();
|
||||
response.putString("uri", fileUri);
|
||||
}
|
||||
|
||||
// Write base64-encoded image to the response if requested
|
||||
if (mOptions.hasKey("base64") && mOptions.getBoolean("base64")) {
|
||||
response.putString("base64", Base64.encodeToString(imageStream.toByteArray(), Base64.NO_WRAP));
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
imageStream.close();
|
||||
if (inputStream != null) {
|
||||
inputStream.close();
|
||||
inputStream = null;
|
||||
// save to file if requested
|
||||
if (!mOptions.hasKey("doNotSave") || !mOptions.getBoolean("doNotSave")) {
|
||||
|
||||
// Prepare file output
|
||||
File imageFile = new File(RNFileUtils.getOutputFilePath(mCacheDirectory, ".jpg"));
|
||||
imageFile.createNewFile();
|
||||
FileOutputStream fOut = new FileOutputStream(imageFile);
|
||||
|
||||
// Save byte array (it is already a JPEG)
|
||||
fOut.write(mImageData);
|
||||
fOut.flush();
|
||||
fOut.close();
|
||||
|
||||
// update exif data if needed.
|
||||
// Since we didn't modify the image, we only update if we have extra exif info
|
||||
if (writeExifToFile && exifExtraData != null) {
|
||||
ExifInterface fileExifInterface = new ExifInterface(imageFile.getAbsolutePath());
|
||||
RNCameraViewHelper.setExifData(fileExifInterface, exifExtraData);
|
||||
fileExifInterface.saveAttributes();
|
||||
}
|
||||
else if (!writeExifToFile){
|
||||
// if we were requested to NOT store exif, we actually need to
|
||||
// clear the exif tags
|
||||
ExifInterface fileExifInterface = new ExifInterface(imageFile.getAbsolutePath());
|
||||
RNCameraViewHelper.clearExifData(fileExifInterface);
|
||||
fileExifInterface.saveAttributes();
|
||||
}
|
||||
// else: exif is unmodified, no need to update anything
|
||||
|
||||
// Return file system URI
|
||||
String fileUri = Uri.fromFile(imageFile).toString();
|
||||
response.putString("uri", fileUri);
|
||||
}
|
||||
|
||||
if (mOptions.hasKey("base64") && mOptions.getBoolean("base64")) {
|
||||
response.putString("base64", Base64.encodeToString(mImageData, Base64.NO_WRAP));
|
||||
}
|
||||
|
||||
}
|
||||
else{
|
||||
|
||||
// get response dimensions right from the bitmap if we have it
|
||||
response.putInt("width", mBitmap.getWidth());
|
||||
response.putInt("height", mBitmap.getHeight());
|
||||
|
||||
// Cache compressed image in imageStream
|
||||
ByteArrayOutputStream imageStream = new ByteArrayOutputStream();
|
||||
mBitmap.compress(Bitmap.CompressFormat.JPEG, getQuality(), imageStream);
|
||||
|
||||
|
||||
// Write compressed image to file in cache directory unless otherwise specified
|
||||
if (!mOptions.hasKey("doNotSave") || !mOptions.getBoolean("doNotSave")) {
|
||||
String filePath = writeStreamToFile(imageStream);
|
||||
|
||||
// since we lost any exif data on bitmap creation, we only need
|
||||
// to add it if requested
|
||||
if (writeExifToFile && exifData != null) {
|
||||
ExifInterface fileExifInterface = new ExifInterface(filePath);
|
||||
RNCameraViewHelper.setExifData(fileExifInterface, exifData);
|
||||
fileExifInterface.saveAttributes();
|
||||
}
|
||||
File imageFile = new File(filePath);
|
||||
String fileUri = Uri.fromFile(imageFile).toString();
|
||||
response.putString("uri", fileUri);
|
||||
}
|
||||
|
||||
// Write base64-encoded image to the response if requested
|
||||
if (mOptions.hasKey("base64") && mOptions.getBoolean("base64")) {
|
||||
response.putString("base64", Base64.encodeToString(imageStream.toByteArray(), Base64.NO_WRAP));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return response;
|
||||
} catch (Resources.NotFoundException e) {
|
||||
|
||||
}
|
||||
catch (Resources.NotFoundException e) {
|
||||
mPromise.reject(ERROR_TAG, "Documents directory of the app could not be found.", e);
|
||||
e.printStackTrace();
|
||||
} catch (IOException e) {
|
||||
}
|
||||
catch (IOException e) {
|
||||
mPromise.reject(ERROR_TAG, "An unknown I/O exception has occurred.", e);
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
}
|
||||
finally {
|
||||
try {
|
||||
if (inputStream != null) {
|
||||
inputStream.close();
|
||||
@ -215,7 +270,6 @@ public class ResolveTakenPictureAsyncTask extends AsyncTask<Void, Void, Writable
|
||||
}
|
||||
}
|
||||
|
||||
// An exception had to occur, promise has already been rejected. Do not try to resolve it again.
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@ -25,11 +25,10 @@ interface TakePictureOptions {
|
||||
mirrorImage?: boolean;
|
||||
doNotSave?: boolean;
|
||||
pauseAfterCapture?: boolean;
|
||||
writeExif?: boolean | { [name: string]: any };
|
||||
|
||||
/** Android only */
|
||||
skipProcessing?: boolean;
|
||||
fixOrientation?: boolean;
|
||||
writeExif?: boolean | { [name: string]: any };
|
||||
|
||||
/** iOS only */
|
||||
forceUpOrientation?: boolean;
|
||||
|
||||
@ -524,7 +524,7 @@ Method to be called when text is detected. Receives a Text Recognized Event obje
|
||||
|
||||
### `takePictureAsync([options]): Promise`
|
||||
|
||||
Takes a picture, saves in your app's cache directory and returns a promise.
|
||||
Takes a picture, saves in your app's cache directory and returns a promise. Note: additional image processing, such as mirror, orientation, and width, can be significantly slow on Android.
|
||||
|
||||
Supported options:
|
||||
|
||||
@ -536,14 +536,21 @@ Supported options:
|
||||
|
||||
- `mirrorImage` (boolean true or false). Use this with `true` if you want the resulting rendered picture to be mirrored (inverted in the vertical axis). If no value is specified `mirrorImage:false` is used.
|
||||
|
||||
- `writeExif`: (boolean or object, defaults to true). Setting this to a boolean indicates if the image exif should be preserved after capture, or removed. Setting it to an object, merges any data with the final exif output. This is useful, for example, to add GPS metadata (note that GPS info is correctly transalted from double values to the EXIF format, so there's no need to read the EXIF protocol).
|
||||
```js
|
||||
writeExif = {
|
||||
"GPSLatitude": latitude,
|
||||
"GPSLongitude": longitude,
|
||||
"GPSAltitude": altitude
|
||||
}
|
||||
```
|
||||
|
||||
- `exif` (boolean true or false) Use this with `true` if you want a exif data map of the picture taken on the return data of your promise. If no value is specified `exif:false` is used.
|
||||
|
||||
- `fixOrientation` (android only, boolean true or false) Use this with `true` if you want to fix incorrect image orientation (can take up to 5 seconds on some devices). Do not provide this if you only need EXIF based orientation.
|
||||
|
||||
- `forceUpOrientation` (iOS only, boolean true or false). This property allows to force portrait orientation based on actual data instead of exif data.
|
||||
|
||||
- `skipProcessing` (android only, boolean). This property skips all image processing on android, this makes taking photos super fast, but you loose some of the information, width, height and the ability to do some processing on the image (base64, width, quality, mirrorImage, exif, etc)
|
||||
|
||||
- `doNotSave` (boolean true or false). Use this with `true` if you do not want the picture to be saved as a file to cache. If no value is specified `doNotSave:false` is used. If you only need the base64 for the image, you can use this with `base64:true` and avoid having to save the file.
|
||||
|
||||
- `pauseAfterCapture` (boolean true or false). If true, pause the preview layer immediately after capturing the image. You will need to call `cameraRef.resumePreview()` before using the camera again. If no value is specified `pauseAfterCapture:false` is used.
|
||||
|
||||
@ -791,7 +791,15 @@ BOOL _sessionInterrupted = NO;
|
||||
// get image metadata so we can re-add it later
|
||||
// make it mutable since we need to adjust quality/compression
|
||||
CFDictionaryRef metaDict = CMCopyDictionaryOfAttachments(NULL, imageSampleBuffer, kCMAttachmentMode_ShouldPropagate);
|
||||
NSMutableDictionary *metadata = [(__bridge NSDictionary*)metaDict mutableCopy];
|
||||
|
||||
CFMutableDictionaryRef mutableMetaDict = CFDictionaryCreateMutableCopy(NULL, 0, metaDict);
|
||||
|
||||
// release the meta dict now that we've copied it
|
||||
// to Objective-C land
|
||||
CFRelease(metaDict);
|
||||
|
||||
// bridge the copy for auto release
|
||||
NSMutableDictionary *metadata = (NSMutableDictionary *)CFBridgingRelease(mutableMetaDict);
|
||||
|
||||
|
||||
// Get final JPEG image and set compression
|
||||
@ -813,7 +821,77 @@ BOOL _sessionInterrupted = NO;
|
||||
|
||||
CGImageDestinationRef destination = CGImageDestinationCreateWithData((__bridge CFMutableDataRef)destData, kUTTypeJPEG, 1, NULL);
|
||||
|
||||
CGImageDestinationAddImage(destination, takenImage.CGImage, (__bridge CFDictionaryRef) metadata);
|
||||
// defaults to true, must like Android
|
||||
bool writeExif = true;
|
||||
|
||||
if(options[@"writeExif"]){
|
||||
|
||||
// if we received an object, merge with our meta
|
||||
if ([options[@"writeExif"] isKindOfClass:[NSDictionary class]]){
|
||||
NSDictionary *newExif = options[@"writeExif"];
|
||||
|
||||
// need to update both, since apple splits data
|
||||
// across exif and tiff dicts. No problems with duplicates
|
||||
// they will be handled appropiately.
|
||||
NSMutableDictionary *exif = metadata[(NSString*)kCGImagePropertyExifDictionary];
|
||||
|
||||
NSMutableDictionary *tiff = metadata[(NSString*)kCGImagePropertyTIFFDictionary];
|
||||
|
||||
|
||||
// initialize exif dict if not built
|
||||
if(!exif){
|
||||
exif = [[NSMutableDictionary alloc] init];
|
||||
metadata[(NSString*)kCGImagePropertyExifDictionary] = exif;
|
||||
}
|
||||
|
||||
if(!tiff){
|
||||
tiff = [[NSMutableDictionary alloc] init];
|
||||
metadata[(NSString*)kCGImagePropertyTIFFDictionary] = exif;
|
||||
}
|
||||
|
||||
// merge new exif info
|
||||
[exif addEntriesFromDictionary:newExif];
|
||||
[tiff addEntriesFromDictionary:newExif];
|
||||
|
||||
|
||||
// correct any GPS metadata like Android does
|
||||
// need to get the right format for each value.
|
||||
NSMutableDictionary *gpsDict = [[NSMutableDictionary alloc] init];
|
||||
|
||||
if(newExif[@"GPSLatitude"]){
|
||||
gpsDict[(NSString *)kCGImagePropertyGPSLatitude] = @(fabs([newExif[@"GPSLatitude"] floatValue]));
|
||||
|
||||
gpsDict[(NSString *)kCGImagePropertyGPSLatitudeRef] = [newExif[@"GPSLatitude"] floatValue] >= 0 ? @"N" : @"S";
|
||||
|
||||
}
|
||||
if(newExif[@"GPSLongitude"]){
|
||||
gpsDict[(NSString *)kCGImagePropertyGPSLongitude] = @(fabs([newExif[@"GPSLongitude"] floatValue]));
|
||||
|
||||
gpsDict[(NSString *)kCGImagePropertyGPSLongitudeRef] = [newExif[@"GPSLongitude"] floatValue] >= 0 ? @"E" : @"W";
|
||||
}
|
||||
if(newExif[@"GPSAltitude"]){
|
||||
gpsDict[(NSString *)kCGImagePropertyGPSAltitude] = @(fabs([newExif[@"GPSAltitude"] floatValue]));
|
||||
|
||||
gpsDict[(NSString *)kCGImagePropertyGPSAltitudeRef] = [newExif[@"GPSAltitude"] floatValue] >= 0 ? @(0) : @(1);
|
||||
}
|
||||
|
||||
// if we don't have gps info, add it
|
||||
// otherwise, merge it
|
||||
if(!metadata[(NSString *)kCGImagePropertyGPSDictionary]){
|
||||
metadata[(NSString *)kCGImagePropertyGPSDictionary] = gpsDict;
|
||||
}
|
||||
else{
|
||||
[metadata[(NSString *)kCGImagePropertyGPSDictionary] addEntriesFromDictionary:gpsDict];
|
||||
}
|
||||
|
||||
}
|
||||
else{
|
||||
writeExif = [options[@"writeExif"] boolValue];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
CGImageDestinationAddImage(destination, takenImage.CGImage, writeExif ? ((__bridge CFDictionaryRef) metadata) : nil);
|
||||
|
||||
|
||||
// write final image data with metadata to our destination
|
||||
@ -1490,7 +1568,7 @@ BOOL _sessionInterrupted = NO;
|
||||
dispatch_async(self.sessionQueue, ^{
|
||||
[self removeAudioCaptureSessionInput];
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
3
types/index.d.ts
vendored
3
types/index.d.ts
vendored
@ -362,11 +362,10 @@ interface TakePictureOptions {
|
||||
mirrorImage?: boolean;
|
||||
doNotSave?: boolean;
|
||||
pauseAfterCapture?: boolean;
|
||||
writeExif?: boolean | { [name: string]: any };
|
||||
|
||||
/** Android only */
|
||||
skipProcessing?: boolean;
|
||||
fixOrientation?: boolean;
|
||||
writeExif?: boolean | { [name: string]: any };
|
||||
|
||||
/** iOS only */
|
||||
forceUpOrientation?: boolean;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user