jamesmontemagno / MediaPlugin

Take & Pick Photos and Video Plugin for Xamarin and Windows
MIT License
713 stars 358 forks source link

No EXIF data after retrieving a picture (that has been taken with this plugin) from the gallery #687

Open IvicaSkrobo opened 5 years ago

IvicaSkrobo commented 5 years ago

Bug Information

Version Number of Plugin: 4.0.1.5 Device Tested On: Simulator Tested On: Android 8.1 API 27 (VS emulator) Version of VS: VS 2017 Version of Xamarin: 3.6.0.293080 Versions of other things you are using:

Steps to reproduce the Behavior

Take a photo with Media.Plugin. Save it to the album with location and try to read the location with ExifReader after you pick an image from the gallery.

Expected Behavior

A photo should hold EXIF data after being saved to the gallery and after it is taken out of the gallery. Is this a bug or this hasn't been implemented?

Actual Behavior

A photo has EXIF data if I take a picture outside of the application, with emulators camera. If I take a picture with the plugin inside my app, the picture file still has the EXIF data, but after I try to get the same picture from the album it doesn't have it anymore. So probably picture loses it's EXIF data while being saved.

Code snippet

//this is how I take the picture Location location = new Location(); location.Latitude = 52.0271723411551; location.Longitude = 0.764563267810425;

            photoFile = await CrossMedia.Current.TakePhotoAsync(new StoreCameraMediaOptions
            {

                SaveToAlbum = true,
                Location = location,
                Directory = "Komunalac",
                Name = "photoKomunalac.jpg",
                SaveMetaData = true,
            });

            var picInfo = ExifLib.ExifReader.ReadJpeg(photoFile.GetStream());

            imageSource = ImageSource.FromStream(() =>
            {
                return photoFile.GetStream();
            });

//And then try to retrieve that same photo with media picker :

photoPicked = await CrossMedia.Current.PickPhotoAsync(new PickMediaOptions { PhotoSize = PhotoSize.Medium, SaveMetaData = true });

            picInfo = null;

            if (photoPicked != null)
            {

                picInfo = ExifReader.ReadJpeg(photoPicked.GetStream());

                imageSource = ImageSource.FromStream(() =>
                  {
                      return photoPicked.GetStream();
                  });
            }

Screenshots

dhewitson commented 4 years ago

Similar problem here with iOS 13.2 and plugin version 4.0.1.5 EXIF Data is missing on images saved using the plugin.

This actually occurs to all images that have been saved using the plugin, regardless of the save location.

Liquidmediagroup commented 4 years ago

Any update on this bug. I am also facing same issue.

gaelian commented 4 years ago

@jamesmontemagno I seem to be running into this problem with iOS 13.3.1 and Plugin.Media 4.0.1.5. Has this been fixed in a pre-release version?

madmapper commented 4 years ago

Also seeing this issue. I'd be willing to sponsor a bounty if anyone has time to jump in and address.

madmapper commented 4 years ago

Also seeing this issue. I'd be willing to sponsor a bounty if anyone has time to jump in and address.

I stand corrected - it appears that photos are captured with EXIF on both Android and iOS.

gaelian commented 4 years ago

Now using iOS 13.4.1 with Plugin.Media 4.0.1.5 and the problem is still evident for me. Latitude, longitude and altitude are not able to be extracted from a photo previously saved to the gallery. I tried a newer version of Plugin.Media but subsequently ran into this problem.

@madmapper if things are working for you, I'd be keen to see some code. I discuss my issue in a little more detail here.

madmapper commented 4 years ago

I retracted my statement after examining captured (via camera) images on both Android and iOS. Both contain metadata. The Android pic included GPS data and the iOS pic did not. This matches up with my experience doing native apps for iOS. The camera doesn't tag photos, it's up to you to do that. The problem it seems is with EXIFReader.ReadJpeg not returning anything. I've switched our app to use a combination of RedCorners ExifLibrary and MetadataExtractor to do what we need. Using those libraries all the metadata preset in the images is showing up as expected.

Testing importing photos from the library just now it seems like the same thing - Android has GPS and iOS doesn't. I know in naive iOS (Objective C) you have to pull in the image through PHImageManager and then use __bridge variables to reach back via the Core Graphics libraries to get the CGImageMetadataRef for the image, and then re-pair the image data and the metadata in your app.

I'm not sure how MediaPlugin is accomplishing this, but it was a royal PITA the last time I had to implement it via native code.

madmapper commented 4 years ago

Here is a slug of Objective-C code that gets a picked photo from the iOS photo library and pulls in the image metadata and image properties... note that these are not the same thing for some reason. Also I left in the bits where we combine the image and the metadata and save to a local app directory. Apologies in advanced for the hideousness of all of this:

