arnaucube
arnaucube/openEventsPlatformAppPublic Self-hosted repository
openEventsPlatformApp/plugins/cordova-plugin-camera/src/wp/Camera.cs
plugins/cordova-plugin-camera/src/wp/Camera.csRaw
  1/*  
  2	Licensed under the Apache License, Version 2.0 (the "License");
  3	you may not use this file except in compliance with the License.
  4	You may obtain a copy of the License at
  5	
  6	http://www.apache.org/licenses/LICENSE-2.0
  7	
  8	Unless required by applicable law or agreed to in writing, software
  9	distributed under the License is distributed on an "AS IS" BASIS,
 10	WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 11	See the License for the specific language governing permissions and
 12	limitations under the License.
 13*/
 14
 15using System;
 16using System.Net;
 17using System.Windows;
 18using System.Windows.Controls;
 19using System.Windows.Documents;
 20using System.Windows.Ink;
 21using System.Windows.Input;
 22using System.Windows.Media;
 23using System.Windows.Media.Animation;
 24using System.Collections.Generic;
 25using Microsoft.Phone.Tasks;
 26using System.Runtime.Serialization;
 27using System.IO;
 28using System.IO.IsolatedStorage;
 29using System.Windows.Media.Imaging;
 30using Microsoft.Phone;
 31using Microsoft.Xna.Framework.Media;
 32using System.Diagnostics;
 33
 34namespace WPCordovaClassLib.Cordova.Commands
 35{
 36    public class Camera : BaseCommand
 37    {
 38
 39        /// <summary>
 40        /// Return base64 encoded string
 41        /// </summary>
 42        private const int DATA_URL = 0;
 43
 44        /// <summary>
 45        /// Return file uri
 46        /// </summary>
 47        private const int FILE_URI = 1;
 48
 49        /// <summary>
 50        /// Return native uri
 51        /// </summary>
 52        private const int NATIVE_URI = 2;
 53
 54        /// <summary>
 55        /// Choose image from picture library
 56        /// </summary>
 57        private const int PHOTOLIBRARY = 0;
 58
 59        /// <summary>
 60        /// Take picture from camera
 61        /// </summary>
 62
 63        private const int CAMERA = 1;
 64
 65        /// <summary>
 66        /// Choose image from picture library
 67        /// </summary>
 68        private const int SAVEDPHOTOALBUM = 2;
 69
 70        /// <summary>
 71        /// Take a picture of type JPEG
 72        /// </summary>
 73        private const int JPEG = 0;
 74
 75        /// <summary>
 76        /// Take a picture of type PNG
 77        /// </summary>
 78        private const int PNG = 1;
 79
 80        /// <summary>
 81        /// Folder to store captured images
 82        /// </summary>
 83        private const string isoFolder = "CapturedImagesCache";
 84
 85        /// <summary>
 86        /// Represents captureImage action options.
 87        /// </summary>
 88        [DataContract]
 89        public class CameraOptions
 90        {
 91            /// <summary>
 92            /// Source to getPicture from.
 93            /// </summary>
 94            [DataMember(IsRequired = false, Name = "sourceType")]
 95            public int PictureSourceType { get; set; }
 96
 97            /// <summary>
 98            /// Format of image that returned from getPicture.
 99            /// </summary>
100            [DataMember(IsRequired = false, Name = "destinationType")]
101            public int DestinationType { get; set; }
102
103            /// <summary>
104            /// Quality of saved image
105            /// </summary>
106            [DataMember(IsRequired = false, Name = "quality")]
107            public int Quality { get; set; }
108
109            /// <summary>
110            /// Controls whether or not the image is also added to the device photo album.
111            /// </summary>
112            [DataMember(IsRequired = false, Name = "saveToPhotoAlbum")]
113            public bool SaveToPhotoAlbum { get; set; }
114
115            /// <summary>
116            /// Ignored
117            /// </summary>
118            [DataMember(IsRequired = false, Name = "correctOrientation")]
119            public bool CorrectOrientation { get; set; }
120
121            /// <summary>
122            /// Ignored
123            /// </summary>
124            [DataMember(IsRequired = false, Name = "allowEdit")]
125            public bool AllowEdit { get; set; }
126
127                        /// <summary>
128            /// Height in pixels to scale image
129            /// </summary>
130            [DataMember(IsRequired = false, Name = "encodingType")]
131            public int EncodingType { get; set; }
132
133                        /// <summary>
134            /// Height in pixels to scale image
135            /// </summary>
136            [DataMember(IsRequired = false, Name = "mediaType")]
137            public int MediaType { get; set; }
138
139
140            /// <summary>
141            /// Height in pixels to scale image
142            /// </summary>
143            [DataMember(IsRequired = false, Name = "targetHeight")]
144            public int TargetHeight { get; set; }
145
146
147            /// <summary>
148            /// Width in pixels to scale image
149            /// </summary>
150            [DataMember(IsRequired = false, Name = "targetWidth")]
151            public int TargetWidth { get; set; }
152
153            /// <summary>
154            /// Creates options object with default parameters
155            /// </summary>
156            public CameraOptions()
157            {
158                this.SetDefaultValues(new StreamingContext());
159            }
160
161            /// <summary>
162            /// Initializes default values for class fields.
163            /// Implemented in separate method because default constructor is not invoked during deserialization.
164            /// </summary>
165            /// <param name="context"></param>
166            [OnDeserializing()]
167            public void SetDefaultValues(StreamingContext context)
168            {
169                PictureSourceType = CAMERA;
170                DestinationType = FILE_URI;
171                Quality = 80;
172                TargetHeight = -1;
173                TargetWidth = -1;
174                SaveToPhotoAlbum = false;
175                CorrectOrientation = true;
176                AllowEdit = false;
177                MediaType = -1;
178                EncodingType = -1;
179            }
180        }
181
182        /// <summary>
183        /// Camera options
184        /// </summary>
185        CameraOptions cameraOptions;
186
187        public void takePicture(string options)
188        {
189            try
190            {
191                string[] args = JSON.JsonHelper.Deserialize<string[]>(options);
192                // ["quality", "destinationType", "sourceType", "targetWidth", "targetHeight", "encodingType",
193                //     "mediaType", "allowEdit", "correctOrientation", "saveToPhotoAlbum" ]
194                cameraOptions = new CameraOptions();
195                cameraOptions.Quality = int.Parse(args[0]);
196                cameraOptions.DestinationType = int.Parse(args[1]);
197                cameraOptions.PictureSourceType = int.Parse(args[2]);
198                cameraOptions.TargetWidth = int.Parse(args[3]);
199                cameraOptions.TargetHeight = int.Parse(args[4]);
200                cameraOptions.EncodingType = int.Parse(args[5]);
201                cameraOptions.MediaType = int.Parse(args[6]);
202                cameraOptions.AllowEdit = bool.Parse(args[7]);
203                cameraOptions.CorrectOrientation = bool.Parse(args[8]);
204                cameraOptions.SaveToPhotoAlbum = bool.Parse(args[9]);
205
206                // a very large number will force the other value to be the bound
207                if (cameraOptions.TargetWidth > -1 && cameraOptions.TargetHeight == -1)
208                {
209                    cameraOptions.TargetHeight = 100000;   
210                }
211                else if (cameraOptions.TargetHeight > -1 && cameraOptions.TargetWidth == -1)
212                {
213                    cameraOptions.TargetWidth = 100000;
214                }
215            }
216            catch (Exception ex)
217            {
218                DispatchCommandResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION, ex.Message));
219                return;
220            }
221
222            // Api supports FILE_URI, DATA_URL, NATIVE_URI destination types.
223            // Treat all other destination types as an error.
224            switch (cameraOptions.DestinationType)
225            {
226                case Camera.FILE_URI:
227                case Camera.DATA_URL:
228                case Camera.NATIVE_URI:
229                    break;
230                default:
231                    DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "Incorrect option: destinationType"));
232                    return;
233            }
234
235            ChooserBase<PhotoResult> chooserTask = null;
236            if (cameraOptions.PictureSourceType == CAMERA)
237            {
238                chooserTask = new CameraCaptureTask();
239            }
240            else if ((cameraOptions.PictureSourceType == PHOTOLIBRARY) || (cameraOptions.PictureSourceType == SAVEDPHOTOALBUM))
241            {
242                chooserTask = new PhotoChooserTask();
243            }
244            // if chooserTask is still null, then PictureSourceType was invalid
245            if (chooserTask != null)
246            {
247                chooserTask.Completed += onTaskCompleted;
248                chooserTask.Show();
249            }
250            else
251            {
252                Debug.WriteLine("Unrecognized PictureSourceType :: " + cameraOptions.PictureSourceType.ToString());
253                DispatchCommandResult(new PluginResult(PluginResult.Status.NO_RESULT));
254            }
255        }
256
257        public void onTaskCompleted(object sender, PhotoResult e)
258        {
259            var task = sender as ChooserBase<PhotoResult>;
260            if (task != null)
261            {
262                task.Completed -= onTaskCompleted;
263            }
264
265            if (e.Error != null)
266            {
267                DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR));
268                return;
269            }
270
271            switch (e.TaskResult)
272            {
273                case TaskResult.OK:
274                    try
275                    {
276                        string imagePathOrContent = string.Empty;
277
278                        // Save image back to media library
279                        // only save to photoalbum if it didn't come from there ...
280                        if (cameraOptions.PictureSourceType == CAMERA && cameraOptions.SaveToPhotoAlbum)
281                        {
282                            MediaLibrary library = new MediaLibrary();
283                            Picture pict = library.SavePicture(e.OriginalFileName, e.ChosenPhoto); // to save to photo-roll ...
284                        }
285
286                        int newAngle = 0;
287                        // There's bug in Windows Phone 8.1 causing Seek on a DssPhotoStream not working properly.
288                        // https://connect.microsoft.com/VisualStudio/feedback/details/783252
289                        // But a mis-oriented file is better than nothing, so try and catch.
290                        try {
291                            int orient = ImageExifHelper.getImageOrientationFromStream(e.ChosenPhoto);
292                            switch (orient) {
293                                case ImageExifOrientation.LandscapeLeft:
294                                    newAngle = 90;
295                                    break;
296                                case ImageExifOrientation.PortraitUpsideDown:
297                                    newAngle = 180;
298                                    break;
299                                case ImageExifOrientation.LandscapeRight:
300                                    newAngle = 270;
301                                    break;
302                                case ImageExifOrientation.Portrait:
303                                default: break; // 0 default already set
304                            }
305                        } catch {
306                            Debug.WriteLine("Error fetching orientation from Exif");
307                        }
308
309                        if (newAngle != 0)
310                        {
311                            using (Stream rotImageStream = ImageExifHelper.RotateStream(e.ChosenPhoto, newAngle))
312                            {
313                                // we should reset stream position after saving stream to media library
314                                rotImageStream.Seek(0, SeekOrigin.Begin);
315                                if (cameraOptions.DestinationType == DATA_URL)
316                                {
317                                    imagePathOrContent = GetImageContent(rotImageStream);
318                                }
319                                else   // FILE_URL or NATIVE_URI (both use the same resultant uri format)
320                                {
321                                    imagePathOrContent = SaveImageToLocalStorage(rotImageStream, Path.GetFileName(e.OriginalFileName));
322                                }
323                            }
324                        }
325                        else   // no need to reorient
326                        {
327                            if (cameraOptions.DestinationType == DATA_URL)
328                            {
329                                imagePathOrContent = GetImageContent(e.ChosenPhoto);
330                            }
331                            else  // FILE_URL or NATIVE_URI (both use the same resultant uri format)
332                            {
333                                imagePathOrContent = SaveImageToLocalStorage(e.ChosenPhoto, Path.GetFileName(e.OriginalFileName));
334                            }
335                        }
336
337                        DispatchCommandResult(new PluginResult(PluginResult.Status.OK, imagePathOrContent));
338                    }
339                    catch (Exception)
340                    {
341                        DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "Error retrieving image."));
342                    }
343                    break;
344                case TaskResult.Cancel:
345                    DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "Selection cancelled."));
346                    break;
347                default:
348                    DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "Selection did not complete!"));
349                    break;
350            }
351        }
352     
353        /// <summary>
354        /// Returns image content in a form of base64 string
355        /// </summary>
356        /// <param name="stream">Image stream</param>
357        /// <returns>Base64 representation of the image</returns>
358        private string GetImageContent(Stream stream)
359        {
360            byte[] imageContent = null;
361
362            try
363            {
364                // Resize photo and convert to JPEG
365                imageContent = ResizePhoto(stream);
366            }
367            finally
368            {
369                stream.Dispose();
370            }
371
372            return Convert.ToBase64String(imageContent);
373        }
374
375        /// <summary>
376        /// Resize image
377        /// </summary>
378        /// <param name="stream">Image stream</param>
379        /// <returns>resized image</returns>
380        private byte[] ResizePhoto(Stream stream)
381        {
382            //output
383            byte[] resizedFile;
384
385            BitmapImage objBitmap = new BitmapImage();
386            objBitmap.SetSource(stream);
387            objBitmap.CreateOptions = BitmapCreateOptions.None;
388
389            WriteableBitmap objWB = new WriteableBitmap(objBitmap);
390            objBitmap.UriSource = null;
391
392            // Calculate resultant image size
393            int width, height;
394            if (cameraOptions.TargetWidth >= 0 && cameraOptions.TargetHeight >= 0)
395            {
396                // Keep proportionally
397                double ratio = Math.Min(
398                    (double)cameraOptions.TargetWidth / objWB.PixelWidth,
399                    (double)cameraOptions.TargetHeight / objWB.PixelHeight);
400                width = Convert.ToInt32(ratio * objWB.PixelWidth);
401                height = Convert.ToInt32(ratio * objWB.PixelHeight);
402            }
403            else
404            {
405                width = objWB.PixelWidth;
406                height = objWB.PixelHeight;
407            }
408
409            //Hold the result stream
410            using (MemoryStream objBitmapStreamResized = new MemoryStream())
411            {
412
413                try
414                {
415                    // resize the photo with user defined TargetWidth & TargetHeight
416                    Extensions.SaveJpeg(objWB, objBitmapStreamResized, width, height, 0, cameraOptions.Quality);
417                }
418                finally
419                {
420                    //Dispose bitmaps immediately, they are memory expensive
421                    DisposeImage(objBitmap);
422                    DisposeImage(objWB);
423                    GC.Collect();
424                }
425
426                //Convert the resized stream to a byte array. 
427                int streamLength = (int)objBitmapStreamResized.Length;
428                resizedFile = new Byte[streamLength]; //-1 
429                objBitmapStreamResized.Position = 0;
430
431                //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... 
432                objBitmapStreamResized.Read(resizedFile, 0, streamLength);
433            }
434
435            return resizedFile;
436        }
437
438        /// <summary>
439        /// Util: Dispose a bitmap resource
440        /// </summary>
441        /// <param name="image">BitmapSource subclass to dispose</param>
442        private void DisposeImage(BitmapSource image)
443        {
444            if (image != null)
445            {
446                try
447                {
448                    using (var ms = new MemoryStream(new byte[] { 0x0 }))
449                    {
450                        image.SetSource(ms);
451                    }
452                }
453                catch (Exception)
454                {
455                }
456            }
457        }
458
459        /// <summary>
460        /// Saves captured image in isolated storage
461        /// </summary>
462        /// <param name="imageFileName">image file name</param>
463        /// <returns>Image path</returns>
464        private string SaveImageToLocalStorage(Stream stream, string imageFileName)
465        {
466
467            if (stream == null)
468            {
469                throw new ArgumentNullException("imageBytes");
470            }
471            try
472            {
473                var isoFile = IsolatedStorageFile.GetUserStoreForApplication();
474
475                if (!isoFile.DirectoryExists(isoFolder))
476                {
477                    isoFile.CreateDirectory(isoFolder);
478                }
479
480                string filePath = System.IO.Path.Combine("///" + isoFolder + "/", imageFileName);
481
482                using (IsolatedStorageFileStream outputStream = isoFile.CreateFile(filePath))
483                {
484                    BitmapImage objBitmap = new BitmapImage();
485                    objBitmap.SetSource(stream);
486                    objBitmap.CreateOptions = BitmapCreateOptions.None;
487
488                    WriteableBitmap objWB = new WriteableBitmap(objBitmap);
489                    objBitmap.UriSource = null;
490
491                    try
492                    {
493
494                        //use photo's actual width & height if user doesn't provide width & height
495                        if (cameraOptions.TargetWidth < 0 && cameraOptions.TargetHeight < 0)
496                        {
497                            objWB.SaveJpeg(outputStream, objWB.PixelWidth, objWB.PixelHeight, 0, cameraOptions.Quality);
498                        }
499                        else
500                        {
501                            //Resize
502                            //Keep proportionally
503                            double ratio = Math.Min((double)cameraOptions.TargetWidth / objWB.PixelWidth, (double)cameraOptions.TargetHeight / objWB.PixelHeight);
504                            int width = Convert.ToInt32(ratio * objWB.PixelWidth);
505                            int height = Convert.ToInt32(ratio * objWB.PixelHeight);
506
507                            // resize the photo with user defined TargetWidth & TargetHeight
508                            objWB.SaveJpeg(outputStream, width, height, 0, cameraOptions.Quality);
509                        }
510                    }
511                    finally
512                    {
513                        //Dispose bitmaps immediately, they are memory expensive
514                        DisposeImage(objBitmap);
515                        DisposeImage(objWB);
516                        GC.Collect();
517                    }
518                }
519
520                return new Uri(filePath, UriKind.Relative).ToString();
521            }
522            catch (Exception)
523            {
524                //TODO: log or do something else
525                throw;
526            }
527            finally
528            {
529                stream.Dispose();
530            }
531        }
532
533    }
534}