-
Notifications
You must be signed in to change notification settings - Fork 9.7k
[image_picker] Fix to keep Exif and so on in case of iOS. #866
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -63,14 +63,22 @@ - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result | |||||
| case SOURCE_CAMERA: | ||||||
| [self showCamera]; | ||||||
| break; | ||||||
| case SOURCE_GALLERY: | ||||||
| [self showPhotoLibrary]; | ||||||
| case SOURCE_GALLERY: { | ||||||
| [PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) { | ||||||
| if (status == PHAuthorizationStatusAuthorized) { | ||||||
| [self showPhotoLibrary]; | ||||||
| } else { | ||||||
| result(nil); | ||||||
| } | ||||||
| }]; | ||||||
| break; | ||||||
| default: | ||||||
| } | ||||||
| default: { | ||||||
| result([FlutterError errorWithCode:@"invalid_source" | ||||||
| message:@"Invalid image source." | ||||||
| details:nil]); | ||||||
| break; | ||||||
| } | ||||||
| } | ||||||
| } else if ([@"pickVideo" isEqualToString:call.method]) { | ||||||
| _imagePickerController.modalPresentationStyle = UIModalPresentationCurrentContext; | ||||||
|
|
@@ -90,14 +98,22 @@ - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result | |||||
| case SOURCE_CAMERA: | ||||||
| [self showCamera]; | ||||||
| break; | ||||||
| case SOURCE_GALLERY: | ||||||
| [self showPhotoLibrary]; | ||||||
| case SOURCE_GALLERY: { | ||||||
| [PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) { | ||||||
| if (status == PHAuthorizationStatusAuthorized) { | ||||||
| [self showPhotoLibrary]; | ||||||
| } else { | ||||||
| result(nil); | ||||||
| } | ||||||
| }]; | ||||||
| break; | ||||||
| default: | ||||||
| } | ||||||
| default: { | ||||||
| result([FlutterError errorWithCode:@"invalid_source" | ||||||
| message:@"Invalid video source." | ||||||
| details:nil]); | ||||||
| break; | ||||||
| } | ||||||
| } | ||||||
| } else { | ||||||
| result(FlutterMethodNotImplemented); | ||||||
|
|
@@ -150,6 +166,11 @@ - (void)imagePickerController:(UIImagePickerController *)picker | |||||
| details:nil]); | ||||||
| } | ||||||
| } else { | ||||||
| NSURL *assetURL = [info objectForKey:UIImagePickerControllerReferenceURL]; | ||||||
| PHFetchResult<PHAsset *> *result = [PHAsset fetchAssetsWithALAssetURLs:@[ assetURL ] | ||||||
| options:nil]; | ||||||
| PHAsset *asset = result.firstObject; | ||||||
|
|
||||||
| if (image == nil) { | ||||||
| image = [info objectForKey:UIImagePickerControllerOriginalImage]; | ||||||
| } | ||||||
|
|
@@ -158,27 +179,71 @@ - (void)imagePickerController:(UIImagePickerController *)picker | |||||
| NSNumber *maxWidth = [_arguments objectForKey:@"maxWidth"]; | ||||||
| NSNumber *maxHeight = [_arguments objectForKey:@"maxHeight"]; | ||||||
|
|
||||||
| bool isScaled = false; | ||||||
|
|
||||||
| if (maxWidth != (id)[NSNull null] || maxHeight != (id)[NSNull null]) { | ||||||
| isScaled = true; | ||||||
| image = [self scaledImage:image maxWidth:maxWidth maxHeight:maxHeight]; | ||||||
| } | ||||||
|
|
||||||
| NSData *data = UIImageJPEGRepresentation(image, 1.0); | ||||||
| NSString *guid = [[NSProcessInfo processInfo] globallyUniqueString]; | ||||||
| NSString *tmpFile = [NSString stringWithFormat:@"image_picker_%@.jpg", guid]; | ||||||
| NSString *tmpDirectory = NSTemporaryDirectory(); | ||||||
| NSString *tmpPath = [tmpDirectory stringByAppendingPathComponent:tmpFile]; | ||||||
|
|
||||||
| if ([[NSFileManager defaultManager] createFileAtPath:tmpPath contents:data attributes:nil]) { | ||||||
| _result(tmpPath); | ||||||
| } else { | ||||||
| _result([FlutterError errorWithCode:@"create_error" | ||||||
| message:@"Temporary file could not be created" | ||||||
| details:nil]); | ||||||
| } | ||||||
| [[PHImageManager defaultManager] | ||||||
| requestImageDataForAsset:asset | ||||||
| options:nil | ||||||
| resultHandler:^(NSData *_Nullable imageData, NSString *_Nullable dataUTI, | ||||||
| UIImageOrientation orientation, NSDictionary *_Nullable info) { | ||||||
| NSString *guid = [[NSProcessInfo processInfo] globallyUniqueString]; | ||||||
| NSString *tmpFile = [NSString stringWithFormat:@"image_picker_%@", guid]; | ||||||
| NSString *tmpDirectory = NSTemporaryDirectory(); | ||||||
| NSString *tmpPath = [tmpDirectory stringByAppendingPathComponent:tmpFile]; | ||||||
|
|
||||||
| uint8_t c; | ||||||
| [imageData getBytes:&c length:1]; | ||||||
| switch (c) { | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we make this an enum? - (FlutterImagePickerFileType)getFileTypeFromFirstByte:(unit8_t c) {
switch(c) {
case 0x47:
return FlutterImagePickerFileTypeGIF;
.....
.....
}
}
|
||||||
| case 0x47: | ||||||
| // image/gif | ||||||
| if (isScaled == true) { | ||||||
| // TODO When scaled, animation disappears. Drawing as JPEG. | ||||||
| [self createImageFileAtPath:[tmpPath stringByAppendingString:@".gif"] | ||||||
| contents:UIImageJPEGRepresentation(image, 1)]; | ||||||
| } else { | ||||||
| [self createImageFileAtPath:[tmpPath stringByAppendingString:@".gif"] | ||||||
| contents:imageData]; | ||||||
| } | ||||||
| break; | ||||||
| case 0x89: | ||||||
| // image/png | ||||||
| [self createImageFileAtPath:[tmpPath stringByAppendingString:@".png"] | ||||||
| contents:UIImagePNGRepresentation(image)]; | ||||||
| break; | ||||||
| case 0xff: | ||||||
| // image/jpeg | ||||||
| [self createImageFileAtPath:[tmpPath stringByAppendingString:@".jpeg"] | ||||||
| contents:UIImageJPEGRepresentation(image, 1) | ||||||
| with:asset]; | ||||||
| break; | ||||||
| case 0x49: | ||||||
| // image/tiff | ||||||
| // TODO Drawing as JPEG. | ||||||
| [self createImageFileAtPath:[tmpPath stringByAppendingString:@".tiff"] | ||||||
| contents:UIImageJPEGRepresentation(image, 1) | ||||||
| with:asset]; | ||||||
| break; | ||||||
| case 0x4d: | ||||||
| // image/tiff | ||||||
| // TODO Drawing as JPEG. | ||||||
| [self createImageFileAtPath:[tmpPath stringByAppendingString:@".tiff"] | ||||||
| contents:UIImageJPEGRepresentation(image, 1) | ||||||
| with:asset]; | ||||||
| break; | ||||||
| default: | ||||||
| // Drawing as JPEG. | ||||||
| [self createImageFileAtPath:[tmpPath stringByAppendingString:@".jpeg"] | ||||||
| contents:UIImageJPEGRepresentation(image, 1) | ||||||
| with:asset]; | ||||||
| break; | ||||||
| } | ||||||
| }]; | ||||||
| } | ||||||
|
|
||||||
| _result = nil; | ||||||
| _arguments = nil; | ||||||
| } | ||||||
|
|
||||||
| - (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker { | ||||||
|
|
@@ -254,4 +319,94 @@ - (UIImage *)scaledImage:(UIImage *)image | |||||
| return scaledImage; | ||||||
| } | ||||||
|
|
||||||
| - (void)createImageFileAtPath:(NSString *)path contents:(NSData *)data { | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit:
Suggested change
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This mimics the signature of
|
||||||
| if ([[NSFileManager defaultManager] createFileAtPath:path contents:data attributes:nil]) { | ||||||
| _result(path); | ||||||
| } else { | ||||||
| _result([FlutterError errorWithCode:@"create_error" | ||||||
| message:@"Temporary file could not be created" | ||||||
| details:nil]); | ||||||
| } | ||||||
| _result = nil; | ||||||
| _arguments = nil; | ||||||
| } | ||||||
|
|
||||||
| - (void)createImageFileAtPath:(NSString *)path contents:(NSData *)data with:(PHAsset *)asset { | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit:
Suggested change
|
||||||
| NSMutableDictionary *exifDict = [self fetchExifFrom:asset]; | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Manually parsing the meta data seems to be risky. Is it possible to get the meta data from the original image directly and pass down to the new image? |
||||||
| NSMutableDictionary *gpsDict = [self fetchGpsFrom:asset]; | ||||||
|
|
||||||
| NSMutableData *imageData = [NSMutableData data]; | ||||||
| CGImageSourceRef cgImage = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL); | ||||||
| CGImageDestinationRef destination = CGImageDestinationCreateWithData( | ||||||
| (__bridge CFMutableDataRef)imageData, CGImageSourceGetType(cgImage), 1, nil); | ||||||
| CGImageDestinationAddImageFromSource( | ||||||
| destination, cgImage, 0, | ||||||
| (__bridge CFDictionaryRef)[NSDictionary | ||||||
| dictionaryWithObjectsAndKeys:exifDict, | ||||||
| (__bridge NSString *)kCGImagePropertyExifDictionary, gpsDict, | ||||||
| (__bridge NSString *)kCGImagePropertyGPSDictionary, nil]); | ||||||
| CGImageDestinationFinalize(destination); | ||||||
|
|
||||||
| [imageData writeToFile:path atomically:YES]; | ||||||
|
|
||||||
| CFRelease(cgImage); | ||||||
| CFRelease(destination); | ||||||
|
|
||||||
| _result(path); | ||||||
| _result = nil; | ||||||
| _arguments = nil; | ||||||
| } | ||||||
|
|
||||||
| - (NSMutableDictionary *)fetchExifFrom:(PHAsset *)asset { | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If a manual parser is inevitable, let's put the meta data handling into a separate file/class. |
||||||
| NSMutableDictionary *result = [NSMutableDictionary dictionary]; | ||||||
| NSDate *creationDate = asset.creationDate; | ||||||
|
|
||||||
| NSDateFormatter *outputFormatter = [[NSDateFormatter alloc] init]; | ||||||
| [outputFormatter setDateFormat:@"yyyy:MM:dd HH:mm:ss"]; | ||||||
| NSString *original = [outputFormatter stringFromDate:creationDate]; | ||||||
| [result setObject:original forKey:(__bridge NSString *)kCGImagePropertyExifDateTimeOriginal]; | ||||||
| [result setObject:original forKey:(__bridge NSString *)kCGImagePropertyExifDateTimeDigitized]; | ||||||
| return result; | ||||||
| } | ||||||
|
|
||||||
| - (NSMutableDictionary *)fetchGpsFrom:(PHAsset *)asset { | ||||||
| NSMutableDictionary *result = [NSMutableDictionary dictionary]; | ||||||
| CLLocation *location = asset.location; | ||||||
| CGFloat latitude = location.coordinate.latitude; | ||||||
| NSString *gpsLatitudeRef; | ||||||
| if (latitude < 0) { | ||||||
| latitude = -latitude; | ||||||
| gpsLatitudeRef = @"S"; | ||||||
| } else { | ||||||
| gpsLatitudeRef = @"N"; | ||||||
| } | ||||||
| [result setObject:gpsLatitudeRef forKey:(__bridge NSString *)kCGImagePropertyGPSLatitudeRef]; | ||||||
| [result setObject:@(latitude) forKey:(__bridge NSString *)kCGImagePropertyGPSLatitude]; | ||||||
|
|
||||||
| CGFloat longitude = location.coordinate.longitude; | ||||||
| NSString *gpsLongitudeRef; | ||||||
| if (longitude < 0) { | ||||||
| longitude = -longitude; | ||||||
| gpsLongitudeRef = @"W"; | ||||||
| } else { | ||||||
| gpsLongitudeRef = @"E"; | ||||||
| } | ||||||
| [result setObject:gpsLongitudeRef forKey:(__bridge NSString *)kCGImagePropertyGPSLongitudeRef]; | ||||||
| [result setObject:@(longitude) forKey:(__bridge NSString *)kCGImagePropertyGPSLongitude]; | ||||||
|
|
||||||
| CGFloat altitude = location.altitude; | ||||||
| if (!isnan(altitude)) { | ||||||
| NSString *gpsAltitudeRef; | ||||||
| if (altitude < 0) { | ||||||
| altitude = -altitude; | ||||||
| gpsAltitudeRef = @"1"; | ||||||
| } else { | ||||||
| gpsAltitudeRef = @"0"; | ||||||
| } | ||||||
| [result setObject:gpsAltitudeRef forKey:(__bridge NSString *)kCGImagePropertyGPSAltitudeRef]; | ||||||
| [result setObject:@(altitude) forKey:(__bridge NSString *)kCGImagePropertyGPSAltitude]; | ||||||
| } | ||||||
| return result; | ||||||
| } | ||||||
|
|
||||||
| @end | ||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
assetURLcan be nil if user take a fresh picture in the image_picker, which leads to crash here.