extract exif data from image

This commit is contained in:
Ivan Pusic 2017-09-27 20:25:41 +02:00
parent aa54cdc3e0
commit d3da5de487
14 changed files with 5798 additions and 5112 deletions

View File

@ -49,7 +49,7 @@ ImagePicker.openPicker({
});
```
**Android: The prop 'cropping' has been known to cause videos not to be display in the gallery on Android. Please do not set cropping to true when selecting videos.**
**Android: The prop 'cropping' has been known to cause videos not to be display in the gallery on Android. Please do not set cropping to true when selecting videos.**
### Select from camera
@ -94,6 +94,7 @@ ImagePicker.clean().then(() => {
| height | number | Height of result image when used with `cropping` option |
| multiple | bool (default false) | Enable or disable multiple image selection |
| includeBase64 | bool (default false) | Enable or disable returning base64 data with image |
| includeExif | bool (default false) | Include image exif data in the response |
| cropperActiveWidgetColor (android only) | string (default `"#424242"`) | When cropping image, determines ActiveWidget color. |
| cropperStatusBarColor (android only) | string (default `#424242`) | When cropping image, determines the color of StatusBar. |
| cropperToolbarColor (android only) | string (default `#424242`) | When cropping image, determines the color of Toolbar. |
@ -126,6 +127,7 @@ ImagePicker.clean().then(() => {
| mime | string | Selected image MIME type (image/jpeg, image/png) |
| size | number | Selected image size in bytes |
| data | base64 | Optional base64 selected file representation |
| exif | object | Extracted exif data from image. Response format is platform specific |
# Install

View File

@ -0,0 +1,71 @@
package com.reactnative.ivpusic.imagepicker;
import android.media.ExifInterface;
import android.os.Build;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.WritableNativeMap;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import static android.media.ExifInterface.*;
public class ExifExtractor {
public static WritableMap extract(String path) throws IOException {
WritableMap exifData = new WritableNativeMap();
List<String> attributes = getBasicAttributes();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
attributes.addAll(getLevel23Attributes());
}
ExifInterface exif = new ExifInterface(path);
for (String attribute : attributes) {
String value = exif.getAttribute(attribute);
exifData.putString(attribute, value);
}
return exifData;
}
private static List<String> getBasicAttributes() {
return new ArrayList<>(Arrays.asList(
TAG_APERTURE,
TAG_DATETIME,
TAG_EXPOSURE_TIME,
TAG_FLASH,
TAG_FOCAL_LENGTH,
TAG_GPS_ALTITUDE,
TAG_GPS_ALTITUDE_REF,
TAG_GPS_DATESTAMP,
TAG_GPS_LATITUDE,
TAG_GPS_LATITUDE_REF,
TAG_GPS_LONGITUDE,
TAG_GPS_LONGITUDE_REF,
TAG_GPS_PROCESSING_METHOD,
TAG_GPS_TIMESTAMP,
TAG_IMAGE_LENGTH,
TAG_IMAGE_WIDTH,
TAG_ISO,
TAG_MAKE,
TAG_MODEL,
TAG_ORIENTATION,
TAG_WHITE_BALANCE
));
}
private static List<String> getLevel23Attributes() {
return new ArrayList<>(Arrays.asList(
TAG_DATETIME_DIGITIZED,
TAG_SUBSEC_TIME,
TAG_SUBSEC_TIME_DIG,
TAG_SUBSEC_TIME_ORIG
));
}
}

View File

@ -67,6 +67,7 @@ class PickerModule extends ReactContextBaseJavaModule implements ActivityEventLi
private String mediaType = "any";
private boolean multiple = false;
private boolean includeBase64 = false;
private boolean includeExif = false;
private boolean cropping = false;
private boolean cropperCircleOverlay = false;
private boolean showCropGuidelines = true;
@ -112,6 +113,7 @@ class PickerModule extends ReactContextBaseJavaModule implements ActivityEventLi
mediaType = options.hasKey("mediaType") ? options.getString("mediaType") : mediaType;
multiple = options.hasKey("multiple") && options.getBoolean("multiple");
includeBase64 = options.hasKey("includeBase64") && options.getBoolean("includeBase64");
includeExif = options.hasKey("includeExif") && options.getBoolean("includeExif");
width = options.hasKey("width") ? options.getInt("width") : width;
height = options.hasKey("height") ? options.getInt("height") : height;
cropping = options.hasKey("cropping") ? options.getBoolean("cropping") : cropping;
@ -539,6 +541,15 @@ class PickerModule extends ReactContextBaseJavaModule implements ActivityEventLi
image.putString("data", getBase64StringFromFile(compressedImagePath));
}
if (includeExif) {
try {
WritableMap exif = ExifExtractor.extract(path);
image.putMap("exif", exif);
} catch (Exception ex) {
ex.printStackTrace();
}
}
return image;
}

