Fix camera issues, cropping, memory leaks CB-4027, CB-5102, CB-2737, CB-2387

This commit is contained in:
Jesse MacFadyen 2014-04-07 15:00:12 -07:00
parent 91d6e10b29
commit e7a3d70fe9

View File

@ -113,8 +113,6 @@ namespace WPCordovaClassLib.Cordova.Commands
[DataMember(IsRequired = false, Name = "correctOrientation")] [DataMember(IsRequired = false, Name = "correctOrientation")]
public bool CorrectOrientation { get; set; } public bool CorrectOrientation { get; set; }
/// <summary> /// <summary>
/// Ignored /// Ignored
/// </summary> /// </summary>
@ -176,16 +174,6 @@ namespace WPCordovaClassLib.Cordova.Commands
} }
} }
/// <summary>
/// Used to open photo library
/// </summary>
PhotoChooserTask photoChooserTask;
/// <summary>
/// Used to open camera application
/// </summary>
CameraCaptureTask cameraTask;
/// <summary> /// <summary>
/// Camera options /// Camera options
/// </summary> /// </summary>
@ -198,20 +186,17 @@ namespace WPCordovaClassLib.Cordova.Commands
string[] args = JSON.JsonHelper.Deserialize<string[]>(options); string[] args = JSON.JsonHelper.Deserialize<string[]>(options);
// ["quality", "destinationType", "sourceType", "targetWidth", "targetHeight", "encodingType", // ["quality", "destinationType", "sourceType", "targetWidth", "targetHeight", "encodingType",
// "mediaType", "allowEdit", "correctOrientation", "saveToPhotoAlbum" ] // "mediaType", "allowEdit", "correctOrientation", "saveToPhotoAlbum" ]
this.cameraOptions = new CameraOptions(); cameraOptions = new CameraOptions();
this.cameraOptions.Quality = int.Parse(args[0]); cameraOptions.Quality = int.Parse(args[0]);
this.cameraOptions.DestinationType = int.Parse(args[1]); cameraOptions.DestinationType = int.Parse(args[1]);
this.cameraOptions.PictureSourceType = int.Parse(args[2]); cameraOptions.PictureSourceType = int.Parse(args[2]);
this.cameraOptions.TargetWidth = int.Parse(args[3]); cameraOptions.TargetWidth = int.Parse(args[3]);
this.cameraOptions.TargetHeight = int.Parse(args[4]); cameraOptions.TargetHeight = int.Parse(args[4]);
this.cameraOptions.EncodingType = int.Parse(args[5]); cameraOptions.EncodingType = int.Parse(args[5]);
this.cameraOptions.MediaType = int.Parse(args[6]); cameraOptions.MediaType = int.Parse(args[6]);
this.cameraOptions.AllowEdit = bool.Parse(args[7]); cameraOptions.AllowEdit = bool.Parse(args[7]);
this.cameraOptions.CorrectOrientation = bool.Parse(args[8]); cameraOptions.CorrectOrientation = bool.Parse(args[8]);
this.cameraOptions.SaveToPhotoAlbum = bool.Parse(args[9]); cameraOptions.SaveToPhotoAlbum = bool.Parse(args[9]);
//this.cameraOptions = String.IsNullOrEmpty(options) ?
// new CameraOptions() : JSON.JsonHelper.Deserialize<CameraOptions>(options);
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -224,15 +209,13 @@ namespace WPCordovaClassLib.Cordova.Commands
if (cameraOptions.PictureSourceType == CAMERA) if (cameraOptions.PictureSourceType == CAMERA)
{ {
cameraTask = new CameraCaptureTask(); CameraCaptureTask cameraTask = new CameraCaptureTask();
cameraTask.Completed += onCameraTaskCompleted; cameraTask.Completed += onCameraTaskCompleted;
cameraTask.Show(); cameraTask.Show();
} }
else else if ((cameraOptions.PictureSourceType == PHOTOLIBRARY) || (cameraOptions.PictureSourceType == SAVEDPHOTOALBUM))
{ {
if ((cameraOptions.PictureSourceType == PHOTOLIBRARY) || (cameraOptions.PictureSourceType == SAVEDPHOTOALBUM)) PhotoChooserTask photoChooserTask = new PhotoChooserTask();
{
photoChooserTask = new PhotoChooserTask();
photoChooserTask.Completed += onPickerTaskCompleted; photoChooserTask.Completed += onPickerTaskCompleted;
photoChooserTask.Show(); photoChooserTask.Show();
} }
@ -240,12 +223,18 @@ namespace WPCordovaClassLib.Cordova.Commands
{ {
DispatchCommandResult(new PluginResult(PluginResult.Status.NO_RESULT)); DispatchCommandResult(new PluginResult(PluginResult.Status.NO_RESULT));
} }
}
} }
public void onCameraTaskCompleted(object sender, PhotoResult e) public void onCameraTaskCompleted(object sender, PhotoResult e)
{ {
var task = sender as ChooserBase<PhotoResult>;
if (task != null)
{
task.Completed -= onCameraTaskCompleted;
}
if (e.Error != null) if (e.Error != null)
{ {
DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR)); DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR));
@ -289,10 +278,7 @@ namespace WPCordovaClassLib.Cordova.Commands
// we should return stream position back after saving stream to media library // we should return stream position back after saving stream to media library
rotImageStream.Seek(0, SeekOrigin.Begin); rotImageStream.Seek(0, SeekOrigin.Begin);
imagePathOrContent = this.SaveImageToLocalStorage(rotImageStream, Path.GetFileName(e.OriginalFileName));
WriteableBitmap image = PictureDecoder.DecodeJpeg(rotImageStream);
imagePathOrContent = this.SaveImageToLocalStorage(image, Path.GetFileName(e.OriginalFileName));
} }
@ -329,6 +315,12 @@ namespace WPCordovaClassLib.Cordova.Commands
public void onPickerTaskCompleted(object sender, PhotoResult e) public void onPickerTaskCompleted(object sender, PhotoResult e)
{ {
var task = sender as ChooserBase<PhotoResult>;
if (task != null)
{
task.Completed -= onCameraTaskCompleted;
}
if (e.Error != null) if (e.Error != null)
{ {
DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR)); DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR));
@ -344,8 +336,7 @@ namespace WPCordovaClassLib.Cordova.Commands
if (cameraOptions.DestinationType == FILE_URI) if (cameraOptions.DestinationType == FILE_URI)
{ {
WriteableBitmap image = PictureDecoder.DecodeJpeg(e.ChosenPhoto); imagePathOrContent = this.SaveImageToLocalStorage(e.ChosenPhoto, Path.GetFileName(e.OriginalFileName));
imagePathOrContent = this.SaveImageToLocalStorage(image, Path.GetFileName(e.OriginalFileName));
} }
else if (cameraOptions.DestinationType == DATA_URL) else if (cameraOptions.DestinationType == DATA_URL)
{ {
@ -385,24 +376,30 @@ namespace WPCordovaClassLib.Cordova.Commands
/// <returns>Base64 representation of the image</returns> /// <returns>Base64 representation of the image</returns>
private string GetImageContent(Stream stream) private string GetImageContent(Stream stream)
{ {
int streamLength = (int)stream.Length; byte[] imageContent = null;
byte[] fileData = new byte[streamLength + 1];
stream.Read(fileData, 0, streamLength);
try
{
//use photo's actual width & height if user doesn't provide width & height //use photo's actual width & height if user doesn't provide width & height
if (cameraOptions.TargetWidth < 0 && cameraOptions.TargetHeight < 0) if (cameraOptions.TargetWidth < 0 && cameraOptions.TargetHeight < 0)
{ {
stream.Close(); int streamLength = (int)stream.Length;
return Convert.ToBase64String(fileData); imageContent = new byte[streamLength + 1];
stream.Read(imageContent, 0, streamLength);
} }
else else
{ {
// resize photo // resize photo
byte[] resizedFile = ResizePhoto(stream, fileData); imageContent = ResizePhoto(stream);
stream.Close();
return Convert.ToBase64String(resizedFile);
} }
} }
finally
{
stream.Dispose();
}
return Convert.ToBase64String(imageContent);
}
/// <summary> /// <summary>
/// Resize image /// Resize image
@ -410,51 +407,87 @@ namespace WPCordovaClassLib.Cordova.Commands
/// <param name="stream">Image stream</param> /// <param name="stream">Image stream</param>
/// <param name="fileData">File data</param> /// <param name="fileData">File data</param>
/// <returns>resized image</returns> /// <returns>resized image</returns>
private byte[] ResizePhoto(Stream stream, byte[] fileData) private byte[] ResizePhoto(Stream stream)
{ {
int streamLength = (int)stream.Length; //output
int intResult = 0;
byte[] resizedFile; byte[] resizedFile;
stream.Read(fileData, 0, streamLength);
BitmapImage objBitmap = new BitmapImage(); BitmapImage objBitmap = new BitmapImage();
MemoryStream objBitmapStream = new MemoryStream(fileData);
MemoryStream objBitmapStreamResized = new MemoryStream();
WriteableBitmap objWB;
objBitmap.SetSource(stream); objBitmap.SetSource(stream);
objWB = new WriteableBitmap(objBitmap); objBitmap.CreateOptions = BitmapCreateOptions.None;
WriteableBitmap objWB = new WriteableBitmap(objBitmap);
objBitmap.UriSource = null;
//Keep proportionally
double ratio = Math.Min((double)cameraOptions.TargetWidth / objWB.PixelWidth, (double)cameraOptions.TargetHeight / objWB.PixelHeight);
int width = Convert.ToInt32(ratio * objWB.PixelWidth);
int height = Convert.ToInt32(ratio * objWB.PixelHeight);
//Hold the result stream
using (MemoryStream objBitmapStreamResized = new MemoryStream())
{
try
{
// resize the photo with user defined TargetWidth & TargetHeight // resize the photo with user defined TargetWidth & TargetHeight
Extensions.SaveJpeg(objWB, objBitmapStreamResized, cameraOptions.TargetWidth, cameraOptions.TargetHeight, 0, cameraOptions.Quality); Extensions.SaveJpeg(objWB, objBitmapStreamResized, width, height, 0, cameraOptions.Quality);
}
finally
{
//Dispose bitmaps immediately, they are memory expensive
DisposeImage(objBitmap);
DisposeImage(objWB);
GC.Collect();
}
//Convert the resized stream to a byte array. //Convert the resized stream to a byte array.
streamLength = (int)objBitmapStreamResized.Length; int streamLength = (int)objBitmapStreamResized.Length;
resizedFile = new Byte[streamLength]; //-1 resizedFile = new Byte[streamLength]; //-1
objBitmapStreamResized.Position = 0; objBitmapStreamResized.Position = 0;
//for some reason we have to set Position to zero, but we don't have to earlier when we get the bytes from the chosen photo... //for some reason we have to set Position to zero, but we don't have to earlier when we get the bytes from the chosen photo...
intResult = objBitmapStreamResized.Read(resizedFile, 0, streamLength); objBitmapStreamResized.Read(resizedFile, 0, streamLength);
}
return resizedFile; return resizedFile;
} }
/// <summary>
/// Util: Dispose a bitmap resource
/// </summary>
/// <param name="image">BitmapSource subclass to dispose</param>
private void DisposeImage(BitmapSource image)
{
if (image != null)
{
try
{
using (var ms = new MemoryStream(new byte[] { 0x0 }))
{
image.SetSource(ms);
}
}
catch (Exception)
{
}
}
}
/// <summary> /// <summary>
/// Saves captured image in isolated storage /// Saves captured image in isolated storage
/// </summary> /// </summary>
/// <param name="imageFileName">image file name</param> /// <param name="imageFileName">image file name</param>
/// <returns>Image path</returns> /// <returns>Image path</returns>
private string SaveImageToLocalStorage(WriteableBitmap image, string imageFileName) private string SaveImageToLocalStorage(Stream stream, string imageFileName)
{ {
if (image == null) if (stream == null)
{ {
throw new ArgumentNullException("imageBytes"); throw new ArgumentNullException("imageBytes");
} }
try try
{ {
var isoFile = IsolatedStorageFile.GetUserStoreForApplication(); var isoFile = IsolatedStorageFile.GetUserStoreForApplication();
if (!isoFile.DirectoryExists(isoFolder)) if (!isoFile.DirectoryExists(isoFolder))
@ -464,16 +497,41 @@ namespace WPCordovaClassLib.Cordova.Commands
string filePath = System.IO.Path.Combine("///" + isoFolder + "/", imageFileName); string filePath = System.IO.Path.Combine("///" + isoFolder + "/", imageFileName);
using (var stream = isoFile.CreateFile(filePath)) using (IsolatedStorageFileStream outputStream = isoFile.CreateFile(filePath))
{ {
// resize image if Height and Width defined via options BitmapImage objBitmap = new BitmapImage();
if (cameraOptions.TargetHeight > 0 && cameraOptions.TargetWidth > 0) objBitmap.SetSource(stream);
objBitmap.CreateOptions = BitmapCreateOptions.None;
WriteableBitmap objWB = new WriteableBitmap(objBitmap);
objBitmap.UriSource = null;
try
{ {
image.SaveJpeg(stream, cameraOptions.TargetWidth, cameraOptions.TargetHeight, 0, cameraOptions.Quality);
//use photo's actual width & height if user doesn't provide width & height
if (cameraOptions.TargetWidth < 0 && cameraOptions.TargetHeight < 0)
{
objWB.SaveJpeg(outputStream, objWB.PixelWidth, objWB.PixelHeight, 0, cameraOptions.Quality);
} }
else else
{ {
image.SaveJpeg(stream, image.PixelWidth, image.PixelHeight, 0, cameraOptions.Quality); //Resize
//Keep proportionally
double ratio = Math.Min((double)cameraOptions.TargetWidth / objWB.PixelWidth, (double)cameraOptions.TargetHeight / objWB.PixelHeight);
int width = Convert.ToInt32(ratio * objWB.PixelWidth);
int height = Convert.ToInt32(ratio * objWB.PixelHeight);
// resize the photo with user defined TargetWidth & TargetHeight
objWB.SaveJpeg(outputStream, width, height, 0, cameraOptions.Quality);
}
}
finally
{
//Dispose bitmaps immediately, they are memory expensive
DisposeImage(objBitmap);
DisposeImage(objWB);
GC.Collect();
} }
} }
@ -484,6 +542,10 @@ namespace WPCordovaClassLib.Cordova.Commands
//TODO: log or do something else //TODO: log or do something else
throw; throw;
} }
finally
{
stream.Dispose();
}
} }
} }