diff --git a/MonoTouch/Xamarin.Mobile/Media/MediaPickerDelegate.cs b/MonoTouch/Xamarin.Mobile/Media/MediaPickerDelegate.cs index 7f66648..f345c0d 100644 --- a/MonoTouch/Xamarin.Mobile/Media/MediaPickerDelegate.cs +++ b/MonoTouch/Xamarin.Mobile/Media/MediaPickerDelegate.cs @@ -20,8 +20,12 @@ using System.Threading.Tasks; using MonoTouch.AssetsLibrary; using MonoTouch.Foundation; +using MonoTouch.ImageIO; +using MonoTouch.MobileCoreServices; using MonoTouch.UIKit; using System.Drawing; +using System.Runtime.InteropServices; +using System.Threading; namespace Xamarin.Media { @@ -237,19 +241,47 @@ private bool GetShouldRotate6 (UIDeviceOrientation orientation) private MediaFile GetPictureMediaFile (NSDictionary info) { + string path = GetOutputPath (MediaPicker.TypeImage, + options.Directory ?? ((IsCaptured) ? String.Empty : "temp"), + options.Name); + var image = (UIImage)info[UIImagePickerController.EditedImage]; - if (image == null) + NSUrl referenceUrl = (NSUrl)info[UIImagePickerController.ReferenceUrl]; + + // if the image is coming directly from the camera + if (image == null && referenceUrl == null) + { image = (UIImage)info[UIImagePickerController.OriginalImage]; + } - string path = GetOutputPath (MediaPicker.TypeImage, - options.Directory ?? ((IsCaptured) ? String.Empty : "temp"), - options.Name); + // if this is an EditedImage or direct camera image + if (image != null) + { + NSDictionary metadata = (NSDictionary)info[UIImagePickerController.MediaMetadata]; + NSMutableDictionary mutableMetadata = new NSMutableDictionary(metadata); - using (FileStream fs = File.OpenWrite (path)) - using (Stream s = new NSDataStream (image.AsJPEG())) + mutableMetadata.SetValueForKey(new NSNumber(options.JpegCompressionQuality), new NSString("kCGImageDestinationLossyCompressionQuality")); + + NSMutableData destData = new NSMutableData(); + CGImageDestination destination = CGImageDestination.FromData(destData, UTType.JPEG, 1); + destination.AddImage(image.CGImage, mutableMetadata); + + destination.Close(); + NSError saveError = null; + destData.Save(path, NSDataWritingOptions.Atomic, out saveError); + + if (saveError != null) + { + throw new InvalidOperationException("Failed to save image: " + saveError.Description); + } + } + else { - s.CopyTo (fs); - fs.Flush(); + // image is coming from the camera roll + // use the original image file data without re-encoding it + // unlike the direct camera image above, this one will also + // include GPS EXIF data if photo was taken by the Camera app + SaveOriginalImage(referenceUrl, path); } Action dispose = null; @@ -259,6 +291,66 @@ private MediaFile GetPictureMediaFile (NSDictionary info) return new MediaFile (path, () => File.OpenRead (path), dispose); } + private static string SaveOriginalImage(NSUrl referenceUrl, string path) + { + ALAssetsLibrary library = new ALAssetsLibrary(); + var doneSignal = new ManualResetEvent(false); + byte[] imageData = null; + NSError assetError = null; + + // even though AssetForUrl is async, the callbacks aren't called + // unless we use another thread due to blocking from our WaitOne below. + System.Threading.Tasks.Task.Run(() => { + library.AssetForUrl(referenceUrl, asset => { + try + { + long size = asset.DefaultRepresentation.Size; + imageData = new byte[size]; + IntPtr buffer = Marshal.AllocHGlobal(imageData.Length); + NSError bytesError; + + asset.DefaultRepresentation.GetBytes(buffer, 0, (uint)size, out bytesError); + + if (bytesError != null) + { + assetError = bytesError; + return; + } + + Marshal.Copy(buffer, imageData, 0, imageData.Length); + } + finally + { + asset.Dispose(); + doneSignal.Set(); + } + }, error => { + assetError = error; + doneSignal.Set(); + }); + }); + + if (!doneSignal.WaitOne(TimeSpan.FromSeconds(10))) + { + throw new TimeoutException("Timed out getting asset"); + } + + library.Dispose(); + + if (assetError != null || imageData == null) + { + throw new InvalidOperationException("Failed to get asset for URL"); + } + + using (FileStream fs = File.OpenWrite(path)) + { + fs.Write(imageData, 0, imageData.Length); + fs.Flush(); + } + + return path; + } + private MediaFile GetMovieMediaFile (NSDictionary info) { NSUrl url = (NSUrl)info[UIImagePickerController.MediaURL]; diff --git a/Shared/Media/StoreMediaOptions.cs b/Shared/Media/StoreMediaOptions.cs index 799d5a8..82ae2fb 100644 --- a/Shared/Media/StoreMediaOptions.cs +++ b/Shared/Media/StoreMediaOptions.cs @@ -46,11 +46,24 @@ public enum CameraDevice public class StoreCameraMediaOptions : StoreMediaOptions { + public StoreCameraMediaOptions() + { + // 0.90 produces an image quality of 96 according to ImageMagick's identify -verbose + // which is equivalent to the default compression quality used by the Camera app + JpegCompressionQuality = 0.90f; + } + public CameraDevice DefaultCamera { get; set; } + + public float JpegCompressionQuality + { + get; + set; + } } public enum VideoQuality