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}