From 19a44608b84966df1cf1a32df46a157e44bfffc0 Mon Sep 17 00:00:00 2001 From: Andrew Grieve Date: Thu, 27 Feb 2014 11:56:29 -0500 Subject: [PATCH 01/20] CB-6114 Updated version and RELEASENOTES.md for release 0.2.8 --- RELEASENOTES.md | 3 +++ plugin.xml | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 91cabf2..531019e 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -68,3 +68,6 @@ * Documented quirk for CB-5335 + CB-5206 for WP7+8 * reference the correct firefoxos implementation * [BlackBerry10] Add permission to access_shared + +### 0.2.8 (Feb 26, 2014) +* CB-1826 Catch OOM on gallery image resize diff --git a/plugin.xml b/plugin.xml index 7233291..b2db93e 100644 --- a/plugin.xml +++ b/plugin.xml @@ -3,7 +3,7 @@ + version="0.2.8"> Camera Cordova Camera Plugin Apache 2.0 From 3be1802c9e5474b9e36d4dbc66ccb42648ff2a43 Mon Sep 17 00:00:00 2001 From: Andrew Grieve Date: Thu, 27 Feb 2014 12:29:15 -0500 Subject: [PATCH 02/20] CB-6114 Incremented plugin version on dev branch. --- plugin.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin.xml b/plugin.xml index b2db93e..4b89899 100644 --- a/plugin.xml +++ b/plugin.xml @@ -3,7 +3,7 @@ + version="0.2.9-dev"> Camera Cordova Camera Plugin Apache 2.0 From 61a963d6fb6e4540253af1f2219db6556824af3c Mon Sep 17 00:00:00 2001 From: Andrew Grieve Date: Thu, 27 Feb 2014 15:36:31 -0500 Subject: [PATCH 03/20] Add NOTICE file --- NOTICE | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 NOTICE diff --git a/NOTICE b/NOTICE new file mode 100644 index 0000000..8ec56a5 --- /dev/null +++ b/NOTICE @@ -0,0 +1,5 @@ +Apache Cordova +Copyright 2012 The Apache Software Foundation + +This product includes software developed at +The Apache Software Foundation (http://www.apache.org/). From 5393a28191e8f53fb073f05c40a6133f68685274 Mon Sep 17 00:00:00 2001 From: Ryan Willoughby Date: Thu, 6 Mar 2014 13:50:49 -0800 Subject: [PATCH 04/20] Add rim xml namespaces declaration --- plugin.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/plugin.xml b/plugin.xml index 4b89899..de07a06 100644 --- a/plugin.xml +++ b/plugin.xml @@ -2,6 +2,7 @@ Camera From c9bab1f94c89e53c0ad8dbc77e1027eaa89527f1 Mon Sep 17 00:00:00 2001 From: James Jong Date: Thu, 13 Mar 2014 09:51:47 -0400 Subject: [PATCH 05/20] CB-6212 iOS: fix warnings compiled under arm64 64-bit --- src/ios/CDVCamera.m | 12 ++++++------ src/ios/CDVJpegHeaderWriter.m | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/ios/CDVCamera.m b/src/ios/CDVCamera.m index 42c5237..8413ac5 100644 --- a/src/ios/CDVCamera.m +++ b/src/ios/CDVCamera.m @@ -84,7 +84,7 @@ static NSSet* org_apache_cordova_validArrowDirections; bool hasCamera = [UIImagePickerController isSourceTypeAvailable:sourceType]; if (!hasCamera) { - NSLog(@"Camera.getPicture: source type %d not available.", sourceType); + NSLog(@"Camera.getPicture: source type %ld not available.", sourceType); CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"no camera available"]; [self.commandDelegate sendPluginResult:result callbackId:callbackId]; return; @@ -170,10 +170,10 @@ static NSSet* org_apache_cordova_validArrowDirections; - (void)displayPopover:(NSDictionary*)options { - int x = 0; - int y = 32; - int width = 320; - int height = 480; + NSInteger x = 0; + NSInteger y = 32; + NSInteger width = 320; + NSInteger height = 480; UIPopoverArrowDirection arrowDirection = UIPopoverArrowDirectionAny; if (options) { @@ -182,7 +182,7 @@ static NSSet* org_apache_cordova_validArrowDirections; width = [options integerValueForKey:@"width" defaultValue:320]; height = [options integerValueForKey:@"height" defaultValue:480]; arrowDirection = [options integerValueForKey:@"arrowDir" defaultValue:UIPopoverArrowDirectionAny]; - if (![org_apache_cordova_validArrowDirections containsObject:[NSNumber numberWithInt:arrowDirection]]) { + if (![org_apache_cordova_validArrowDirections containsObject:[NSNumber numberWithUnsignedInteger:arrowDirection]]) { arrowDirection = UIPopoverArrowDirectionAny; } } diff --git a/src/ios/CDVJpegHeaderWriter.m b/src/ios/CDVJpegHeaderWriter.m index 93cafb8..4d3ea24 100644 --- a/src/ios/CDVJpegHeaderWriter.m +++ b/src/ios/CDVJpegHeaderWriter.m @@ -190,7 +190,7 @@ const uint mTiffLength = 0x2a; // after byte align bits, next to bits are 0x002a // construct the complete app1 data block app1 = [[NSMutableString alloc] initWithFormat: @"%@%04x%@%@%@%@%@", app1marker, - 16 + ([exifIFD length]/2) + ([subExifIFD length]/2) /*16+[exifIFD length]/2*/, + (unsigned int)(16 + ([exifIFD length]/2) + ([subExifIFD length]/2)) /*16+[exifIFD length]/2*/, exifmarker, tiffheader, ifd0offset, @@ -268,10 +268,10 @@ const uint mTiffLength = 0x2a; // after byte align bits, next to bits are 0x002a } // calculate IFD0 terminal offset tags, currently ExifSubIFD - int entrycount = [ifdblock count]; + unsigned int entrycount = (unsigned int)[ifdblock count]; if (ifd0flag) { // 18 accounts for 8769's width + offset to next ifd, 8 accounts for start of header - NSNumber * offset = [NSNumber numberWithInt:[exifstr length] / 2 + [dbstr length] / 2 + 18+8]; + NSNumber * offset = [NSNumber numberWithUnsignedInteger:[exifstr length] / 2 + [dbstr length] / 2 + 18+8]; [self appendExifOffsetTagTo: exifstr withOffset : offset]; @@ -293,7 +293,7 @@ const uint mTiffLength = 0x2a; // after byte align bits, next to bits are 0x002a NSNumber * dataformat = [formtemplate objectAtIndex:1]; NSNumber * components = [formtemplate objectAtIndex:2]; if([components intValue] == 0) { - components = [NSNumber numberWithInt: [data length] * DataTypeToWidth[[dataformat intValue]-1]]; + components = [NSNumber numberWithUnsignedInteger:[data length] * DataTypeToWidth[[dataformat intValue]-1]]; } return [[NSString alloc] initWithFormat: @"%@%@%08x", @@ -344,7 +344,7 @@ const uint mTiffLength = 0x2a; // after byte align bits, next to bits are 0x002a [datastr appendString:[dataformat objectAtIndex:3]]; } if ([datastr length] < 8) { - NSString * format = [NSString stringWithFormat:@"%%0%dd", 8 - [datastr length]]; + NSString * format = [NSString stringWithFormat:@"%%0%dd", (int)(8 - [datastr length])]; [datastr appendFormat:format,0]; } return datastr; @@ -464,7 +464,7 @@ const uint mTiffLength = 0x2a; // after byte align bits, next to bits are 0x002a -(void) expandContinuedFraction: (NSArray*) fractionlist withResultNumerator: (NSNumber**) numerator withResultDenominator: (NSNumber**) denominator { - int i = 0; + NSUInteger i = 0; int den = 0; int num = 0; if ([fractionlist count] == 1) { From 6fb63fede54e4f5defe45d3bf8a6945fbd8ff352 Mon Sep 17 00:00:00 2001 From: Long Nguyen Date: Thu, 13 Mar 2014 15:29:38 +0700 Subject: [PATCH 06/20] Fix typo error in docs from libarary to library github: close #18 --- doc/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/index.md b/doc/index.md index c36d6cb..edd881f 100644 --- a/doc/index.md +++ b/doc/index.md @@ -20,7 +20,7 @@ # org.apache.cordova.camera This plugin provides an API for taking pictures and for choosing images from -the system's image libarary. +the system's image library. cordova plugin add org.apache.cordova.camera From 91d6e10b29e492d8d72a2d5c759ebf7b876ecc3b Mon Sep 17 00:00:00 2001 From: James Jong Date: Thu, 13 Mar 2014 10:16:30 -0400 Subject: [PATCH 07/20] CB-6212 iOS: fix warnings compiled under arm64 64-bit -one update to CDVCamera.m --- src/ios/CDVCamera.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ios/CDVCamera.m b/src/ios/CDVCamera.m index 8413ac5..9981747 100644 --- a/src/ios/CDVCamera.m +++ b/src/ios/CDVCamera.m @@ -84,7 +84,7 @@ static NSSet* org_apache_cordova_validArrowDirections; bool hasCamera = [UIImagePickerController isSourceTypeAvailable:sourceType]; if (!hasCamera) { - NSLog(@"Camera.getPicture: source type %ld not available.", sourceType); + NSLog(@"Camera.getPicture: source type %lu not available.", (unsigned long)sourceType); CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"no camera available"]; [self.commandDelegate sendPluginResult:result callbackId:callbackId]; return; From e7a3d70fe969f75b795ca216dd9173f8df1635ee Mon Sep 17 00:00:00 2001 From: Jesse MacFadyen Date: Mon, 7 Apr 2014 15:00:12 -0700 Subject: [PATCH 08/20] Fix camera issues, cropping, memory leaks CB-4027, CB-5102, CB-2737, CB-2387 --- src/wp/Camera.cs | 228 ++++++++++++++++++++++++++++++----------------- 1 file changed, 145 insertions(+), 83 deletions(-) diff --git a/src/wp/Camera.cs b/src/wp/Camera.cs index 5ff8045..53ae61a 100644 --- a/src/wp/Camera.cs +++ b/src/wp/Camera.cs @@ -113,8 +113,6 @@ namespace WPCordovaClassLib.Cordova.Commands [DataMember(IsRequired = false, Name = "correctOrientation")] public bool CorrectOrientation { get; set; } - - /// /// Ignored /// @@ -176,16 +174,6 @@ namespace WPCordovaClassLib.Cordova.Commands } } - /// - /// Used to open photo library - /// - PhotoChooserTask photoChooserTask; - - /// - /// Used to open camera application - /// - CameraCaptureTask cameraTask; - /// /// Camera options /// @@ -198,20 +186,17 @@ namespace WPCordovaClassLib.Cordova.Commands string[] args = JSON.JsonHelper.Deserialize(options); // ["quality", "destinationType", "sourceType", "targetWidth", "targetHeight", "encodingType", // "mediaType", "allowEdit", "correctOrientation", "saveToPhotoAlbum" ] - this.cameraOptions = new CameraOptions(); - this.cameraOptions.Quality = int.Parse(args[0]); - this.cameraOptions.DestinationType = int.Parse(args[1]); - this.cameraOptions.PictureSourceType = int.Parse(args[2]); - this.cameraOptions.TargetWidth = int.Parse(args[3]); - this.cameraOptions.TargetHeight = int.Parse(args[4]); - this.cameraOptions.EncodingType = int.Parse(args[5]); - this.cameraOptions.MediaType = int.Parse(args[6]); - this.cameraOptions.AllowEdit = bool.Parse(args[7]); - this.cameraOptions.CorrectOrientation = bool.Parse(args[8]); - this.cameraOptions.SaveToPhotoAlbum = bool.Parse(args[9]); - - //this.cameraOptions = String.IsNullOrEmpty(options) ? - // new CameraOptions() : JSON.JsonHelper.Deserialize(options); + cameraOptions = new CameraOptions(); + cameraOptions.Quality = int.Parse(args[0]); + cameraOptions.DestinationType = int.Parse(args[1]); + cameraOptions.PictureSourceType = int.Parse(args[2]); + cameraOptions.TargetWidth = int.Parse(args[3]); + cameraOptions.TargetHeight = int.Parse(args[4]); + cameraOptions.EncodingType = int.Parse(args[5]); + cameraOptions.MediaType = int.Parse(args[6]); + cameraOptions.AllowEdit = bool.Parse(args[7]); + cameraOptions.CorrectOrientation = bool.Parse(args[8]); + cameraOptions.SaveToPhotoAlbum = bool.Parse(args[9]); } catch (Exception ex) { @@ -224,28 +209,32 @@ namespace WPCordovaClassLib.Cordova.Commands if (cameraOptions.PictureSourceType == CAMERA) { - cameraTask = new CameraCaptureTask(); + CameraCaptureTask cameraTask = new CameraCaptureTask(); cameraTask.Completed += onCameraTaskCompleted; cameraTask.Show(); } + else if ((cameraOptions.PictureSourceType == PHOTOLIBRARY) || (cameraOptions.PictureSourceType == SAVEDPHOTOALBUM)) + { + PhotoChooserTask photoChooserTask = new PhotoChooserTask(); + photoChooserTask.Completed += onPickerTaskCompleted; + photoChooserTask.Show(); + } else { - if ((cameraOptions.PictureSourceType == PHOTOLIBRARY) || (cameraOptions.PictureSourceType == SAVEDPHOTOALBUM)) - { - photoChooserTask = new PhotoChooserTask(); - photoChooserTask.Completed += onPickerTaskCompleted; - photoChooserTask.Show(); - } - else - { - DispatchCommandResult(new PluginResult(PluginResult.Status.NO_RESULT)); - } + DispatchCommandResult(new PluginResult(PluginResult.Status.NO_RESULT)); } + } public void onCameraTaskCompleted(object sender, PhotoResult e) { + var task = sender as ChooserBase; + if (task != null) + { + task.Completed -= onCameraTaskCompleted; + } + if (e.Error != null) { 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 rotImageStream.Seek(0, SeekOrigin.Begin); - - WriteableBitmap image = PictureDecoder.DecodeJpeg(rotImageStream); - - imagePathOrContent = this.SaveImageToLocalStorage(image, Path.GetFileName(e.OriginalFileName)); + imagePathOrContent = this.SaveImageToLocalStorage(rotImageStream, Path.GetFileName(e.OriginalFileName)); } @@ -329,6 +315,12 @@ namespace WPCordovaClassLib.Cordova.Commands public void onPickerTaskCompleted(object sender, PhotoResult e) { + var task = sender as ChooserBase; + if (task != null) + { + task.Completed -= onCameraTaskCompleted; + } + if (e.Error != null) { DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR)); @@ -344,8 +336,7 @@ namespace WPCordovaClassLib.Cordova.Commands if (cameraOptions.DestinationType == FILE_URI) { - WriteableBitmap image = PictureDecoder.DecodeJpeg(e.ChosenPhoto); - imagePathOrContent = this.SaveImageToLocalStorage(image, Path.GetFileName(e.OriginalFileName)); + imagePathOrContent = this.SaveImageToLocalStorage(e.ChosenPhoto, Path.GetFileName(e.OriginalFileName)); } else if (cameraOptions.DestinationType == DATA_URL) { @@ -385,23 +376,29 @@ namespace WPCordovaClassLib.Cordova.Commands /// Base64 representation of the image private string GetImageContent(Stream stream) { - int streamLength = (int)stream.Length; - byte[] fileData = new byte[streamLength + 1]; - stream.Read(fileData, 0, streamLength); + byte[] imageContent = null; - //use photo's actual width & height if user doesn't provide width & height - if (cameraOptions.TargetWidth < 0 && cameraOptions.TargetHeight < 0) + try { - stream.Close(); - return Convert.ToBase64String(fileData); + //use photo's actual width & height if user doesn't provide width & height + if (cameraOptions.TargetWidth < 0 && cameraOptions.TargetHeight < 0) + { + int streamLength = (int)stream.Length; + imageContent = new byte[streamLength + 1]; + stream.Read(imageContent, 0, streamLength); + } + else + { + // resize photo + imageContent = ResizePhoto(stream); + } } - else + finally { - // resize photo - byte[] resizedFile = ResizePhoto(stream, fileData); - stream.Close(); - return Convert.ToBase64String(resizedFile); + stream.Dispose(); } + + return Convert.ToBase64String(imageContent); } /// @@ -410,51 +407,87 @@ namespace WPCordovaClassLib.Cordova.Commands /// Image stream /// File data /// resized image - private byte[] ResizePhoto(Stream stream, byte[] fileData) + private byte[] ResizePhoto(Stream stream) { - int streamLength = (int)stream.Length; - int intResult = 0; - + //output byte[] resizedFile; - stream.Read(fileData, 0, streamLength); - BitmapImage objBitmap = new BitmapImage(); - MemoryStream objBitmapStream = new MemoryStream(fileData); - MemoryStream objBitmapStreamResized = new MemoryStream(); - WriteableBitmap objWB; objBitmap.SetSource(stream); - objWB = new WriteableBitmap(objBitmap); + objBitmap.CreateOptions = BitmapCreateOptions.None; - // resize the photo with user defined TargetWidth & TargetHeight - Extensions.SaveJpeg(objWB, objBitmapStreamResized, cameraOptions.TargetWidth, cameraOptions.TargetHeight, 0, cameraOptions.Quality); + WriteableBitmap objWB = new WriteableBitmap(objBitmap); + objBitmap.UriSource = null; - //Convert the resized stream to a byte array. - streamLength = (int)objBitmapStreamResized.Length; - resizedFile = new Byte[streamLength]; //-1 - 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... - intResult = objBitmapStreamResized.Read(resizedFile, 0, streamLength); + //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 + 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. + int streamLength = (int)objBitmapStreamResized.Length; + resizedFile = new Byte[streamLength]; //-1 + 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... + objBitmapStreamResized.Read(resizedFile, 0, streamLength); + } return resizedFile; } + /// + /// Util: Dispose a bitmap resource + /// + /// BitmapSource subclass to dispose + private void DisposeImage(BitmapSource image) + { + if (image != null) + { + try + { + using (var ms = new MemoryStream(new byte[] { 0x0 })) + { + image.SetSource(ms); + } + } + catch (Exception) + { + } + } + } + /// /// Saves captured image in isolated storage /// /// image file name /// Image path - private string SaveImageToLocalStorage(WriteableBitmap image, string imageFileName) + private string SaveImageToLocalStorage(Stream stream, string imageFileName) { - if (image == null) + if (stream == null) { throw new ArgumentNullException("imageBytes"); } try { - - var isoFile = IsolatedStorageFile.GetUserStoreForApplication(); if (!isoFile.DirectoryExists(isoFolder)) @@ -464,16 +497,41 @@ namespace WPCordovaClassLib.Cordova.Commands 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 - if (cameraOptions.TargetHeight > 0 && cameraOptions.TargetWidth > 0) + BitmapImage objBitmap = new BitmapImage(); + 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 + { + //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); + } } - else + finally { - image.SaveJpeg(stream, image.PixelWidth, image.PixelHeight, 0, cameraOptions.Quality); + //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 throw; } + finally + { + stream.Dispose(); + } } } From f6e8548381db0e7e4b7d6e095c6718313a001edf Mon Sep 17 00:00:00 2001 From: Jesse MacFadyen Date: Mon, 7 Apr 2014 16:32:21 -0700 Subject: [PATCH 09/20] combining callbacks, removing lots of dupe code --- src/wp/Camera.cs | 99 +++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 94 insertions(+), 5 deletions(-) diff --git a/src/wp/Camera.cs b/src/wp/Camera.cs index 53ae61a..c9f6f78 100644 --- a/src/wp/Camera.cs +++ b/src/wp/Camera.cs @@ -200,7 +200,7 @@ namespace WPCordovaClassLib.Cordova.Commands } catch (Exception ex) { - this.DispatchCommandResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION, ex.Message)); + DispatchCommandResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION, ex.Message)); return; } @@ -225,6 +225,95 @@ namespace WPCordovaClassLib.Cordova.Commands } + } + + public void onTaskCompleted(object sender, PhotoResult e) + { + var task = sender as ChooserBase; + if (task != null) + { + task.Completed -= onCameraTaskCompleted; + } + + if (e.Error != null) + { + DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR)); + return; + } + + switch (e.TaskResult) + { + case TaskResult.OK: + try + { + string imagePathOrContent = string.Empty; + if (cameraOptions.DestinationType == FILE_URI) + { + // Save image in media library + if (cameraOptions.SaveToPhotoAlbum) + { + MediaLibrary library = new MediaLibrary(); + Picture pict = library.SavePicture(e.OriginalFileName, e.ChosenPhoto); // to save to photo-roll ... + } + + /* + int orient = ImageExifHelper.getImageOrientationFromStream(e.ChosenPhoto); + int newAngle = 0; + switch (orient) + { + case ImageExifOrientation.LandscapeLeft: + newAngle = 90; + break; + case ImageExifOrientation.PortraitUpsideDown: + newAngle = 180; + break; + case ImageExifOrientation.LandscapeRight: + newAngle = 270; + break; + case ImageExifOrientation.Portrait: + default: break; // 0 default already set + } + + Stream rotImageStream = ImageExifHelper.RotateStream(e.ChosenPhoto, newAngle); + + // we should reset stream position after saving stream to media library + rotImageStream.Seek(0, SeekOrigin.Begin); + imagePathOrContent = SaveImageToLocalStorage(rotImageStream, Path.GetFileName(e.OriginalFileName)); + + */ + + + imagePathOrContent = SaveImageToLocalStorage(e.ChosenPhoto, Path.GetFileName(e.OriginalFileName)); + } + else if (cameraOptions.DestinationType == DATA_URL) + { + imagePathOrContent = GetImageContent(e.ChosenPhoto); + } + else + { + // TODO: shouldn't this happen before we launch the camera-picker? + DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "Incorrect option: destinationType")); + return; + } + DispatchCommandResult(new PluginResult(PluginResult.Status.OK, imagePathOrContent)); + + } + catch (Exception) + { + DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "Error retrieving image.")); + } + break; + + case TaskResult.Cancel: + DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "Selection cancelled.")); + break; + + default: + DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "Selection did not complete!")); + break; + } + + } public void onCameraTaskCompleted(object sender, PhotoResult e) @@ -278,13 +367,13 @@ namespace WPCordovaClassLib.Cordova.Commands // we should return stream position back after saving stream to media library rotImageStream.Seek(0, SeekOrigin.Begin); - imagePathOrContent = this.SaveImageToLocalStorage(rotImageStream, Path.GetFileName(e.OriginalFileName)); + imagePathOrContent = SaveImageToLocalStorage(rotImageStream, Path.GetFileName(e.OriginalFileName)); } else if (cameraOptions.DestinationType == DATA_URL) { - imagePathOrContent = this.GetImageContent(e.ChosenPhoto); + imagePathOrContent = GetImageContent(e.ChosenPhoto); } else { @@ -336,11 +425,11 @@ namespace WPCordovaClassLib.Cordova.Commands if (cameraOptions.DestinationType == FILE_URI) { - imagePathOrContent = this.SaveImageToLocalStorage(e.ChosenPhoto, Path.GetFileName(e.OriginalFileName)); + imagePathOrContent = SaveImageToLocalStorage(e.ChosenPhoto, Path.GetFileName(e.OriginalFileName)); } else if (cameraOptions.DestinationType == DATA_URL) { - imagePathOrContent = this.GetImageContent(e.ChosenPhoto); + imagePathOrContent = GetImageContent(e.ChosenPhoto); } else From ae2acd9ab237df976bd90c3da2fcf0498b027935 Mon Sep 17 00:00:00 2001 From: Jesse MacFadyen Date: Mon, 7 Apr 2014 18:13:57 -0700 Subject: [PATCH 10/20] cleanup, finalize implementations/consolidations --- src/wp/Camera.cs | 262 ++++++++++++----------------------------------- 1 file changed, 63 insertions(+), 199 deletions(-) diff --git a/src/wp/Camera.cs b/src/wp/Camera.cs index c9f6f78..977634b 100644 --- a/src/wp/Camera.cs +++ b/src/wp/Camera.cs @@ -204,27 +204,32 @@ namespace WPCordovaClassLib.Cordova.Commands return; } - //TODO Check if all the options are acceptable - + if(cameraOptions.DestinationType != Camera.FILE_URI && cameraOptions.DestinationType != Camera.DATA_URL ) + { + DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "Incorrect option: destinationType")); + return; + } + ChooserBase chooserTask = null; if (cameraOptions.PictureSourceType == CAMERA) { - CameraCaptureTask cameraTask = new CameraCaptureTask(); - cameraTask.Completed += onCameraTaskCompleted; - cameraTask.Show(); + chooserTask = new CameraCaptureTask(); } else if ((cameraOptions.PictureSourceType == PHOTOLIBRARY) || (cameraOptions.PictureSourceType == SAVEDPHOTOALBUM)) { - PhotoChooserTask photoChooserTask = new PhotoChooserTask(); - photoChooserTask.Completed += onPickerTaskCompleted; - photoChooserTask.Show(); + chooserTask = new PhotoChooserTask(); + } + // if chooserTask is still null, then PictureSourceType was invalid + if (chooserTask != null) + { + chooserTask.Completed += onTaskCompleted; + chooserTask.Show(); } else { + Debug.WriteLine("Unrecognized PictureSourceType :: " + cameraOptions.PictureSourceType.ToString()); DispatchCommandResult(new PluginResult(PluginResult.Status.NO_RESULT)); } - - } public void onTaskCompleted(object sender, PhotoResult e) @@ -232,96 +237,7 @@ namespace WPCordovaClassLib.Cordova.Commands var task = sender as ChooserBase; if (task != null) { - task.Completed -= onCameraTaskCompleted; - } - - if (e.Error != null) - { - DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR)); - return; - } - - switch (e.TaskResult) - { - case TaskResult.OK: - try - { - string imagePathOrContent = string.Empty; - if (cameraOptions.DestinationType == FILE_URI) - { - // Save image in media library - if (cameraOptions.SaveToPhotoAlbum) - { - MediaLibrary library = new MediaLibrary(); - Picture pict = library.SavePicture(e.OriginalFileName, e.ChosenPhoto); // to save to photo-roll ... - } - - /* - int orient = ImageExifHelper.getImageOrientationFromStream(e.ChosenPhoto); - int newAngle = 0; - switch (orient) - { - case ImageExifOrientation.LandscapeLeft: - newAngle = 90; - break; - case ImageExifOrientation.PortraitUpsideDown: - newAngle = 180; - break; - case ImageExifOrientation.LandscapeRight: - newAngle = 270; - break; - case ImageExifOrientation.Portrait: - default: break; // 0 default already set - } - - Stream rotImageStream = ImageExifHelper.RotateStream(e.ChosenPhoto, newAngle); - - // we should reset stream position after saving stream to media library - rotImageStream.Seek(0, SeekOrigin.Begin); - imagePathOrContent = SaveImageToLocalStorage(rotImageStream, Path.GetFileName(e.OriginalFileName)); - - */ - - - imagePathOrContent = SaveImageToLocalStorage(e.ChosenPhoto, Path.GetFileName(e.OriginalFileName)); - } - else if (cameraOptions.DestinationType == DATA_URL) - { - imagePathOrContent = GetImageContent(e.ChosenPhoto); - } - else - { - // TODO: shouldn't this happen before we launch the camera-picker? - DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "Incorrect option: destinationType")); - return; - } - DispatchCommandResult(new PluginResult(PluginResult.Status.OK, imagePathOrContent)); - - } - catch (Exception) - { - DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "Error retrieving image.")); - } - break; - - case TaskResult.Cancel: - DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "Selection cancelled.")); - break; - - default: - DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "Selection did not complete!")); - break; - } - - - } - - public void onCameraTaskCompleted(object sender, PhotoResult e) - { - var task = sender as ChooserBase; - if (task != null) - { - task.Completed -= onCameraTaskCompleted; + task.Completed -= onTaskCompleted; } if (e.Error != null) @@ -337,127 +253,75 @@ namespace WPCordovaClassLib.Cordova.Commands { string imagePathOrContent = string.Empty; - if (cameraOptions.DestinationType == FILE_URI) + // Save image back to media library + // only save to photoalbum if it didn't come from there ... + if (cameraOptions.PictureSourceType == CAMERA && cameraOptions.SaveToPhotoAlbum) { - // Save image in media library - if (cameraOptions.SaveToPhotoAlbum) - { - MediaLibrary library = new MediaLibrary(); - Picture pict = library.SavePicture(e.OriginalFileName, e.ChosenPhoto); // to save to photo-roll ... - } - - int orient = ImageExifHelper.getImageOrientationFromStream(e.ChosenPhoto); - int newAngle = 0; - switch (orient) - { - case ImageExifOrientation.LandscapeLeft: - newAngle = 90; - break; - case ImageExifOrientation.PortraitUpsideDown: - newAngle = 180; - break; - case ImageExifOrientation.LandscapeRight: - newAngle = 270; - break; - case ImageExifOrientation.Portrait: - default: break; // 0 default already set - } - - Stream rotImageStream = ImageExifHelper.RotateStream(e.ChosenPhoto, newAngle); - - // we should return stream position back after saving stream to media library - rotImageStream.Seek(0, SeekOrigin.Begin); - imagePathOrContent = SaveImageToLocalStorage(rotImageStream, Path.GetFileName(e.OriginalFileName)); - - + MediaLibrary library = new MediaLibrary(); + Picture pict = library.SavePicture(e.OriginalFileName, e.ChosenPhoto); // to save to photo-roll ... } - else if (cameraOptions.DestinationType == DATA_URL) + + int orient = ImageExifHelper.getImageOrientationFromStream(e.ChosenPhoto); + int newAngle = 270; + switch (orient) { - imagePathOrContent = GetImageContent(e.ChosenPhoto); + case ImageExifOrientation.LandscapeLeft: + newAngle = 90; + break; + case ImageExifOrientation.PortraitUpsideDown: + newAngle = 180; + break; + case ImageExifOrientation.LandscapeRight: + newAngle = 270; + break; + case ImageExifOrientation.Portrait: + default: break; // 0 default already set } - else + + if (newAngle != 0) { - // TODO: shouldn't this happen before we launch the camera-picker? - DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "Incorrect option: destinationType")); - return; + using (Stream rotImageStream = ImageExifHelper.RotateStream(e.ChosenPhoto, newAngle)) + { + // we should reset stream position after saving stream to media library + rotImageStream.Seek(0, SeekOrigin.Begin); + if (cameraOptions.DestinationType == DATA_URL) + { + imagePathOrContent = GetImageContent(rotImageStream); + } + else // FILE_URL + { + imagePathOrContent = SaveImageToLocalStorage(rotImageStream, Path.GetFileName(e.OriginalFileName)); + } + } + } + else // no need to reorient + { + if (cameraOptions.DestinationType == DATA_URL) + { + imagePathOrContent = GetImageContent(e.ChosenPhoto); + } + else // FILE_URL + { + imagePathOrContent = SaveImageToLocalStorage(e.ChosenPhoto, Path.GetFileName(e.OriginalFileName)); + } } DispatchCommandResult(new PluginResult(PluginResult.Status.OK, imagePathOrContent)); - } catch (Exception) { DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "Error retrieving image.")); } break; - case TaskResult.Cancel: DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "Selection cancelled.")); break; - - default: - DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "Selection did not complete!")); - break; - } - - } - - public void onPickerTaskCompleted(object sender, PhotoResult e) - { - var task = sender as ChooserBase; - if (task != null) - { - task.Completed -= onCameraTaskCompleted; - } - - if (e.Error != null) - { - DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR)); - return; - } - - switch (e.TaskResult) - { - case TaskResult.OK: - try - { - string imagePathOrContent = string.Empty; - - if (cameraOptions.DestinationType == FILE_URI) - { - imagePathOrContent = SaveImageToLocalStorage(e.ChosenPhoto, Path.GetFileName(e.OriginalFileName)); - } - else if (cameraOptions.DestinationType == DATA_URL) - { - imagePathOrContent = GetImageContent(e.ChosenPhoto); - - } - else - { - // TODO: shouldn't this happen before we launch the camera-picker? - DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "Incorrect option: destinationType")); - return; - } - - DispatchCommandResult(new PluginResult(PluginResult.Status.OK, imagePathOrContent)); - - } - catch (Exception) - { - DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "Error retrieving image.")); - } - break; - - case TaskResult.Cancel: - DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "Selection cancelled.")); - break; - default: DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "Selection did not complete!")); break; } } - + /// /// Returns image content in a form of base64 string /// From fe6dc72a755930a82ccf810e71d57fb10eac1b5b Mon Sep 17 00:00:00 2001 From: Jesse MacFadyen Date: Tue, 8 Apr 2014 12:03:22 -0700 Subject: [PATCH 11/20] Remove rotation test value --- src/wp/Camera.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wp/Camera.cs b/src/wp/Camera.cs index 977634b..87617e4 100644 --- a/src/wp/Camera.cs +++ b/src/wp/Camera.cs @@ -262,7 +262,7 @@ namespace WPCordovaClassLib.Cordova.Commands } int orient = ImageExifHelper.getImageOrientationFromStream(e.ChosenPhoto); - int newAngle = 270; + int newAngle = 0; switch (orient) { case ImageExifOrientation.LandscapeLeft: From 4c2c567fd874a861d4707b1a5c2e268fb56f4d76 Mon Sep 17 00:00:00 2001 From: Jesse MacFadyen Date: Tue, 8 Apr 2014 12:19:44 -0700 Subject: [PATCH 12/20] WP8 When only targetWidth or targetHeight is provided, use it as the only bound --- src/wp/Camera.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/wp/Camera.cs b/src/wp/Camera.cs index 87617e4..b76929b 100644 --- a/src/wp/Camera.cs +++ b/src/wp/Camera.cs @@ -197,6 +197,16 @@ namespace WPCordovaClassLib.Cordova.Commands cameraOptions.AllowEdit = bool.Parse(args[7]); cameraOptions.CorrectOrientation = bool.Parse(args[8]); cameraOptions.SaveToPhotoAlbum = bool.Parse(args[9]); + + // a very large number will force the other value to be the bound + if (cameraOptions.TargetWidth > -1 && cameraOptions.TargetHeight == -1) + { + cameraOptions.TargetHeight = 100000; + } + else if (cameraOptions.TargetHeight > -1 && cameraOptions.TargetWidth == -1) + { + cameraOptions.TargetWidth = 100000; + } } catch (Exception ex) { From 85a986f589e8dfaf3bb5ed4b24f4ea2e7c457826 Mon Sep 17 00:00:00 2001 From: Jesse MacFadyen Date: Tue, 8 Apr 2014 15:53:51 -0700 Subject: [PATCH 13/20] CB-6422 [windows8] use cordova/exec/proxy --- src/windows8/CameraProxy.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/windows8/CameraProxy.js b/src/windows8/CameraProxy.js index 65ebdf5..d46553b 100644 --- a/src/windows8/CameraProxy.js +++ b/src/windows8/CameraProxy.js @@ -351,4 +351,4 @@ module.exports = { } }; -require("cordova/windows8/commandProxy").add("Camera",module.exports); +require("cordova/exec/proxy").add("Camera",module.exports); From f2a41e4b5e5de71ac62803bf371ad2bcd7e901f3 Mon Sep 17 00:00:00 2001 From: Ian Clelland Date: Wed, 16 Apr 2014 16:15:25 -0400 Subject: [PATCH 14/20] CB-6460: Update license headers --- plugin.xml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/plugin.xml b/plugin.xml index de07a06..b9f1b02 100644 --- a/plugin.xml +++ b/plugin.xml @@ -1,4 +1,22 @@ + Date: Thu, 17 Apr 2014 10:53:20 -0400 Subject: [PATCH 15/20] CB-6452 Updated version and RELEASENOTES.md for release 0.2.9 --- RELEASENOTES.md | 9 +++++++++ plugin.xml | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 531019e..836bc27 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -71,3 +71,12 @@ ### 0.2.8 (Feb 26, 2014) * CB-1826 Catch OOM on gallery image resize + +### 0.2.9 (Apr 17, 2014) +* CB-6460: Update license headers +* CB-6422: [windows8] use cordova/exec/proxy +* [WP8] When only targetWidth or targetHeight is provided, use it as the only bound +* CB-4027, CB-5102, CB-2737, CB-2387: [WP] Fix camera issues, cropping, memory leaks +* CB-6212: [iOS] fix warnings compiled under arm64 64-bit +* [BlackBerry10] Add rim xml namespaces declaration +* Add NOTICE file diff --git a/plugin.xml b/plugin.xml index b9f1b02..252fc3e 100644 --- a/plugin.xml +++ b/plugin.xml @@ -22,7 +22,7 @@ xmlns:android="http://schemas.android.com/apk/res/android" xmlns:rim="http://www.blackberry.com/ns/widgets" id="org.apache.cordova.camera" - version="0.2.9-dev"> + version="0.2.9"> Camera Cordova Camera Plugin Apache 2.0 From d6af2098c1c4d97e0ae52a74b4d3938a73a3818d Mon Sep 17 00:00:00 2001 From: Ian Clelland Date: Thu, 17 Apr 2014 11:16:03 -0400 Subject: [PATCH 16/20] CB-6452 Incremented plugin version on dev branch. --- plugin.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin.xml b/plugin.xml index 252fc3e..a51f172 100644 --- a/plugin.xml +++ b/plugin.xml @@ -22,7 +22,7 @@ xmlns:android="http://schemas.android.com/apk/res/android" xmlns:rim="http://www.blackberry.com/ns/widgets" id="org.apache.cordova.camera" - version="0.2.9"> + version="0.2.10-dev"> Camera Cordova Camera Plugin Apache 2.0 From c7d88e8b34f51919dbc2d1e2ed9ff1abcc58737d Mon Sep 17 00:00:00 2001 From: Andrew Grieve Date: Tue, 29 Apr 2014 00:49:29 -0400 Subject: [PATCH 17/20] CB-6546 android: Add support for allowEdit Camera option GitHub: Close #12 --- src/android/CameraLauncher.java | 142 +++++++++++++++++++++++++++----- 1 file changed, 120 insertions(+), 22 deletions(-) diff --git a/src/android/CameraLauncher.java b/src/android/CameraLauncher.java index 57878ab..aa0ef1e 100755 --- a/src/android/CameraLauncher.java +++ b/src/android/CameraLauncher.java @@ -34,16 +34,18 @@ import org.json.JSONArray; import org.json.JSONException; import android.app.Activity; +import android.content.ActivityNotFoundException; import android.content.ContentValues; import android.content.Intent; import android.database.Cursor; import android.graphics.Bitmap; +import android.graphics.Bitmap.CompressFormat; import android.graphics.BitmapFactory; import android.graphics.Matrix; -import android.graphics.Bitmap.CompressFormat; import android.media.MediaScannerConnection; import android.media.MediaScannerConnection.MediaScannerConnectionClient; import android.net.Uri; +import android.os.Bundle; import android.os.Environment; import android.provider.MediaStore; import android.util.Base64; @@ -58,7 +60,7 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect private static final int DATA_URL = 0; // Return base64 encoded string private static final int FILE_URI = 1; // Return file uri (content://media/external/images/media/2 for Android) - private static final int NATIVE_URI = 2; // On Android, this is the same as FILE_URI + private static final int NATIVE_URI = 2; // On Android, this is the same as FILE_URI private static final int PHOTOLIBRARY = 0; // Choose image from picture library (same as SAVEDPHOTOALBUM for Android) private static final int CAMERA = 1; // Take picture from camera @@ -75,6 +77,7 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect private static final String GET_All = "Get All"; private static final String LOG_TAG = "CameraLauncher"; + private static final int CROP_CAMERA = 100; private int mQuality; // Compression quality hint (0-100: 0=low quality & high compression, 100=compress of max quality) private int targetWidth; // desired width of the image @@ -85,7 +88,7 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect private boolean saveToPhotoAlbum; // Should the picture be saved to the device's photo album private boolean correctOrientation; // Should the pictures orientation be corrected private boolean orientationCorrected; // Has the picture's orientation been corrected - //private boolean allowEdit; // Should we allow the user to crop the image. UNUSED. + private boolean allowEdit; // Should we allow the user to crop the image. public CallbackContext callbackContext; private int numPics; @@ -96,10 +99,10 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect /** * Executes the request and returns PluginResult. * - * @param action The action to execute. - * @param args JSONArry of arguments for the plugin. + * @param action The action to execute. + * @param args JSONArry of arguments for the plugin. * @param callbackContext The callback id used when calling back into JavaScript. - * @return A PluginResult object with a status and message. + * @return A PluginResult object with a status and message. */ public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException { this.callbackContext = callbackContext; @@ -121,7 +124,7 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect this.targetHeight = args.getInt(4); this.encodingType = args.getInt(5); this.mediaType = args.getInt(6); - //this.allowEdit = args.getBoolean(7); // This field is unused. + this.allowEdit = args.getBoolean(7); this.correctOrientation = args.getBoolean(8); this.saveToPhotoAlbum = args.getBoolean(9); @@ -139,7 +142,7 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect this.takePicture(destType, encodingType); } else if ((srcType == PHOTOLIBRARY) || (srcType == SAVEDPHOTOALBUM)) { - this.getImage(srcType, destType); + this.getImage(srcType, destType, encodingType); } } catch (IllegalArgumentException e) @@ -238,33 +241,84 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect * @param quality Compression quality hint (0-100: 0=low quality & high compression, 100=compress of max quality) * @param srcType The album to get image from. * @param returnType Set the type of image to return. + * @param encodingType */ // TODO: Images selected from SDCARD don't display correctly, but from CAMERA ALBUM do! - public void getImage(int srcType, int returnType) { + // TODO: Images from kitkat filechooser not going into crop function + public void getImage(int srcType, int returnType, int encodingType) { Intent intent = new Intent(); String title = GET_PICTURE; if (this.mediaType == PICTURE) { intent.setType("image/*"); - } - else if (this.mediaType == VIDEO) { + if (this.allowEdit) { + intent.setAction(Intent.ACTION_PICK); + intent.putExtra("crop", "true"); + if (this.targetHeight == this.targetWidth) { + intent.putExtra("aspectX", 1); + intent.putExtra("aspectY", 1); + } + intent.putExtra("outputX", this.targetWidth); + intent.putExtra("outputY", this.targetHeight); + File photo = createCaptureFile(encodingType); + intent.putExtra(android.provider.MediaStore.EXTRA_OUTPUT, + Uri.fromFile(photo)); + } else { + intent.setAction(Intent.ACTION_GET_CONTENT); + intent.addCategory(Intent.CATEGORY_OPENABLE); + } + } else if (this.mediaType == VIDEO) { intent.setType("video/*"); title = GET_VIDEO; - } - else if (this.mediaType == ALLMEDIA) { + intent.setAction(Intent.ACTION_GET_CONTENT); + intent.addCategory(Intent.CATEGORY_OPENABLE); + } else if (this.mediaType == ALLMEDIA) { // I wanted to make the type 'image/*, video/*' but this does not work on all versions // of android so I had to go with the wildcard search. intent.setType("*/*"); title = GET_All; - } - - intent.setAction(Intent.ACTION_GET_CONTENT); - intent.addCategory(Intent.CATEGORY_OPENABLE); + intent.setAction(Intent.ACTION_GET_CONTENT); + intent.addCategory(Intent.CATEGORY_OPENABLE); + } if (this.cordova != null) { this.cordova.startActivityForResult((CordovaPlugin) this, Intent.createChooser(intent, new String(title)), (srcType + 1) * 16 + returnType + 1); } } + /** + * Brings up the UI to perform crop on passed image URI + * + * @param picUri + */ + private void performCrop(Uri picUri) { + try { + Intent cropIntent = new Intent("com.android.camera.action.CROP"); + // indicate image type and Uri + cropIntent.setDataAndType(picUri, "image/*"); + // set crop properties + cropIntent.putExtra("crop", "true"); + if (this.targetHeight == this.targetWidth) { + cropIntent.putExtra("aspectX", 1); + cropIntent.putExtra("aspectY", 1); + } + // indicate output X and Y + cropIntent.putExtra("outputX", this.targetWidth); + cropIntent.putExtra("outputY", this.targetHeight); + // retrieve data on return + cropIntent.putExtra("return-data", true); + // start the activity - we handle returning in onActivityResult + + if (this.cordova != null) { + this.cordova.startActivityForResult((CordovaPlugin) this, + cropIntent, CROP_CAMERA); + } + } catch (ActivityNotFoundException anfe) { + Log.e(LOG_TAG, "Crop operation not supported on this device"); + // Send Uri back to JavaScript for viewing image + this.callbackContext.success(picUri.toString()); + } + } + /** * Applies all needed transformation to the image received from the camera. * @@ -355,7 +409,12 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect exif.createOutFile(exifPath); exif.writeExifData(); } - + if (this.allowEdit) { + performCrop(uri); + } else { + // Send Uri back to JavaScript for viewing image + this.callbackContext.success(uri.toString()); + } } // Send Uri back to JavaScript for viewing image this.callbackContext.success(uri.toString()); @@ -364,8 +423,8 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect this.cleanup(FILE_URI, this.imageUri, uri, bitmap); bitmap = null; } - - private String ouputModifiedBitmap(Bitmap bitmap, Uri uri) throws IOException { + +private String ouputModifiedBitmap(Bitmap bitmap, Uri uri) throws IOException { // Create an ExifHelper to save the exif data that is lost during compression String modifiedPath = getTempDirectoryPath() + "/modified.jpg"; @@ -392,7 +451,7 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect return modifiedPath; } - /** +/** * Applies all needed transformation to the image received from the gallery. * * @param destType In which form should we return the image @@ -495,7 +554,46 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect // Get src and dest types from request code int srcType = (requestCode / 16) - 1; int destType = (requestCode % 16) - 1; + // if camera crop + if (requestCode == CROP_CAMERA) { + if (resultCode == Activity.RESULT_OK) { + // // get the returned data + Bundle extras = intent.getExtras(); + // get the cropped bitmap + Bitmap thePic = extras.getParcelable("data"); + // now save the bitmap to a file + OutputStream fOut = null; + File temp_file = new File(getTempDirectoryPath(), + System.currentTimeMillis() + ".jpg"); + try { + temp_file.createNewFile(); + fOut = new FileOutputStream(temp_file); + thePic.compress(Bitmap.CompressFormat.JPEG, this.mQuality, + fOut); + fOut.flush(); + fOut.close(); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + + // // Send Uri back to JavaScript for viewing image + this.callbackContext + .success(Uri.fromFile(temp_file).toString()); + + }// If cancelled + else if (resultCode == Activity.RESULT_CANCELED) { + this.failPicture("Camera cancelled."); + } + + // If something else + else { + this.failPicture("Did not complete!"); + } + + } // If CAMERA if (srcType == CAMERA) { // If image available @@ -863,4 +961,4 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect public void onScanCompleted(String path, Uri uri) { this.conn.disconnect(); } -} +} \ No newline at end of file From d899d7a4b814c7a0bcbadba42f9d93ff2f26fab4 Mon Sep 17 00:00:00 2001 From: Andrew Grieve Date: Tue, 29 Apr 2014 00:51:09 -0400 Subject: [PATCH 18/20] CB-6546 android: Fix a couple bugs with allowEdit pull request - Don't set width/height when they are not specified - photolibrary returns null from getData when image is cropped --- src/android/CameraLauncher.java | 232 +++++++++++++++++--------------- 1 file changed, 127 insertions(+), 105 deletions(-) diff --git a/src/android/CameraLauncher.java b/src/android/CameraLauncher.java index aa0ef1e..96c15da 100755 --- a/src/android/CameraLauncher.java +++ b/src/android/CameraLauncher.java @@ -60,7 +60,7 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect private static final int DATA_URL = 0; // Return base64 encoded string private static final int FILE_URI = 1; // Return file uri (content://media/external/images/media/2 for Android) - private static final int NATIVE_URI = 2; // On Android, this is the same as FILE_URI + private static final int NATIVE_URI = 2; // On Android, this is the same as FILE_URI private static final int PHOTOLIBRARY = 0; // Choose image from picture library (same as SAVEDPHOTOALBUM for Android) private static final int CAMERA = 1; // Take picture from camera @@ -95,14 +95,15 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect private MediaScannerConnection conn; // Used to update gallery app with newly-written files private Uri scanMe; // Uri of image to be added to content store + private Uri croppedUri; /** * Executes the request and returns PluginResult. * - * @param action The action to execute. - * @param args JSONArry of arguments for the plugin. + * @param action The action to execute. + * @param args JSONArry of arguments for the plugin. * @param callbackContext The callback id used when calling back into JavaScript. - * @return A PluginResult object with a status and message. + * @return A PluginResult object with a status and message. */ public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException { this.callbackContext = callbackContext; @@ -248,76 +249,85 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect public void getImage(int srcType, int returnType, int encodingType) { Intent intent = new Intent(); String title = GET_PICTURE; + croppedUri = null; if (this.mediaType == PICTURE) { intent.setType("image/*"); - if (this.allowEdit) { - intent.setAction(Intent.ACTION_PICK); - intent.putExtra("crop", "true"); - if (this.targetHeight == this.targetWidth) { - intent.putExtra("aspectX", 1); - intent.putExtra("aspectY", 1); - } - intent.putExtra("outputX", this.targetWidth); - intent.putExtra("outputY", this.targetHeight); - File photo = createCaptureFile(encodingType); - intent.putExtra(android.provider.MediaStore.EXTRA_OUTPUT, - Uri.fromFile(photo)); - } else { - intent.setAction(Intent.ACTION_GET_CONTENT); - intent.addCategory(Intent.CATEGORY_OPENABLE); - } - } else if (this.mediaType == VIDEO) { - intent.setType("video/*"); - title = GET_VIDEO; - intent.setAction(Intent.ACTION_GET_CONTENT); - intent.addCategory(Intent.CATEGORY_OPENABLE); - } else if (this.mediaType == ALLMEDIA) { - // I wanted to make the type 'image/*, video/*' but this does not work on all versions - // of android so I had to go with the wildcard search. - intent.setType("*/*"); - title = GET_All; - intent.setAction(Intent.ACTION_GET_CONTENT); - intent.addCategory(Intent.CATEGORY_OPENABLE); - } + if (this.allowEdit) { + intent.setAction(Intent.ACTION_PICK); + intent.putExtra("crop", "true"); + if (targetWidth > 0) { + intent.putExtra("outputX", targetWidth); + } + if (targetHeight > 0) { + intent.putExtra("outputY", targetHeight); + } + if (targetHeight > 0 && targetWidth > 0 && targetWidth == targetHeight) { + intent.putExtra("aspectX", 1); + intent.putExtra("aspectY", 1); + } + File photo = createCaptureFile(encodingType); + croppedUri = Uri.fromFile(photo); + intent.putExtra(android.provider.MediaStore.EXTRA_OUTPUT, croppedUri); + } else { + intent.setAction(Intent.ACTION_GET_CONTENT); + intent.addCategory(Intent.CATEGORY_OPENABLE); + } + } else if (this.mediaType == VIDEO) { + intent.setType("video/*"); + title = GET_VIDEO; + intent.setAction(Intent.ACTION_GET_CONTENT); + intent.addCategory(Intent.CATEGORY_OPENABLE); + } else if (this.mediaType == ALLMEDIA) { + // I wanted to make the type 'image/*, video/*' but this does not work on all versions + // of android so I had to go with the wildcard search. + intent.setType("*/*"); + title = GET_All; + intent.setAction(Intent.ACTION_GET_CONTENT); + intent.addCategory(Intent.CATEGORY_OPENABLE); + } if (this.cordova != null) { this.cordova.startActivityForResult((CordovaPlugin) this, Intent.createChooser(intent, new String(title)), (srcType + 1) * 16 + returnType + 1); } } - /** - * Brings up the UI to perform crop on passed image URI - * - * @param picUri - */ - private void performCrop(Uri picUri) { - try { - Intent cropIntent = new Intent("com.android.camera.action.CROP"); - // indicate image type and Uri - cropIntent.setDataAndType(picUri, "image/*"); - // set crop properties - cropIntent.putExtra("crop", "true"); - if (this.targetHeight == this.targetWidth) { - cropIntent.putExtra("aspectX", 1); - cropIntent.putExtra("aspectY", 1); - } - // indicate output X and Y - cropIntent.putExtra("outputX", this.targetWidth); - cropIntent.putExtra("outputY", this.targetHeight); - // retrieve data on return - cropIntent.putExtra("return-data", true); - // start the activity - we handle returning in onActivityResult + /** + * Brings up the UI to perform crop on passed image URI + * + * @param picUri + */ + private void performCrop(Uri picUri) { + try { + Intent cropIntent = new Intent("com.android.camera.action.CROP"); + // indicate image type and Uri + cropIntent.setDataAndType(picUri, "image/*"); + // set crop properties + cropIntent.putExtra("crop", "true"); + // indicate output X and Y + if (targetWidth > 0) { + cropIntent.putExtra("outputX", targetWidth); + } + if (targetHeight > 0) { + cropIntent.putExtra("outputY", targetHeight); + } + if (targetHeight > 0 && targetWidth > 0 && targetWidth == targetHeight) { + cropIntent.putExtra("aspectX", 1); + cropIntent.putExtra("aspectY", 1); + } + // retrieve data on return + cropIntent.putExtra("return-data", true); + // start the activity - we handle returning in onActivityResult - if (this.cordova != null) { - this.cordova.startActivityForResult((CordovaPlugin) this, - cropIntent, CROP_CAMERA); - } - } catch (ActivityNotFoundException anfe) { - Log.e(LOG_TAG, "Crop operation not supported on this device"); - // Send Uri back to JavaScript for viewing image - this.callbackContext.success(picUri.toString()); - } - } + if (this.cordova != null) { + this.cordova.startActivityForResult((CordovaPlugin) this, + cropIntent, CROP_CAMERA); + } + } catch (ActivityNotFoundException anfe) { + Log.e(LOG_TAG, "Crop operation not supported on this device"); + // Send Uri back to JavaScript for viewing image + this.callbackContext.success(picUri.toString()); + } + } /** * Applies all needed transformation to the image received from the camera. @@ -410,11 +420,11 @@ public class CameraLauncher extends CordovaPlugin implements MediaScannerConnect exif.writeExifData(); } if (this.allowEdit) { - performCrop(uri); - } else { - // Send Uri back to JavaScript for viewing image - this.callbackContext.success(uri.toString()); - } + performCrop(uri); + } else { + // Send Uri back to JavaScript for viewing image + this.callbackContext.success(uri.toString()); + } } // Send Uri back to JavaScript for viewing image this.callbackContext.success(uri.toString()); @@ -459,6 +469,14 @@ private String ouputModifiedBitmap(Bitmap bitmap, Uri uri) throws IOException { */ private void processResultFromGallery(int destType, Intent intent) { Uri uri = intent.getData(); + if (uri == null) { + if (croppedUri != null) { + uri = croppedUri; + } else { + this.failPicture("null data from photo library"); + return; + } + } int rotate = 0; // If you ask for video or all media type you will automatically get back a file URI @@ -555,45 +573,49 @@ private String ouputModifiedBitmap(Bitmap bitmap, Uri uri) throws IOException { int srcType = (requestCode / 16) - 1; int destType = (requestCode % 16) - 1; // if camera crop - if (requestCode == CROP_CAMERA) { - if (resultCode == Activity.RESULT_OK) { - // // get the returned data - Bundle extras = intent.getExtras(); - // get the cropped bitmap - Bitmap thePic = extras.getParcelable("data"); + if (requestCode == CROP_CAMERA) { + if (resultCode == Activity.RESULT_OK) { + // // get the returned data + Bundle extras = intent.getExtras(); + // get the cropped bitmap + Bitmap thePic = extras.getParcelable("data"); + if (thePic == null) { + this.failPicture("Crop returned no data."); + return; + } - // now save the bitmap to a file - OutputStream fOut = null; - File temp_file = new File(getTempDirectoryPath(), - System.currentTimeMillis() + ".jpg"); - try { - temp_file.createNewFile(); - fOut = new FileOutputStream(temp_file); - thePic.compress(Bitmap.CompressFormat.JPEG, this.mQuality, - fOut); - fOut.flush(); - fOut.close(); - } catch (FileNotFoundException e) { - e.printStackTrace(); - } catch (IOException e) { - e.printStackTrace(); - } + // now save the bitmap to a file + OutputStream fOut = null; + File temp_file = new File(getTempDirectoryPath(), + System.currentTimeMillis() + ".jpg"); + try { + temp_file.createNewFile(); + fOut = new FileOutputStream(temp_file); + thePic.compress(Bitmap.CompressFormat.JPEG, this.mQuality, + fOut); + fOut.flush(); + fOut.close(); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } - // // Send Uri back to JavaScript for viewing image - this.callbackContext - .success(Uri.fromFile(temp_file).toString()); + // // Send Uri back to JavaScript for viewing image + this.callbackContext + .success(Uri.fromFile(temp_file).toString()); - }// If cancelled - else if (resultCode == Activity.RESULT_CANCELED) { - this.failPicture("Camera cancelled."); - } + }// If cancelled + else if (resultCode == Activity.RESULT_CANCELED) { + this.failPicture("Camera cancelled."); + } - // If something else - else { - this.failPicture("Did not complete!"); - } + // If something else + else { + this.failPicture("Did not complete!"); + } - } + } // If CAMERA if (srcType == CAMERA) { // If image available @@ -961,4 +983,4 @@ private String ouputModifiedBitmap(Bitmap bitmap, Uri uri) throws IOException { public void onScanCompleted(String path, Uri uri) { this.conn.disconnect(); } -} \ No newline at end of file +} From e8596fbc8e58eba0f3272cc20828664d9b051472 Mon Sep 17 00:00:00 2001 From: Marcel Kinard Date: Wed, 30 Apr 2014 09:20:09 -0400 Subject: [PATCH 19/20] CB-6491 add CONTRIBUTING.md --- CONTRIBUTING.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..1594d12 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,16 @@ +# Contributing to Apache Cordova + +Anyone can contribute to Cordova. And we need your contributions. + +There are multiple ways to contribute: report bugs, improve the docs, and +contribute code. + +For instructions on this, start with the +[contribution overview](http://cordova.apache.org/#contribute). + +The details are explained there, but the important items are: + - Sign and submit an Apache ICLA (Contributor License Agreement). + - Have a Jira issue open that corresponds to your contribution. + - Run the tests so your patch doesn't break existing functionality. + +We look forward to your contributions! From 0c9de56da59cc06d1e8680afbc6c96437c9b41cd Mon Sep 17 00:00:00 2001 From: RemeR Date: Tue, 29 Apr 2014 12:30:50 -0300 Subject: [PATCH 20/20] CB-6576 - Returns a specific error message when app has no access to library. Signed-off-by: Shazron Abdullah --- src/ios/CDVCamera.m | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/ios/CDVCamera.m b/src/ios/CDVCamera.m index 9981747..8c11e60 100644 --- a/src/ios/CDVCamera.m +++ b/src/ios/CDVCamera.m @@ -24,6 +24,7 @@ #import #import #import +#import #import #import #import @@ -392,7 +393,13 @@ static NSSet* org_apache_cordova_validArrowDirections; } // popoverControllerDidDismissPopover:(id)popoverController is called if popover is cancelled - CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"no image selected"]; // error callback expects string ATM + CDVPluginResult* result; + if ([ALAssetsLibrary authorizationStatus] == ALAuthorizationStatusAuthorized) { + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"no image selected"]; // error callback expects string ATM + } else { + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"has no access to assets"]; // error callback expects string ATM + } + [self.commandDelegate sendPluginResult:result callbackId:cameraPicker.callbackId]; self.hasPendingOperation = NO;