openEventsPlatformApp/plugins/cordova-plugin-camera/src/windows/CameraProxy.js
plugins/cordova-plugin-camera/src/windows/CameraProxy.jsRaw
1/*
2 *
3 * Licensed to the Apache Software Foundation (ASF) under one
4 * or more contributor license agreements. See the NOTICE file
5 * distributed with this work for additional information
6 * regarding copyright ownership. The ASF licenses this file
7 * to you under the Apache License, Version 2.0 (the
8 * "License"); you may not use this file except in compliance
9 * with the License. You may obtain a copy of the License at
10 *
11 * http://www.apache.org/licenses/LICENSE-2.0
12 *
13 * Unless required by applicable law or agreed to in writing,
14 * software distributed under the License is distributed on an
15 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16 * KIND, either express or implied. See the License for the
17 * specific language governing permissions and limitations
18 * under the License.
19 *
20*/
21
22/*jshint unused:true, undef:true, browser:true */
23/*global Windows:true, URL:true, module:true, require:true, WinJS:true */
24
25
26var Camera = require('./Camera');
27
28
29var getAppData = function () {
30 return Windows.Storage.ApplicationData.current;
31};
32var encodeToBase64String = function (buffer) {
33 return Windows.Security.Cryptography.CryptographicBuffer.encodeToBase64String(buffer);
34};
35var OptUnique = Windows.Storage.CreationCollisionOption.generateUniqueName;
36var CapMSType = Windows.Media.Capture.MediaStreamType;
37var webUIApp = Windows.UI.WebUI.WebUIApplication;
38var fileIO = Windows.Storage.FileIO;
39var pickerLocId = Windows.Storage.Pickers.PickerLocationId;
40
41module.exports = {
42
43 // args will contain :
44 // ... it is an array, so be careful
45 // 0 quality:50,
46 // 1 destinationType:Camera.DestinationType.FILE_URI,
47 // 2 sourceType:Camera.PictureSourceType.CAMERA,
48 // 3 targetWidth:-1,
49 // 4 targetHeight:-1,
50 // 5 encodingType:Camera.EncodingType.JPEG,
51 // 6 mediaType:Camera.MediaType.PICTURE,
52 // 7 allowEdit:false,
53 // 8 correctOrientation:false,
54 // 9 saveToPhotoAlbum:false,
55 // 10 popoverOptions:null
56 // 11 cameraDirection:0
57
58 takePicture: function (successCallback, errorCallback, args) {
59 var sourceType = args[2];
60
61 if (sourceType != Camera.PictureSourceType.CAMERA) {
62 takePictureFromFile(successCallback, errorCallback, args);
63 } else {
64 takePictureFromCamera(successCallback, errorCallback, args);
65 }
66 }
67};
68
69// https://msdn.microsoft.com/en-us/library/windows/apps/ff462087(v=vs.105).aspx
70var windowsVideoContainers = [".avi", ".flv", ".asx", ".asf", ".mov", ".mp4", ".mpg", ".rm", ".srt", ".swf", ".wmv", ".vob"];
71var windowsPhoneVideoContainers = [".avi", ".3gp", ".3g2", ".wmv", ".3gp", ".3g2", ".mp4", ".m4v"];
72
73// Default aspect ratio 1.78 (16:9 hd video standard)
74var DEFAULT_ASPECT_RATIO = '1.8';
75
76// Highest possible z-index supported across browsers. Anything used above is converted to this value.
77var HIGHEST_POSSIBLE_Z_INDEX = 2147483647;
78
79// Resize method
80function resizeImage(successCallback, errorCallback, file, targetWidth, targetHeight, encodingType) {
81 var tempPhotoFileName = "";
82 var targetContentType = "";
83
84 if (encodingType == Camera.EncodingType.PNG) {
85 tempPhotoFileName = "camera_cordova_temp_return.png";
86 targetContentType = "image/png";
87 } else {
88 tempPhotoFileName = "camera_cordova_temp_return.jpg";
89 targetContentType = "image/jpeg";
90 }
91
92 var storageFolder = getAppData().localFolder;
93 file.copyAsync(storageFolder, file.name, Windows.Storage.NameCollisionOption.replaceExisting)
94 .then(function (storageFile) {
95 return fileIO.readBufferAsync(storageFile);
96 })
97 .then(function(buffer) {
98 var strBase64 = encodeToBase64String(buffer);
99 var imageData = "data:" + file.contentType + ";base64," + strBase64;
100 var image = new Image();
101 image.src = imageData;
102 image.onload = function() {
103 var ratio = Math.min(targetWidth / this.width, targetHeight / this.height);
104 var imageWidth = ratio * this.width;
105 var imageHeight = ratio * this.height;
106
107 var canvas = document.createElement('canvas');
108 var storageFileName;
109
110 canvas.width = imageWidth;
111 canvas.height = imageHeight;
112
113 canvas.getContext("2d").drawImage(this, 0, 0, imageWidth, imageHeight);
114
115 var fileContent = canvas.toDataURL(targetContentType).split(',')[1];
116
117 var storageFolder = getAppData().localFolder;
118
119 storageFolder.createFileAsync(tempPhotoFileName, OptUnique)
120 .then(function (storagefile) {
121 var content = Windows.Security.Cryptography.CryptographicBuffer.decodeFromBase64String(fileContent);
122 storageFileName = storagefile.name;
123 return fileIO.writeBufferAsync(storagefile, content);
124 })
125 .done(function () {
126 successCallback("ms-appdata:///local/" + storageFileName);
127 }, errorCallback);
128 };
129 })
130 .done(null, function(err) {
131 errorCallback(err);
132 }
133 );
134}
135
136// Because of asynchronous method, so let the successCallback be called in it.
137function resizeImageBase64(successCallback, errorCallback, file, targetWidth, targetHeight) {
138 fileIO.readBufferAsync(file).done( function(buffer) {
139 var strBase64 = encodeToBase64String(buffer);
140 var imageData = "data:" + file.contentType + ";base64," + strBase64;
141
142 var image = new Image();
143 image.src = imageData;
144
145 image.onload = function() {
146 var ratio = Math.min(targetWidth / this.width, targetHeight / this.height);
147 var imageWidth = ratio * this.width;
148 var imageHeight = ratio * this.height;
149 var canvas = document.createElement('canvas');
150
151 canvas.width = imageWidth;
152 canvas.height = imageHeight;
153
154 var ctx = canvas.getContext("2d");
155 ctx.drawImage(this, 0, 0, imageWidth, imageHeight);
156
157 // The resized file ready for upload
158 var finalFile = canvas.toDataURL(file.contentType);
159
160 // Remove the prefix such as "data:" + contentType + ";base64," , in order to meet the Cordova API.
161 var arr = finalFile.split(",");
162 var newStr = finalFile.substr(arr[0].length + 1);
163 successCallback(newStr);
164 };
165 }, function(err) { errorCallback(err); });
166}
167
168function takePictureFromFile(successCallback, errorCallback, args) {
169 // Detect Windows Phone
170 if (navigator.appVersion.indexOf('Windows Phone 8.1') >= 0) {
171 takePictureFromFileWP(successCallback, errorCallback, args);
172 } else {
173 takePictureFromFileWindows(successCallback, errorCallback, args);
174 }
175}
176
177function takePictureFromFileWP(successCallback, errorCallback, args) {
178 var mediaType = args[6],
179 destinationType = args[1],
180 targetWidth = args[3],
181 targetHeight = args[4],
182 encodingType = args[5];
183
184 /*
185 Need to add and remove an event listener to catch activation state
186 Using FileOpenPicker will suspend the app and it's required to catch the PickSingleFileAndContinue
187 https://msdn.microsoft.com/en-us/library/windows/apps/xaml/dn631755.aspx
188 */
189 var filePickerActivationHandler = function(eventArgs) {
190 if (eventArgs.kind === Windows.ApplicationModel.Activation.ActivationKind.pickFileContinuation) {
191 var file = eventArgs.files[0];
192 if (!file) {
193 errorCallback("User didn't choose a file.");
194 webUIApp.removeEventListener("activated", filePickerActivationHandler);
195 return;
196 }
197 if (destinationType == Camera.DestinationType.FILE_URI || destinationType == Camera.DestinationType.NATIVE_URI) {
198 if (targetHeight > 0 && targetWidth > 0) {
199 resizeImage(successCallback, errorCallback, file, targetWidth, targetHeight, encodingType);
200 }
201 else {
202 var storageFolder = getAppData().localFolder;
203 file.copyAsync(storageFolder, file.name, Windows.Storage.NameCollisionOption.replaceExisting).done(function (storageFile) {
204 if(destinationType == Camera.DestinationType.NATIVE_URI) {
205 successCallback("ms-appdata:///local/" + storageFile.name);
206 }
207 else {
208 successCallback(URL.createObjectURL(storageFile));
209 }
210 }, function () {
211 errorCallback("Can't access localStorage folder.");
212 });
213 }
214 }
215 else {
216 if (targetHeight > 0 && targetWidth > 0) {
217 resizeImageBase64(successCallback, errorCallback, file, targetWidth, targetHeight);
218 } else {
219 fileIO.readBufferAsync(file).done(function (buffer) {
220 var strBase64 =encodeToBase64String(buffer);
221 successCallback(strBase64);
222 }, errorCallback);
223 }
224 }
225 webUIApp.removeEventListener("activated", filePickerActivationHandler);
226 }
227 };
228
229 var fileOpenPicker = new Windows.Storage.Pickers.FileOpenPicker();
230 if (mediaType == Camera.MediaType.PICTURE) {
231 fileOpenPicker.fileTypeFilter.replaceAll([".png", ".jpg", ".jpeg"]);
232 fileOpenPicker.suggestedStartLocation = pickerLocId.picturesLibrary;
233 }
234 else if (mediaType == Camera.MediaType.VIDEO) {
235 fileOpenPicker.fileTypeFilter.replaceAll(windowsPhoneVideoContainers);
236 fileOpenPicker.suggestedStartLocation = pickerLocId.videosLibrary;
237 }
238 else {
239 fileOpenPicker.fileTypeFilter.replaceAll(["*"]);
240 fileOpenPicker.suggestedStartLocation = pickerLocId.documentsLibrary;
241 }
242
243 webUIApp.addEventListener("activated", filePickerActivationHandler);
244 fileOpenPicker.pickSingleFileAndContinue();
245}
246
247function takePictureFromFileWindows(successCallback, errorCallback, args) {
248 var mediaType = args[6],
249 destinationType = args[1],
250 targetWidth = args[3],
251 targetHeight = args[4],
252 encodingType = args[5];
253
254 var fileOpenPicker = new Windows.Storage.Pickers.FileOpenPicker();
255 if (mediaType == Camera.MediaType.PICTURE) {
256 fileOpenPicker.fileTypeFilter.replaceAll([".png", ".jpg", ".jpeg"]);
257 fileOpenPicker.suggestedStartLocation = pickerLocId.picturesLibrary;
258 }
259 else if (mediaType == Camera.MediaType.VIDEO) {
260 fileOpenPicker.fileTypeFilter.replaceAll(windowsVideoContainers);
261 fileOpenPicker.suggestedStartLocation = pickerLocId.videosLibrary;
262 }
263 else {
264 fileOpenPicker.fileTypeFilter.replaceAll(["*"]);
265 fileOpenPicker.suggestedStartLocation = pickerLocId.documentsLibrary;
266 }
267
268 fileOpenPicker.pickSingleFileAsync().done(function (file) {
269 if (!file) {
270 errorCallback("User didn't choose a file.");
271 return;
272 }
273 if (destinationType == Camera.DestinationType.FILE_URI || destinationType == Camera.DestinationType.NATIVE_URI) {
274 if (targetHeight > 0 && targetWidth > 0) {
275 resizeImage(successCallback, errorCallback, file, targetWidth, targetHeight, encodingType);
276 }
277 else {
278 var storageFolder = getAppData().localFolder;
279 file.copyAsync(storageFolder, file.name, Windows.Storage.NameCollisionOption.replaceExisting).done(function (storageFile) {
280 if(destinationType == Camera.DestinationType.NATIVE_URI) {
281 successCallback("ms-appdata:///local/" + storageFile.name);
282 }
283 else {
284 successCallback(URL.createObjectURL(storageFile));
285 }
286 }, function () {
287 errorCallback("Can't access localStorage folder.");
288 });
289 }
290 }
291 else {
292 if (targetHeight > 0 && targetWidth > 0) {
293 resizeImageBase64(successCallback, errorCallback, file, targetWidth, targetHeight);
294 } else {
295 fileIO.readBufferAsync(file).done(function (buffer) {
296 var strBase64 =encodeToBase64String(buffer);
297 successCallback(strBase64);
298 }, errorCallback);
299 }
300 }
301 }, function () {
302 errorCallback("User didn't choose a file.");
303 });
304}
305
306function takePictureFromCamera(successCallback, errorCallback, args) {
307 // Check if necessary API available
308 if (!Windows.Media.Capture.CameraCaptureUI) {
309 takePictureFromCameraWP(successCallback, errorCallback, args);
310 } else {
311 takePictureFromCameraWindows(successCallback, errorCallback, args);
312 }
313}
314
315function takePictureFromCameraWP(successCallback, errorCallback, args) {
316 // We are running on WP8.1 which lacks CameraCaptureUI class
317 // so we need to use MediaCapture class instead and implement custom UI for camera
318 var destinationType = args[1],
319 targetWidth = args[3],
320 targetHeight = args[4],
321 encodingType = args[5],
322 saveToPhotoAlbum = args[9],
323 cameraDirection = args[11],
324 capturePreview = null,
325 cameraCaptureButton = null,
326 cameraCancelButton = null,
327 capture = null,
328 captureSettings = null,
329 CaptureNS = Windows.Media.Capture,
330 sensor = null;
331
332 function createCameraUI() {
333 // create style for take and cancel buttons
334 var buttonStyle = "width:45%;padding: 10px 16px;font-size: 18px;line-height: 1.3333333;color: #333;background-color: #fff;border-color: #ccc; border: 1px solid transparent;border-radius: 6px; display: block; margin: 20px; z-index: 1000;border-color: #adadad;";
335
336 // Create fullscreen preview
337 // z-order style element for capturePreview and cameraCancelButton elts
338 // is necessary to avoid overriding by another page elements, -1 sometimes is not enough
339 capturePreview = document.createElement("video");
340 capturePreview.style.cssText = "position: fixed; left: 0; top: 0; width: 100%; height: 100%; z-index: " + (HIGHEST_POSSIBLE_Z_INDEX - 1) + ";";
341
342 // Create capture button
343 cameraCaptureButton = document.createElement("button");
344 cameraCaptureButton.innerText = "Take";
345 cameraCaptureButton.style.cssText = buttonStyle + "position: fixed; left: 0; bottom: 0; margin: 20px; z-index: " + HIGHEST_POSSIBLE_Z_INDEX + ";";
346
347 // Create cancel button
348 cameraCancelButton = document.createElement("button");
349 cameraCancelButton.innerText = "Cancel";
350 cameraCancelButton.style.cssText = buttonStyle + "position: fixed; right: 0; bottom: 0; margin: 20px; z-index: " + HIGHEST_POSSIBLE_Z_INDEX + ";";
351
352 capture = new CaptureNS.MediaCapture();
353
354 captureSettings = new CaptureNS.MediaCaptureInitializationSettings();
355 captureSettings.streamingCaptureMode = CaptureNS.StreamingCaptureMode.video;
356 }
357
358 function continueVideoOnFocus() {
359 // if preview is defined it would be stuck, play it
360 if (capturePreview) {
361 capturePreview.play();
362 }
363 }
364
365 function startCameraPreview() {
366 // Search for available camera devices
367 // This is necessary to detect which camera (front or back) we should use
368 var DeviceEnum = Windows.Devices.Enumeration;
369 var expectedPanel = cameraDirection === 1 ? DeviceEnum.Panel.front : DeviceEnum.Panel.back;
370
371 // Add focus event handler to capture the event when user suspends the app and comes back while the preview is on
372 window.addEventListener("focus", continueVideoOnFocus);
373
374 DeviceEnum.DeviceInformation.findAllAsync(DeviceEnum.DeviceClass.videoCapture).then(function (devices) {
375 if (devices.length <= 0) {
376 destroyCameraPreview();
377 errorCallback('Camera not found');
378 return;
379 }
380
381 devices.forEach(function(currDev) {
382 if (currDev.enclosureLocation.panel && currDev.enclosureLocation.panel == expectedPanel) {
383 captureSettings.videoDeviceId = currDev.id;
384 }
385 });
386
387 captureSettings.photoCaptureSource = Windows.Media.Capture.PhotoCaptureSource.photo;
388
389 return capture.initializeAsync(captureSettings);
390 }).then(function () {
391
392 // create focus control if available
393 var VideoDeviceController = capture.videoDeviceController;
394 var FocusControl = VideoDeviceController.focusControl;
395
396 if (FocusControl.supported === true) {
397 capturePreview.addEventListener('click', function () {
398 // Make sure function isn't called again before previous focus is completed
399 if (this.getAttribute('clicked') === '1') {
400 return false;
401 } else {
402 this.setAttribute('clicked', '1');
403 }
404 var preset = Windows.Media.Devices.FocusPreset.autoNormal;
405 var parent = this;
406 FocusControl.setPresetAsync(preset).done(function () {
407 // set the clicked attribute back to '0' to allow focus again
408 parent.setAttribute('clicked', '0');
409 });
410 });
411 }
412
413 // msdn.microsoft.com/en-us/library/windows/apps/hh452807.aspx
414 capturePreview.msZoom = true;
415 capturePreview.src = URL.createObjectURL(capture);
416 capturePreview.play();
417
418 // Bind events to controls
419 sensor = Windows.Devices.Sensors.SimpleOrientationSensor.getDefault();
420 if (sensor !== null) {
421 sensor.addEventListener("orientationchanged", onOrientationChange);
422 }
423
424 // add click events to capture and cancel buttons
425 cameraCaptureButton.addEventListener('click', onCameraCaptureButtonClick);
426 cameraCancelButton.addEventListener('click', onCameraCancelButtonClick);
427
428 // Change default orientation
429 if (sensor) {
430 setPreviewRotation(sensor.getCurrentOrientation());
431 } else {
432 setPreviewRotation(Windows.Graphics.Display.DisplayInformation.getForCurrentView().currentOrientation);
433 }
434
435 // Get available aspect ratios
436 var aspectRatios = getAspectRatios(capture);
437
438 // Couldn't find a good ratio
439 if (aspectRatios.length === 0) {
440 destroyCameraPreview();
441 errorCallback('There\'s not a good aspect ratio available');
442 return;
443 }
444
445 // add elements to body
446 document.body.appendChild(capturePreview);
447 document.body.appendChild(cameraCaptureButton);
448 document.body.appendChild(cameraCancelButton);
449
450 if (aspectRatios.indexOf(DEFAULT_ASPECT_RATIO) > -1) {
451 return setAspectRatio(capture, DEFAULT_ASPECT_RATIO);
452 } else {
453 // Doesn't support 16:9 - pick next best
454 return setAspectRatio(capture, aspectRatios[0]);
455 }
456 }).done(null, function (err) {
457 destroyCameraPreview();
458 errorCallback('Camera intitialization error ' + err);
459 });
460 }
461
462 function destroyCameraPreview() {
463 // If sensor is available, remove event listener
464 if (sensor !== null) {
465 sensor.removeEventListener('orientationchanged', onOrientationChange);
466 }
467
468 // Pause and dispose preview element
469 capturePreview.pause();
470 capturePreview.src = null;
471
472 // Remove event listeners from buttons
473 cameraCaptureButton.removeEventListener('click', onCameraCaptureButtonClick);
474 cameraCancelButton.removeEventListener('click', onCameraCancelButtonClick);
475
476 // Remove the focus event handler
477 window.removeEventListener("focus", continueVideoOnFocus);
478
479 // Remove elements
480 [capturePreview, cameraCaptureButton, cameraCancelButton].forEach(function (elem) {
481 if (elem /* && elem in document.body.childNodes */) {
482 document.body.removeChild(elem);
483 }
484 });
485
486 // Stop and dispose media capture manager
487 if (capture) {
488 capture.stopRecordAsync();
489 capture = null;
490 }
491 }
492
493 function captureAction() {
494
495 var encodingProperties,
496 fileName,
497 tempFolder = getAppData().temporaryFolder;
498
499 if (encodingType == Camera.EncodingType.PNG) {
500 fileName = 'photo.png';
501 encodingProperties = Windows.Media.MediaProperties.ImageEncodingProperties.createPng();
502 } else {
503 fileName = 'photo.jpg';
504 encodingProperties = Windows.Media.MediaProperties.ImageEncodingProperties.createJpeg();
505 }
506
507 tempFolder.createFileAsync(fileName, OptUnique)
508 .then(function(tempCapturedFile) {
509 return new WinJS.Promise(function (complete) {
510 var photoStream = new Windows.Storage.Streams.InMemoryRandomAccessStream();
511 var finalStream = new Windows.Storage.Streams.InMemoryRandomAccessStream();
512 capture.capturePhotoToStreamAsync(encodingProperties, photoStream)
513 .then(function() {
514 return Windows.Graphics.Imaging.BitmapDecoder.createAsync(photoStream);
515 })
516 .then(function(dec) {
517 finalStream.size = 0; // BitmapEncoder requires the output stream to be empty
518 return Windows.Graphics.Imaging.BitmapEncoder.createForTranscodingAsync(finalStream, dec);
519 })
520 .then(function(enc) {
521 // We need to rotate the photo wrt sensor orientation
522 enc.bitmapTransform.rotation = orientationToRotation(sensor.getCurrentOrientation());
523 return enc.flushAsync();
524 })
525 .then(function() {
526 return tempCapturedFile.openAsync(Windows.Storage.FileAccessMode.readWrite);
527 })
528 .then(function(fileStream) {
529 return Windows.Storage.Streams.RandomAccessStream.copyAndCloseAsync(finalStream, fileStream);
530 })
531 .done(function() {
532 photoStream.close();
533 finalStream.close();
534 complete(tempCapturedFile);
535 }, function() {
536 photoStream.close();
537 finalStream.close();
538 throw new Error("An error has occured while capturing the photo.");
539 });
540 });
541 })
542 .done(function(capturedFile) {
543 destroyCameraPreview();
544 savePhoto(capturedFile, {
545 destinationType: destinationType,
546 targetHeight: targetHeight,
547 targetWidth: targetWidth,
548 encodingType: encodingType,
549 saveToPhotoAlbum: saveToPhotoAlbum
550 }, successCallback, errorCallback);
551 }, function(err) {
552 destroyCameraPreview();
553 errorCallback(err);
554 });
555 }
556
557 function getAspectRatios(capture) {
558 var videoDeviceController = capture.videoDeviceController;
559 var photoAspectRatios = videoDeviceController.getAvailableMediaStreamProperties(CapMSType.photo).map(function (element) {
560 return (element.width / element.height).toFixed(1);
561 }).filter(function (element, index, array) { return (index === array.indexOf(element)); });
562
563 var videoAspectRatios = videoDeviceController.getAvailableMediaStreamProperties(CapMSType.videoRecord).map(function (element) {
564 return (element.width / element.height).toFixed(1);
565 }).filter(function (element, index, array) { return (index === array.indexOf(element)); });
566
567 var videoPreviewAspectRatios = videoDeviceController.getAvailableMediaStreamProperties(CapMSType.videoPreview).map(function (element) {
568 return (element.width / element.height).toFixed(1);
569 }).filter(function (element, index, array) { return (index === array.indexOf(element)); });
570
571 var allAspectRatios = [].concat(photoAspectRatios, videoAspectRatios, videoPreviewAspectRatios);
572
573 var aspectObj = allAspectRatios.reduce(function (map, item) {
574 if (!map[item]) {
575 map[item] = 0;
576 }
577 map[item]++;
578 return map;
579 }, {});
580
581 return Object.keys(aspectObj).filter(function (k) {
582 return aspectObj[k] === 3;
583 });
584 }
585
586 function setAspectRatio(capture, aspect) {
587 // Max photo resolution with desired aspect ratio
588 var videoDeviceController = capture.videoDeviceController;
589 var photoResolution = videoDeviceController.getAvailableMediaStreamProperties(CapMSType.photo)
590 .filter(function (elem) {
591 return ((elem.width / elem.height).toFixed(1) === aspect);
592 })
593 .reduce(function (prop1, prop2) {
594 return (prop1.width * prop1.height) > (prop2.width * prop2.height) ? prop1 : prop2;
595 });
596
597 // Max video resolution with desired aspect ratio
598 var videoRecordResolution = videoDeviceController.getAvailableMediaStreamProperties(CapMSType.videoRecord)
599 .filter(function (elem) {
600 return ((elem.width / elem.height).toFixed(1) === aspect);
601 })
602 .reduce(function (prop1, prop2) {
603 return (prop1.width * prop1.height) > (prop2.width * prop2.height) ? prop1 : prop2;
604 });
605
606 // Max video preview resolution with desired aspect ratio
607 var videoPreviewResolution = videoDeviceController.getAvailableMediaStreamProperties(CapMSType.videoPreview)
608 .filter(function (elem) {
609 return ((elem.width / elem.height).toFixed(1) === aspect);
610 })
611 .reduce(function (prop1, prop2) {
612 return (prop1.width * prop1.height) > (prop2.width * prop2.height) ? prop1 : prop2;
613 });
614
615 return videoDeviceController.setMediaStreamPropertiesAsync(CapMSType.photo, photoResolution)
616 .then(function () {
617 return videoDeviceController.setMediaStreamPropertiesAsync(CapMSType.videoPreview, videoPreviewResolution);
618 })
619 .then(function () {
620 return videoDeviceController.setMediaStreamPropertiesAsync(CapMSType.videoRecord, videoRecordResolution);
621 });
622 }
623
624 /**
625 * When Capture button is clicked, try to capture a picture and return
626 */
627 function onCameraCaptureButtonClick() {
628 // Make sure user can't click more than once
629 if (this.getAttribute('clicked') === '1') {
630 return false;
631 } else {
632 this.setAttribute('clicked', '1');
633 }
634 captureAction();
635 }
636
637 /**
638 * When Cancel button is clicked, destroy camera preview and return with error callback
639 */
640 function onCameraCancelButtonClick() {
641 // Make sure user can't click more than once
642 if (this.getAttribute('clicked') === '1') {
643 return false;
644 } else {
645 this.setAttribute('clicked', '1');
646 }
647 destroyCameraPreview();
648 errorCallback('no image selected');
649 }
650
651 /**
652 * When the phone orientation change, get the event and change camera preview rotation
653 * @param {Object} e - SimpleOrientationSensorOrientationChangedEventArgs
654 */
655 function onOrientationChange(e) {
656 setPreviewRotation(e.orientation);
657 }
658
659 /**
660 * Converts SimpleOrientation to a VideoRotation to remove difference between camera sensor orientation
661 * and video orientation
662 * @param {number} orientation - Windows.Devices.Sensors.SimpleOrientation
663 * @return {number} - Windows.Media.Capture.VideoRotation
664 */
665 function orientationToRotation(orientation) {
666 // VideoRotation enumerable and BitmapRotation enumerable have the same values
667 // https://msdn.microsoft.com/en-us/library/windows/apps/windows.media.capture.videorotation.aspx
668 // https://msdn.microsoft.com/en-us/library/windows/apps/windows.graphics.imaging.bitmaprotation.aspx
669
670 switch (orientation) {
671 // portrait
672 case Windows.Devices.Sensors.SimpleOrientation.notRotated:
673 return Windows.Media.Capture.VideoRotation.clockwise90Degrees;
674 // landscape
675 case Windows.Devices.Sensors.SimpleOrientation.rotated90DegreesCounterclockwise:
676 return Windows.Media.Capture.VideoRotation.none;
677 // portrait-flipped (not supported by WinPhone Apps)
678 case Windows.Devices.Sensors.SimpleOrientation.rotated180DegreesCounterclockwise:
679 // Falling back to portrait default
680 return Windows.Media.Capture.VideoRotation.clockwise90Degrees;
681 // landscape-flipped
682 case Windows.Devices.Sensors.SimpleOrientation.rotated270DegreesCounterclockwise:
683 return Windows.Media.Capture.VideoRotation.clockwise180Degrees;
684 // faceup & facedown
685 default:
686 // Falling back to portrait default
687 return Windows.Media.Capture.VideoRotation.clockwise90Degrees;
688 }
689 }
690
691 /**
692 * Rotates the current MediaCapture's video
693 * @param {number} orientation - Windows.Devices.Sensors.SimpleOrientation
694 */
695 function setPreviewRotation(orientation) {
696 capture.setPreviewRotation(orientationToRotation(orientation));
697 }
698
699 try {
700 createCameraUI();
701 startCameraPreview();
702 } catch (ex) {
703 errorCallback(ex);
704 }
705}
706
707function takePictureFromCameraWindows(successCallback, errorCallback, args) {
708 var destinationType = args[1],
709 targetWidth = args[3],
710 targetHeight = args[4],
711 encodingType = args[5],
712 allowCrop = !!args[7],
713 saveToPhotoAlbum = args[9],
714 WMCapture = Windows.Media.Capture,
715 cameraCaptureUI = new WMCapture.CameraCaptureUI();
716
717 cameraCaptureUI.photoSettings.allowCropping = allowCrop;
718
719 if (encodingType == Camera.EncodingType.PNG) {
720 cameraCaptureUI.photoSettings.format = WMCapture.CameraCaptureUIPhotoFormat.png;
721 } else {
722 cameraCaptureUI.photoSettings.format = WMCapture.CameraCaptureUIPhotoFormat.jpeg;
723 }
724
725 // decide which max pixels should be supported by targetWidth or targetHeight.
726 var maxRes = null;
727 var UIMaxRes = WMCapture.CameraCaptureUIMaxPhotoResolution;
728 var totalPixels = targetWidth * targetHeight;
729
730 if (targetWidth == -1 && targetHeight == -1) {
731 maxRes = UIMaxRes.highestAvailable;
732 }
733 // Temp fix for CB-10539
734 /*else if (totalPixels <= 320 * 240) {
735 maxRes = UIMaxRes.verySmallQvga;
736 }*/
737 else if (totalPixels <= 640 * 480) {
738 maxRes = UIMaxRes.smallVga;
739 } else if (totalPixels <= 1024 * 768) {
740 maxRes = UIMaxRes.mediumXga;
741 } else if (totalPixels <= 3 * 1000 * 1000) {
742 maxRes = UIMaxRes.large3M;
743 } else if (totalPixels <= 5 * 1000 * 1000) {
744 maxRes = UIMaxRes.veryLarge5M;
745 } else {
746 maxRes = UIMaxRes.highestAvailable;
747 }
748
749 cameraCaptureUI.photoSettings.maxResolution = maxRes;
750
751 var cameraPicture;
752
753 // define focus handler for windows phone 10.0
754 var savePhotoOnFocus = function () {
755 window.removeEventListener("focus", savePhotoOnFocus);
756 // call only when the app is in focus again
757 savePhoto(cameraPicture, {
758 destinationType: destinationType,
759 targetHeight: targetHeight,
760 targetWidth: targetWidth,
761 encodingType: encodingType,
762 saveToPhotoAlbum: saveToPhotoAlbum
763 }, successCallback, errorCallback);
764 };
765
766 // if windows phone 10, add and delete focus eventHandler to capture the focus back from cameraUI to app
767 if (navigator.appVersion.indexOf('Windows Phone 10.0') >= 0) {
768 window.addEventListener("focus", savePhotoOnFocus);
769 }
770
771 cameraCaptureUI.captureFileAsync(WMCapture.CameraCaptureUIMode.photo).done(function (picture) {
772 if (!picture) {
773 errorCallback("User didn't capture a photo.");
774 // Remove the focus handler if present
775 window.removeEventListener("focus", savePhotoOnFocus);
776 return;
777 }
778 cameraPicture = picture;
779
780 // If not windows 10, call savePhoto() now. If windows 10, wait for the app to be in focus again
781 if (navigator.appVersion.indexOf('Windows Phone 10.0') < 0) {
782 savePhoto(cameraPicture, {
783 destinationType: destinationType,
784 targetHeight: targetHeight,
785 targetWidth: targetWidth,
786 encodingType: encodingType,
787 saveToPhotoAlbum: saveToPhotoAlbum
788 }, successCallback, errorCallback);
789 }
790 }, function () {
791 errorCallback("Fail to capture a photo.");
792 window.removeEventListener("focus", savePhotoOnFocus);
793 });
794}
795
796function savePhoto(picture, options, successCallback, errorCallback) {
797 // success callback for capture operation
798 var success = function(picture) {
799 if (options.destinationType == Camera.DestinationType.FILE_URI || options.destinationType == Camera.DestinationType.NATIVE_URI) {
800 if (options.targetHeight > 0 && options.targetWidth > 0) {
801 resizeImage(successCallback, errorCallback, picture, options.targetWidth, options.targetHeight, options.encodingType);
802 } else {
803 picture.copyAsync(getAppData().localFolder, picture.name, OptUnique).done(function (copiedFile) {
804 successCallback("ms-appdata:///local/" + copiedFile.name);
805 },errorCallback);
806 }
807 } else {
808 if (options.targetHeight > 0 && options.targetWidth > 0) {
809 resizeImageBase64(successCallback, errorCallback, picture, options.targetWidth, options.targetHeight);
810 } else {
811 fileIO.readBufferAsync(picture).done(function(buffer) {
812 var strBase64 = encodeToBase64String(buffer);
813 picture.deleteAsync().done(function() {
814 successCallback(strBase64);
815 }, function(err) {
816 errorCallback(err);
817 });
818 }, errorCallback);
819 }
820 }
821 };
822
823 if (!options.saveToPhotoAlbum) {
824 success(picture);
825 return;
826 } else {
827 var savePicker = new Windows.Storage.Pickers.FileSavePicker();
828 var saveFile = function(file) {
829 if (file) {
830 // Prevent updates to the remote version of the file until we're done
831 Windows.Storage.CachedFileManager.deferUpdates(file);
832 picture.moveAndReplaceAsync(file)
833 .then(function() {
834 // Let Windows know that we're finished changing the file so
835 // the other app can update the remote version of the file.
836 return Windows.Storage.CachedFileManager.completeUpdatesAsync(file);
837 })
838 .done(function(updateStatus) {
839 if (updateStatus === Windows.Storage.Provider.FileUpdateStatus.complete) {
840 success(picture);
841 } else {
842 errorCallback("File update status is not complete.");
843 }
844 }, errorCallback);
845 } else {
846 errorCallback("Failed to select a file.");
847 }
848 };
849 savePicker.suggestedStartLocation = pickerLocId.picturesLibrary;
850
851 if (options.encodingType === Camera.EncodingType.PNG) {
852 savePicker.fileTypeChoices.insert("PNG", [".png"]);
853 savePicker.suggestedFileName = "photo.png";
854 } else {
855 savePicker.fileTypeChoices.insert("JPEG", [".jpg"]);
856 savePicker.suggestedFileName = "photo.jpg";
857 }
858
859 // If Windows Phone 8.1 use pickSaveFileAndContinue()
860 if (navigator.appVersion.indexOf('Windows Phone 8.1') >= 0) {
861 /*
862 Need to add and remove an event listener to catch activation state
863 Using FileSavePicker will suspend the app and it's required to catch the pickSaveFileContinuation
864 https://msdn.microsoft.com/en-us/library/windows/apps/xaml/dn631755.aspx
865 */
866 var fileSaveHandler = function(eventArgs) {
867 if (eventArgs.kind === Windows.ApplicationModel.Activation.ActivationKind.pickSaveFileContinuation) {
868 var file = eventArgs.file;
869 saveFile(file);
870 webUIApp.removeEventListener("activated", fileSaveHandler);
871 }
872 };
873 webUIApp.addEventListener("activated", fileSaveHandler);
874 savePicker.pickSaveFileAndContinue();
875 } else {
876 savePicker.pickSaveFileAsync()
877 .done(saveFile, errorCallback);
878 }
879 }
880}
881
882require("cordova/exec/proxy").add("Camera",module.exports);