Skip to content
This repository has been archived by the owner on Jun 19, 2024. It is now read-only.

Commit

Permalink
save original image file when picking pre-existing images instead of …
Browse files Browse the repository at this point in the history
…re-encoding

save images with original EXIF and configurable compression when picking directly from the camera or picking an edited image
  • Loading branch information
orand committed Jul 30, 2015
1 parent 8e5bcf4 commit b128dfe
Show file tree
Hide file tree
Showing 2 changed files with 120 additions and 9 deletions.
116 changes: 107 additions & 9 deletions MonoTouch/Xamarin.Mobile/Media/MediaPickerDelegate.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,22 @@
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Runtime.InteropServices;
using System.Threading;

#if __UNIFIED__
using CoreGraphics;
using AssetsLibrary;
using Foundation;
using ImageIO;
using MobileCoreServices;
using UIKit;
using NSAction = global::System.Action;
#else
using MonoTouch.AssetsLibrary;
using MonoTouch.Foundation;
using MonoTouch.ImageIO;
using MonoTouch.MobileCoreServices;
using MonoTouch.UIKit;

using CGRect = global::System.Drawing.RectangleF;
Expand Down Expand Up @@ -248,19 +254,51 @@ 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)
image = (UIImage)info[UIImagePickerController.OriginalImage];
NSUrl referenceUrl = (NSUrl)info[UIImagePickerController.ReferenceUrl];

string path = GetOutputPath (MediaPicker.TypeImage,
options.Directory ?? ((IsCaptured) ? String.Empty : "temp"),
options.Name);
// if the image is coming directly from the camera
if (image == null && referenceUrl == null)
{
image = (UIImage)info[UIImagePickerController.OriginalImage];
}

using (FileStream fs = File.OpenWrite (path))
using (Stream s = new NSDataStream (image.AsJPEG()))
// if this is an EditedImage or direct camera image
if (image != null)
{
s.CopyTo (fs);
fs.Flush();
NSDictionary metadata = (NSDictionary)info[UIImagePickerController.MediaMetadata];
NSMutableDictionary mutableMetadata = new NSMutableDictionary(metadata);

mutableMetadata.SetValueForKey(new NSNumber(options.JpegCompressionQuality), new NSString("kCGImageDestinationLossyCompressionQuality"));

NSMutableData destData = new NSMutableData();
#if __UNIFIED__
CGImageDestination destination = CGImageDestination.Create(destData, UTType.JPEG, 1);
#else
CGImageDestination destination = CGImageDestination.FromData(destData, UTType.JPEG, 1);
#endif
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
{
// 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<bool> dispose = null;
Expand All @@ -270,6 +308,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];
Expand Down
13 changes: 13 additions & 0 deletions Shared/Media/StoreMediaOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit b128dfe

Please sign in to comment.