if (picker.sourceType == UIImagePickerControllerSourceTypePhotoLibrary) {
        PHAsset *asset = [info objectForKey:UIImagePickerControllerPHAsset];
        if (asset) {
            // get photo info from this asset
            PHImageRequestOptions * imageRequestOptions = [[PHImageRequestOptions alloc] init];
            imageRequestOptions.synchronous = YES;
            [[PHImageManager defaultManager] requestImageDataAndOrientationForAsset:asset options:imageRequestOptions resultHandler:^(NSData * _Nullable imageData, NSString * _Nullable dataUTI, CGImagePropertyOrientation orientation, NSDictionary * _Nullable info) {
                UIImage *image = [UIImage imageWithData:imageData];
                //creaing a core graphics image source with our image data
                CGImageSourceRef imageSource = CGImageSourceCreateWithData((__bridge CFDataRef)(imageData), NULL);
                if (imageSource) {
                    //grabbing image metadata to look for specific Photo Sphere key
                    CGImageMetadataRef imageMetaData = CGImageSourceCopyMetadataAtIndex(imageSource,0,NULL);
                    if (imageMetaData) {
                        //finding a specific value in the metadata dictionary - in this case looking for the "projection" tag to detect 360 photos
                        NSDictionary *metadata = (__bridge NSDictionary *)imageMetaData;
                        CGImageMetadataEnumerateTagsUsingBlock(imageMetaData, nil, nil, ^bool(CFStringRef  _Nonnull path, CGImageMetadataTagRef  _Nonnull tag) {
                            CFRange range = CFStringFind(path, (CFStringRef)@"Projection", kCFCompareCaseInsensitive);
                            if (range.length > 0) {
                                //do stuff if found
                                return false;
                            }
                            else{
                                return true;
                            }
                        });

                        //Now we are looking at the image properties... which is different than image metadata... for some reason
                        NSDictionary *options = @{(NSString *)kCGImageSourceShouldCache : [NSNumber numberWithBool:YES]};
                        CFDictionaryRef imageProperties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, (__bridge CFDictionaryRef)options);
                        if (imageProperties) {
                            //grab photo property values.  This is where you would find the info you would expect in EXIF tags
                            NSDictionary *properties = (__bridge NSDictionary *)imageProperties;
                            NSDictionary *photoInfo = [properties objectForKey:@"{Exif}"];
                            NSString *captureDate = [photoInfo objectForKey:@"DateTimeOriginal"];
                            NSDateFormatter *dateFormat = [[NSDateFormatter alloc] init];
                            [dateFormat setDateFormat:@"yyyy:MM:dd HH:mm:ss"];
                            NSDate *date = [dateFormat dateFromString:captureDate];

                            NSDictionary *locationInfo = [properties objectForKey:@"{GPS}"];
                            NSString *lon = [locationInfo objectForKey:@"Longitude"];
                            NSString *lonDir = [locationInfo objectForKey:@"LongitudeRef"];
                            NSString *lat = [locationInfo objectForKey:@"Latitude"];
                            NSString *latDir = [locationInfo objectForKey:@"LatitudeRef"];
                        }

                        //Here we are combining the image metadata with the image and saving to an app directory
                        NSString *photoName = [[NSString stringWithFormat:@"%@",[[NSUUID UUID] UUIDString]] uppercaseString];
                        NSString *originalFileName = [NSString stringWithFormat:@"/%@.jpg", photoName];

                        NSError* error;
                        NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
                        NSString *photoDirectoryPath = [[paths firstObject] stringByAppendingString:@"/Photos"];
                        if (![[NSFileManager defaultManager] fileExistsAtPath:photoDirectoryPath]) {
                            [[NSFileManager defaultManager] createDirectoryAtPath:photoDirectoryPath withIntermediateDirectories:NO attributes:nil error:&error];
                        }
                        NSString *originalFullPath = [photoDirectoryPath stringByAppendingString:originalFileName];
                        NSURL *finalPhotoURL = [NSURL fileURLWithPath:originalFullPath];
                        CGImageDestinationRef imgFileDestination = CGImageDestinationCreateWithURL((__bridge CFURLRef)finalPhotoURL, kUTTypeJPEG, 1, NULL);
                        if (imgFileDestination == NULL ) {
                            // Handle failure.
                            NSLog(@"Error -> failed to create image destination.");
                        }
                        CGImageDestinationAddImageAndMetadata(imgFileDestination, image.CGImage, imageMetaData, NULL);
                        // Finalize the destination.
                        if (CGImageDestinationFinalize(imgFileDestination))
                           {
                               NSLog(@"Successful image creation.");
                            }
                            else
                            {
                                  NSLog(@"Error -> failed to finalize the image.");
                            }
                        CFRelease(imageSource);
                        CFRelease(imgFileDestination);

                    }
                }
            }];
        }
    }
gaelian commented 4 years ago

I appreciate the extra info and yes, that Obj-C is quite something. :P With my situation, I am definitely able to successfully save the desired metadata with the photo, it just doesn't get retrieved by iOS when subsequently pulling the photo from the gallery using the approach that used to work. I'll look into the alternatives you mention, thanks.