View File

@ -40,6 +40,7 @@ export default class App extends Component {
cropping: cropping,
width: 500,
height: 500,
includeExif: true,
}).then(image => {
console.log('received image', image);
this.setState({
@ -54,7 +55,8 @@ export default class App extends Component {
width: 300,
height: 300,
cropping: cropit,
includeBase64: true
includeBase64: true,
includeExif: true,
}).then(image => {
console.log('received base64 image');
this.setState({
@ -114,6 +116,7 @@ export default class App extends Component {
compressImageMaxHeight: 480,
compressImageQuality: 0.5,
compressVideoPreset: 'MediumQuality',
includeExif: true,
}).then(image => {
console.log('received image', image);
this.setState({
@ -129,7 +132,8 @@ export default class App extends Component {
pickMultiple() {
ImagePicker.openPicker({
multiple: true,
waitAnimationEnd: false
waitAnimationEnd: false,
includeExif: true,
}).then(images => {
this.setState({
image: null,

View File

@ -472,6 +472,14 @@
name = Products;
sourceTree = "<group>";
};
3468219D1F7BAED90093F7F5 /* Recovered References */ = {
isa = PBXGroup;
children = (
5B590311A8214ACA88447B83 /* libRCTVideo.a */,
);
name = "Recovered References";
sourceTree = "<group>";
};
34A9DDB71D7F43220012B1F5 /* ImageCropPickerSDK */ = {
isa = PBXGroup;
children = (
@ -485,7 +493,7 @@
isa = PBXGroup;
children = (
34F74E351E0733E3001D9901 /* libRCTAnimation.a */,
34F74E371E0733E3001D9901 /* libRCTAnimation-tvOS.a */,
34F74E371E0733E3001D9901 /* libRCTAnimation.a */,
);
name = Products;
sourceTree = "<group>";
@ -536,6 +544,7 @@
832341AE1AAA6A7D00B99B32 /* Libraries */,
00E356EF1AD99517003FC87E /* exampleTests */,
83CBBA001A601CBA00E9B192 /* Products */,
3468219D1F7BAED90093F7F5 /* Recovered References */,
);
indentWidth = 2;
sourceTree = "<group>";
@ -836,7 +845,7 @@
remoteRef = 34F74E341E0733E3001D9901 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
34F74E371E0733E3001D9901 /* libRCTAnimation-tvOS.a */ = {
34F74E371E0733E3001D9901 /* libRCTAnimation.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
path = libRCTAnimation.a;

View File

@ -54,6 +54,7 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
language = ""
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
<TestableReference
@ -83,6 +84,7 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
language = ""
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"

9
example/jsconfig.json Normal file
View File

@ -0,0 +1,9 @@
ir{
"compilerOptions": {
"allowJs": true,
"allowSyntheticDefaultImports": true
},
"exclude": [
"node_modules"
]
}

5522
example/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -7,7 +7,7 @@
},
"dependencies": {
"react": "16.0.0-alpha.12",
"react-native": "0.47.0",
"react-native": "0.48.4",
"react-native-image-crop-picker": "../",
"react-native-video": "git://github.com/react-native-community/react-native-video.git"
}

File diff suppressed because it is too large Load Diff

2
index.d.ts vendored
View File

@ -6,6 +6,7 @@ declare module "react-native-image-crop-picker" {
multiple?: boolean;
path?: string;
includeBase64?: boolean;
includeExif?: boolean;
cropperTintColor?: string;
cropperCircleOverlay?: boolean;
maxFiles?: number;
@ -31,6 +32,7 @@ declare module "react-native-image-crop-picker" {
width: number;
height: number;
mime: string;
exif: null | object;
}
export function openPicker(options: Options): Promise<Image | Image[]>;

View File

@ -31,10 +31,18 @@
#import <math.h>
@interface ImageCropPicker : NSObject<
RCTBridgeModule,
QBImagePickerControllerDelegate,
RSKImageCropViewControllerDelegate,
RSKImageCropViewControllerDataSource>
UIImagePickerControllerDelegate,
UINavigationControllerDelegate,
RCTBridgeModule,
QBImagePickerControllerDelegate,
RSKImageCropViewControllerDelegate,
RSKImageCropViewControllerDataSource>
typedef enum selectionMode {
CAMERA,
CROPPING,
PICKER
} SelectionMode;
@property (nonatomic, strong) NSMutableDictionary *croppingFile;
@property (nonatomic, strong) NSDictionary *defaultOptions;
@ -42,7 +50,7 @@
@property (nonatomic, retain) NSMutableDictionary *options;
@property (nonatomic, strong) RCTPromiseResolveBlock resolve;
@property (nonatomic, strong) RCTPromiseRejectBlock reject;
@property BOOL cropOnly;
@property SelectionMode currentSelectionMode;
@end

View File

@ -51,6 +51,7 @@ RCT_EXPORT_MODULE();
@"cropping": @NO,
@"cropperCircleOverlay": @NO,
@"includeBase64": @NO,
@"includeExif": @NO,
@"compressVideo": @YES,
@"minFiles": @1,
@"maxFiles": @5,
@ -66,7 +67,7 @@ RCT_EXPORT_MODULE();
};
self.compression = [[Compression alloc] init];
}
return self;
}
@ -74,11 +75,11 @@ RCT_EXPORT_MODULE();
if ([[self.options objectForKey:@"waitAnimationEnd"] boolValue]) {
return completion;
}
if (completion != nil) {
completion();
}
return nil;
}
@ -101,7 +102,7 @@ RCT_EXPORT_MODULE();
- (void) setConfiguration:(NSDictionary *)options
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject {
self.resolve = resolve;
self.reject = reject;
self.options = [NSMutableDictionary dictionaryWithDictionary:self.defaultOptions];
@ -115,17 +116,17 @@ RCT_EXPORT_MODULE();
while (root.presentedViewController != nil) {
root = root.presentedViewController;
}
return root;
}
RCT_EXPORT_METHOD(openCamera:(NSDictionary *)options
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject) {
[self setConfiguration:options resolver:resolve rejecter:reject];
self.cropOnly = NO;
self.currentSelectionMode = CAMERA;
#if TARGET_IPHONE_SIMULATOR
self.reject(ERROR_PICKER_CANNOT_RUN_CAMERA_ON_SIMULATOR_KEY, ERROR_PICKER_CANNOT_RUN_CAMERA_ON_SIMULATOR_MSG, nil);
return;
@ -135,7 +136,7 @@ RCT_EXPORT_METHOD(openCamera:(NSDictionary *)options
self.reject(ERROR_PICKER_NO_CAMERA_PERMISSION_KEY, ERROR_PICKER_NO_CAMERA_PERMISSION_MSG, nil);
return;
}
UIImagePickerController *picker = [[UIImagePickerController alloc] init];
picker.delegate = self;
picker.allowsEditing = NO;
@ -143,7 +144,7 @@ RCT_EXPORT_METHOD(openCamera:(NSDictionary *)options
if ([[self.options objectForKey:@"useFrontCamera"] boolValue]) {
picker.cameraDevice = UIImagePickerControllerCameraDeviceFront;
}
dispatch_async(dispatch_get_main_queue(), ^{
[[self getRootVC] presentViewController:picker animated:YES completion:nil];
});
@ -151,11 +152,20 @@ RCT_EXPORT_METHOD(openCamera:(NSDictionary *)options
#endif
}
- (void)viewDidLoad {
[self viewDidLoad];
}
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info {
UIImage *chosenImage = [info objectForKey:UIImagePickerControllerOriginalImage];
UIImage *chosenImageT = [chosenImage fixOrientation];
[self processSingleImagePick:chosenImageT withViewController:picker withSourceURL:self.croppingFile[@"sourceURL"] withLocalIdentifier:self.croppingFile[@"localIdentifier"] withFilename:self.croppingFile[@"filename"]];
NSDictionary *exif;
if([[self.options objectForKey:@"includeExif"] boolValue]) {
exif = [info objectForKey:UIImagePickerControllerMediaMetadata];
}
[self processSingleImagePick:chosenImageT withExif:exif withViewController:picker withSourceURL:self.croppingFile[@"sourceURL"] withLocalIdentifier:self.croppingFile[@"localIdentifier"] withFilename:self.croppingFile[@"filename"]];
}
- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker {
@ -167,38 +177,38 @@ RCT_EXPORT_METHOD(openCamera:(NSDictionary *)options
- (NSString*) getTmpDirectory {
NSString *TMP_DIRECTORY = @"react-native-image-crop-picker/";
NSString *tmpFullPath = [NSTemporaryDirectory() stringByAppendingString:TMP_DIRECTORY];
BOOL isDir;
BOOL exists = [[NSFileManager defaultManager] fileExistsAtPath:tmpFullPath isDirectory:&isDir];
if (!exists) {
[[NSFileManager defaultManager] createDirectoryAtPath: tmpFullPath
withIntermediateDirectories:YES attributes:nil error:nil];
}
return tmpFullPath;
}
- (BOOL)cleanTmpDirectory {
NSString* tmpDirectoryPath = [self getTmpDirectory];
NSArray* tmpDirectory = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:tmpDirectoryPath error:NULL];
for (NSString *file in tmpDirectory) {
BOOL deleted = [[NSFileManager defaultManager] removeItemAtPath:[NSString stringWithFormat:@"%@%@", tmpDirectoryPath, file] error:NULL];
if (!deleted) {
return NO;
}
}
return YES;
}
RCT_EXPORT_METHOD(cleanSingle:(NSString *) path
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject) {
BOOL deleted = [[NSFileManager defaultManager] removeItemAtPath:path error:NULL];
if (!deleted) {
reject(ERROR_CLEANUP_ERROR_KEY, ERROR_CLEANUP_ERROR_MSG, nil);
} else {
@ -218,16 +228,16 @@ RCT_REMAP_METHOD(clean, resolver:(RCTPromiseResolveBlock)resolve
RCT_EXPORT_METHOD(openPicker:(NSDictionary *)options
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject) {
[self setConfiguration:options resolver:resolve rejecter:reject];
self.cropOnly = NO;
self.currentSelectionMode = PICKER;
[PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) {
if (status != PHAuthorizationStatusAuthorized) {
self.reject(ERROR_PICKER_UNAUTHORIZED_KEY, ERROR_PICKER_UNAUTHORIZED_MSG, nil);
return;
}
dispatch_async(dispatch_get_main_queue(), ^{
// init picker
QBImagePickerController *imagePickerController =
@ -237,15 +247,15 @@ RCT_EXPORT_METHOD(openPicker:(NSDictionary *)options
imagePickerController.minimumNumberOfSelection = abs([[self.options objectForKey:@"minFiles"] intValue]);
imagePickerController.maximumNumberOfSelection = abs([[self.options objectForKey:@"maxFiles"] intValue]);
imagePickerController.showsNumberOfSelectedAssets = [[self.options objectForKey:@"showsSelectedCount"] boolValue];
if ([self.options objectForKey:@"smartAlbums"] != nil) {
NSDictionary *smartAlbums = @{
@"UserLibrary" : @(PHAssetCollectionSubtypeSmartAlbumUserLibrary),
@"PhotoStream" : @(PHAssetCollectionSubtypeAlbumMyPhotoStream),
@"Panoramas" : @(PHAssetCollectionSubtypeSmartAlbumPanoramas),
@"Videos" : @(PHAssetCollectionSubtypeSmartAlbumVideos),
@"Bursts" : @(PHAssetCollectionSubtypeSmartAlbumBursts),
};
@"UserLibrary" : @(PHAssetCollectionSubtypeSmartAlbumUserLibrary),
@"PhotoStream" : @(PHAssetCollectionSubtypeAlbumMyPhotoStream),
@"Panoramas" : @(PHAssetCollectionSubtypeSmartAlbumPanoramas),
@"Videos" : @(PHAssetCollectionSubtypeSmartAlbumVideos),
@"Bursts" : @(PHAssetCollectionSubtypeSmartAlbumBursts),
};
NSMutableArray *albumsToShow = [NSMutableArray arrayWithCapacity:5];
for (NSString* album in [self.options objectForKey:@"smartAlbums"]) {
if ([smartAlbums objectForKey:album] != nil) {
@ -254,12 +264,12 @@ RCT_EXPORT_METHOD(openPicker:(NSDictionary *)options
}
imagePickerController.assetCollectionSubtypes = albumsToShow;
}
if ([[self.options objectForKey:@"cropping"] boolValue]) {
imagePickerController.mediaType = QBImagePickerMediaTypeImage;
} else {
NSString *mediaType = [self.options objectForKey:@"mediaType"];
if ([mediaType isEqualToString:@"any"]) {
imagePickerController.mediaType = QBImagePickerMediaTypeAny;
} else if ([mediaType isEqualToString:@"photo"]) {
@ -267,9 +277,9 @@ RCT_EXPORT_METHOD(openPicker:(NSDictionary *)options
} else {
imagePickerController.mediaType = QBImagePickerMediaTypeVideo;
}
}
[[self getRootVC] presentViewController:imagePickerController animated:YES completion:nil];
});
}];
@ -278,12 +288,12 @@ RCT_EXPORT_METHOD(openPicker:(NSDictionary *)options
RCT_EXPORT_METHOD(openCropper:(NSDictionary *)options
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject) {
[self setConfiguration:options resolver:resolve rejecter:reject];
self.cropOnly = YES;
self.currentSelectionMode = CROPPING;
NSString *path = [options objectForKey:@"path"];
[self.bridge.imageLoader loadImageWithURLRequest:[RCTConvert NSURLRequest:path] callback:^(NSError *error, UIImage *image) {
if (error) {
self.reject(ERROR_CROPPER_IMAGE_NOT_FOUND_KEY, ERROR_CROPPER_IMAGE_NOT_FOUND_MSG, nil);
@ -305,7 +315,7 @@ RCT_EXPORT_METHOD(openCropper:(NSDictionary *)options
imageCropVC.delegate = self;
[imageCropVC setModalPresentationStyle:UIModalPresentationCustom];
[imageCropVC setModalTransitionStyle:UIModalTransitionStyleCrossDissolve];
dispatch_async(dispatch_get_main_queue(), ^{
[[self getRootVC] presentViewController:imageCropVC animated:YES completion:nil];
});
@ -314,18 +324,18 @@ RCT_EXPORT_METHOD(openCropper:(NSDictionary *)options
- (void)showActivityIndicator:(void (^)(UIActivityIndicatorView*, UIView*))handler {
dispatch_async(dispatch_get_main_queue(), ^{
UIView *mainView = [[self getRootVC] view];
// create overlay
UIView *loadingView = [[UIView alloc] initWithFrame:[UIScreen mainScreen].bounds];
loadingView.backgroundColor = [UIColor colorWithRed:0 green:0 blue:0 alpha:0.5];
loadingView.clipsToBounds = YES;
// create loading spinner
UIActivityIndicatorView *activityView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
activityView.frame = CGRectMake(65, 40, activityView.bounds.size.width, activityView.bounds.size.height);
activityView.center = loadingView.center;
[loadingView addSubview:activityView];
// create message
UILabel *loadingLabel = [[UILabel alloc] initWithFrame:CGRectMake(20, 115, 130, 22)];
loadingLabel.backgroundColor = [UIColor clearColor];
@ -334,15 +344,15 @@ RCT_EXPORT_METHOD(openCropper:(NSDictionary *)options
CGPoint loadingLabelLocation = loadingView.center;
loadingLabelLocation.y += [activityView bounds].size.height;
loadingLabel.center = loadingLabelLocation;
loadingLabel.textAlignment = UITextAlignmentCenter;
loadingLabel.textAlignment = NSTextAlignmentCenter;
loadingLabel.text = [self.options objectForKey:@"loadingLabelText"];
[loadingLabel setFont:[UIFont boldSystemFontOfSize:18]];
[loadingView addSubview:loadingLabel];
// show all
[mainView addSubview:loadingView];
[activityView startAnimating];
handler(activityView, loadingView);
});
}
@ -352,39 +362,40 @@ RCT_EXPORT_METHOD(openCropper:(NSDictionary *)options
PHImageManager *manager = [PHImageManager defaultManager];
PHVideoRequestOptions *options = [[PHVideoRequestOptions alloc] init];
options.version = PHVideoRequestOptionsVersionOriginal;
[manager
requestAVAssetForVideo:forAsset
options:options
resultHandler:^(AVAsset * asset, AVAudioMix * audioMix,
NSDictionary *info) {
NSURL *sourceURL = [(AVURLAsset *)asset URL];
// create temp file
NSString *tmpDirFullPath = [self getTmpDirectory];
NSString *filePath = [tmpDirFullPath stringByAppendingString:[[NSUUID UUID] UUIDString]];
filePath = [filePath stringByAppendingString:@".mp4"];
NSURL *outputURL = [NSURL fileURLWithPath:filePath];
[self.compression compressVideo:sourceURL outputURL:outputURL withOptions:self.options handler:^(AVAssetExportSession *exportSession) {
if (exportSession.status == AVAssetExportSessionStatusCompleted) {
AVAsset *compressedAsset = [AVAsset assetWithURL:outputURL];
AVAssetTrack *track = [[compressedAsset tracksWithMediaType:AVMediaTypeVideo] firstObject];
NSNumber *fileSizeValue = nil;
[outputURL getResourceValue:&fileSizeValue
forKey:NSURLFileSizeKey
error:nil];
completion([self createAttachmentResponse:[outputURL absoluteString]
withExif:nil
withSourceURL:[sourceURL absoluteString]
withLocalIdentifier: forAsset.localIdentifier
withFilename:[forAsset valueForKey:@"filename"]
withLocalIdentifier: forAsset.localIdentifier
withFilename:[forAsset valueForKey:@"filename"]
withWidth:[NSNumber numberWithFloat:track.naturalSize.width]
withHeight:[NSNumber numberWithFloat:track.naturalSize.height]
withMime:@"video/mp4"
withSize:fileSizeValue
withData:[NSNull null]]);
withData:nil]);
} else {
completion(nil);
}
@ -392,43 +403,44 @@ RCT_EXPORT_METHOD(openCropper:(NSDictionary *)options
}];
}
- (NSDictionary*) createAttachmentResponse:(NSString*)filePath withSourceURL:(NSString*)sourceURL withLocalIdentifier:(NSString*)localIdentifier withFilename:(NSString*)filename withWidth:(NSNumber*)width withHeight:(NSNumber*)height withMime:(NSString*)mime withSize:(NSNumber*)size withData:(NSString*)data {
- (NSDictionary*) createAttachmentResponse:(NSString*)filePath withExif:(NSDictionary*) exif withSourceURL:(NSString*)sourceURL withLocalIdentifier:(NSString*)localIdentifier withFilename:(NSString*)filename withWidth:(NSNumber*)width withHeight:(NSNumber*)height withMime:(NSString*)mime withSize:(NSNumber*)size withData:(NSString*)data {
return @{
@"path": filePath,
@"sourceURL": (sourceURL) ? sourceURL : @"",
@"localIdentifier": (localIdentifier) ? localIdentifier : @"",
@"filename": (filename) ? filename : @"",
@"sourceURL": (sourceURL) ? sourceURL : [NSNull null],
@"localIdentifier": (localIdentifier) ? localIdentifier : [NSNull null],
@"filename": (filename) ? filename : [NSNull null],
@"width": width,
@"height": height,
@"mime": mime,
@"size": size,
@"data": data,
@"data": (data) ? data : [NSNull null],
@"exif": (exif) ? exif : [NSNull null],
};
}
- (void)qb_imagePickerController:
(QBImagePickerController *)imagePickerController
didFinishPickingAssets:(NSArray *)assets {
PHImageManager *manager = [PHImageManager defaultManager];
PHImageRequestOptions* options = [[PHImageRequestOptions alloc] init];
options.synchronous = NO;
options.networkAccessAllowed = YES;
if ([[[self options] objectForKey:@"multiple"] boolValue]) {
NSMutableArray *selections = [[NSMutableArray alloc] init];
[self showActivityIndicator:^(UIActivityIndicatorView *indicatorView, UIView *overlayView) {
NSLock *lock = [[NSLock alloc] init];
__block int processed = 0;
for (PHAsset *phAsset in assets) {
if (phAsset.mediaType == PHAssetMediaTypeVideo) {
[self getVideoAsset:phAsset completion:^(NSDictionary* video) {
dispatch_async(dispatch_get_main_queue(), ^{
[lock lock];
if (video == nil) {
[indicatorView stopAnimating];
[overlayView removeFromSuperview];
@ -437,11 +449,11 @@ RCT_EXPORT_METHOD(openCropper:(NSDictionary *)options
}]];
return;
}
[selections addObject:video];
processed++;
[lock unlock];
if (processed == [assets count]) {
[indicatorView stopAnimating];
[overlayView removeFromSuperview];
@ -478,22 +490,28 @@ RCT_EXPORT_METHOD(openCropper:(NSDictionary *)options
return;
}
NSDictionary* exif = nil;
if([[self.options objectForKey:@"includeExif"] boolValue]) {
exif = [[CIImage imageWithData:imageData] properties];
}
[selections addObject:[self createAttachmentResponse:filePath
withSourceURL:[sourceURL absoluteString]
withExif: exif
withSourceURL:[sourceURL absoluteString]
withLocalIdentifier: phAsset.localIdentifier
withFilename: [phAsset valueForKey:@"filename"]
withWidth:imageResult.width
withHeight:imageResult.height
withMime:imageResult.mime
withSize:[NSNumber numberWithUnsignedInteger:imageResult.data.length]
withData:[[self.options objectForKey:@"includeBase64"] boolValue] ? [imageResult.data base64EncodedStringWithOptions:0] : [NSNull null]
withData:[[self.options objectForKey:@"includeBase64"] boolValue] ? [imageResult.data base64EncodedStringWithOptions:0]: nil
]];
}
processed++;
[lock unlock];
if (processed == [assets count]) {
[indicatorView stopAnimating];
[overlayView removeFromSuperview];
[imagePickerController dismissViewControllerAnimated:YES completion:[self waitAnimationEnd:^{
@ -508,7 +526,7 @@ RCT_EXPORT_METHOD(openCropper:(NSDictionary *)options
}];
} else {
PHAsset *phAsset = [assets objectAtIndex:0];
[self showActivityIndicator:^(UIActivityIndicatorView *indicatorView, UIView *overlayView) {
if (phAsset.mediaType == PHAssetMediaTypeVideo) {
[self getVideoAsset:phAsset completion:^(NSDictionary* video) {
@ -532,10 +550,21 @@ RCT_EXPORT_METHOD(openCropper:(NSDictionary *)options
UIImageOrientation orientation,
NSDictionary *info) {
NSURL *sourceURL = [info objectForKey:@"PHImageFileURLKey"];
NSDictionary* exif;
if([[self.options objectForKey:@"includeExif"] boolValue]) {
exif = [[CIImage imageWithData:imageData] properties];
}
dispatch_async(dispatch_get_main_queue(), ^{
[indicatorView stopAnimating];
[overlayView removeFromSuperview];
[self processSingleImagePick:[UIImage imageWithData:imageData] withViewController:imagePickerController withSourceURL:[sourceURL absoluteString] withLocalIdentifier:phAsset.localIdentifier withFilename:[phAsset valueForKey:@"filename"]];
[self processSingleImagePick:[UIImage imageWithData:imageData]
withExif: exif
withViewController:imagePickerController
withSourceURL:[sourceURL absoluteString]
withLocalIdentifier:phAsset.localIdentifier
withFilename:[phAsset valueForKey:@"filename"]];
});
}];
}
@ -552,8 +581,8 @@ RCT_EXPORT_METHOD(openCropper:(NSDictionary *)options
// when user selected single image, with camera or from photo gallery,
// this method will take care of attaching image metadata, and sending image to cropping controller
// or to user directly
- (void) processSingleImagePick:(UIImage*)image withViewController:(UIViewController*)viewController withSourceURL:(NSString*)sourceURL withLocalIdentifier:(NSString*)localIdentifier withFilename:(NSString*)filename {
- (void) processSingleImagePick:(UIImage*)image withExif:(NSDictionary*) exif withViewController:(UIViewController*)viewController withSourceURL:(NSString*)sourceURL withLocalIdentifier:(NSString*)localIdentifier withFilename:(NSString*)filename {
if (image == nil) {
[viewController dismissViewControllerAnimated:YES completion:[self waitAnimationEnd:^{
self.reject(ERROR_PICKER_NO_DATA_KEY, ERROR_PICKER_NO_DATA_MSG, nil);
@ -580,11 +609,12 @@ RCT_EXPORT_METHOD(openCropper:(NSDictionary *)options
}]];
return;
}
// Wait for viewController to dismiss before resolving, or we lose the ability to display
// Alert.alert in the .then() handler.
[viewController dismissViewControllerAnimated:YES completion:[self waitAnimationEnd:^{
self.resolve([self createAttachmentResponse:filePath
withExif:exif
withSourceURL:sourceURL
withLocalIdentifier:localIdentifier
withFilename:filename
@ -592,7 +622,7 @@ RCT_EXPORT_METHOD(openCropper:(NSDictionary *)options
withHeight:imageResult.height
withMime:imageResult.mime
withSize:[NSNumber numberWithUnsignedInteger:imageResult.data.length]
withData:[[self.options objectForKey:@"includeBase64"] boolValue] ? [imageResult.data base64EncodedStringWithOptions:0] : [NSNull null]]);
withData:[[self.options objectForKey:@"includeBase64"] boolValue] ? [imageResult.data base64EncodedStringWithOptions:0] : nil]);
}]];
}
}
@ -605,14 +635,14 @@ RCT_EXPORT_METHOD(openCropper:(NSDictionary *)options
CGSize maskSize = CGSizeMake(
[[self.options objectForKey:@"width"] intValue],
[[self.options objectForKey:@"height"] intValue]);
CGFloat viewWidth = CGRectGetWidth(controller.view.frame);
CGFloat viewHeight = CGRectGetHeight(controller.view.frame);
CGRect maskRect = CGRectMake((viewWidth - maskSize.width) * 0.5f,
(viewHeight - maskSize.height) * 0.5f,
maskSize.width, maskSize.height);
return maskRect;
}
@ -622,13 +652,13 @@ RCT_EXPORT_METHOD(openCropper:(NSDictionary *)options
CGRect rect = controller.maskRect;
CGFloat viewWidth = CGRectGetWidth(controller.view.frame);
CGFloat viewHeight = CGRectGetHeight(controller.view.frame);
double scaleFactor = fmin(viewWidth / rect.size.width, viewHeight / rect.size.height);
rect.size.width *= scaleFactor;
rect.size.height *= scaleFactor;
rect.origin.x = (viewWidth - rect.size.width) / 2;
rect.origin.y = (viewHeight - rect.size.height) / 2;
return rect;
}
@ -653,26 +683,30 @@ RCT_EXPORT_METHOD(openCropper:(NSDictionary *)options
// Crop image has been canceled.
- (void)imageCropViewControllerDidCancelCrop:
(RSKImageCropViewController *)controller {
[self dismissCropper:controller dismissAll: NO completion:[self waitAnimationEnd:^{
if (self.cropOnly) {
[self dismissCropper:controller selectionDone:NO completion:[self waitAnimationEnd:^{
if (self.currentSelectionMode == CROPPING) {
self.reject(ERROR_PICKER_CANCEL_KEY, ERROR_PICKER_CANCEL_MSG, nil);
}
}]];
}
- (void) dismissCropper:(RSKImageCropViewController*) controller dismissAll: (BOOL) dissmissAll completion:(void (^)())completion {
//We've presented the cropper on top of the image picker as to not have a double modal animation.
//Thus, we need to dismiss the image picker view controller to dismiss the whole stack.
if (!self.cropOnly) {
if (dissmissAll) {
UIViewController *topViewController = controller.presentingViewController.presentingViewController;
[topViewController dismissViewControllerAnimated:YES completion:completion];
} else {
UIViewController *topViewController = controller.presentingViewController;
[topViewController dismissViewControllerAnimated:YES completion:completion];
}
} else {
[controller dismissViewControllerAnimated:YES completion:completion];
- (void) dismissCropper:(RSKImageCropViewController*)controller selectionDone:(BOOL)selectionDone completion:(void (^)())completion {
switch (self.currentSelectionMode) {
case CROPPING:
[controller dismissViewControllerAnimated:YES completion:completion];
break;
case PICKER:
if (selectionDone) {
[controller.presentingViewController.presentingViewController dismissViewControllerAnimated:YES completion:completion];
} else {
// if user opened picker, tried to crop image, and cancelled cropping
// return him to the image selection instead of returning him to the app
[controller.presentingViewController dismissViewControllerAnimated:YES completion:completion];
}
break;
case CAMERA:
[controller.presentingViewController.presentingViewController dismissViewControllerAnimated:YES completion:completion];
break;
}
}
@ -680,31 +714,37 @@ RCT_EXPORT_METHOD(openCropper:(NSDictionary *)options
- (void)imageCropViewController:(RSKImageCropViewController *)controller
didCropImage:(UIImage *)croppedImage
usingCropRect:(CGRect)cropRect {
// we have correct rect, but not correct dimensions
// so resize image
CGSize resizedImageSize = CGSizeMake([[[self options] objectForKey:@"width"] intValue], [[[self options] objectForKey:@"height"] intValue]);
UIImage *resizedImage = [croppedImage resizedImageToFitInSize:resizedImageSize scaleIfSmaller:YES];
ImageResult *imageResult = [self.compression compressImage:resizedImage withOptions:self.options];
NSString *filePath = [self persistFile:imageResult.data];
if (filePath == nil) {
[self dismissCropper:controller dismissAll: YES completion:[self waitAnimationEnd:^{
[self dismissCropper:controller selectionDone:YES completion:[self waitAnimationEnd:^{
self.reject(ERROR_CANNOT_SAVE_IMAGE_KEY, ERROR_CANNOT_SAVE_IMAGE_MSG, nil);
}]];
return;
}
[self dismissCropper:controller dismissAll: YES completion:[self waitAnimationEnd:^{
NSDictionary* exif = nil;
if([[self.options objectForKey:@"includeExif"] boolValue]) {
exif = [[CIImage imageWithData:imageResult.data] properties];
}
[self dismissCropper:controller selectionDone:YES completion:[self waitAnimationEnd:^{
self.resolve([self createAttachmentResponse:filePath
withExif: exif
withSourceURL: self.croppingFile[@"sourceURL"]
withLocalIdentifier: self.croppingFile[@"localIdentifier"]
withFilename: self.croppingFile[@"filename"]
withLocalIdentifier: self.croppingFile[@"localIdentifier"]
withFilename: self.croppingFile[@"filename"]
withWidth:imageResult.width
withHeight:imageResult.height
withMime:imageResult.mime
withSize:[NSNumber numberWithUnsignedInteger:imageResult.data.length]
withData:[[self.options objectForKey:@"includeBase64"] boolValue] ? [imageResult.data base64EncodedStringWithOptions:0] : [NSNull null]]);
withData:[[self.options objectForKey:@"includeBase64"] boolValue] ? [imageResult.data base64EncodedStringWithOptions:0] : nil]);
}]];
}
@ -715,13 +755,13 @@ RCT_EXPORT_METHOD(openCropper:(NSDictionary *)options
NSString *tmpDirFullPath = [self getTmpDirectory];
NSString *filePath = [tmpDirFullPath stringByAppendingString:[[NSUUID UUID] UUIDString]];
filePath = [filePath stringByAppendingString:@".jpg"];
// save cropped file
BOOL status = [data writeToFile:filePath atomically:YES];
if (!status) {
return nil;
}
return filePath;
}

View File

@ -1,6 +1,6 @@
{
"name": "react-native-image-crop-picker",
"version": "0.16.1",
"version": "0.17.0",
"description": "Select single or multiple images, with cropping option",
"main": "index.js",
"scripts": {
@ -40,4 +40,4 @@
"url": "https://opencollective.com/react-native-image-crop-picker",
"logo": "https://opencollective.com/opencollective/logo.txt"
}
}
}