diff --git a/README.md b/README.md index e9a60253f..4c92e77a7 100644 --- a/README.md +++ b/README.md @@ -172,8 +172,6 @@ __Supported Platforms__ - Android - Browser - iOS -- Windows -- OSX More examples [here](#camera-getPicture-examples). Quirks [here](#camera-getPicture-quirks). @@ -476,16 +474,6 @@ displays: // do your thing here! }, 0); -#### Windows quirks - -On Windows Phone 8.1 using `SAVEDPHOTOALBUM` or `PHOTOLIBRARY` as a source type causes application to suspend until file picker returns the selected image and -then restore with start page as defined in app's `config.xml`. In case when `camera.getPicture` was called from different page, this will lead to reloading -start page from scratch and success and error callbacks will never be called. - -To avoid this we suggest using SPA pattern or call `camera.getPicture` only from your app's start page. - -More information about Windows Phone 8.1 picker APIs is here: [How to continue your Windows Phone app after calling a file picker](https://msdn.microsoft.com/en-us/library/windows/apps/dn720490.aspx) - ## `CameraOptions` Errata #### Android Quirks @@ -571,12 +559,6 @@ function displayImage(imgUri) { } ``` -To display the image on some platforms, you might need to include the main part of the URI in the Content-Security-Policy `` element in index.html. For example, on Windows 10, you can include `ms-appdata:` in your `` element. Here is an example. - -```html - -``` - ## Take a Picture and Return Thumbnails (Resize the Picture) To get smaller images, you can return a resized image by passing both `targetHeight` and `targetWidth` values with your CameraOptions object. In this example, you resize the returned image to fit in a 100px by 100px box (the aspect ratio is maintained, so 100px is either the height or width, whichever is greater in the source). diff --git a/package.json b/package.json index 239e92aac..744959289 100644 --- a/package.json +++ b/package.json @@ -8,9 +8,7 @@ "platforms": [ "android", "ios", - "browser", - "windows", - "osx" + "browser" ] }, "repository": "github:apache/cordova-plugin-camera", @@ -21,9 +19,7 @@ "ecosystem:cordova", "cordova-android", "cordova-ios", - "cordova-browser", - "cordova-windows", - "cordova-osx" + "cordova-browser" ], "scripts": { "test": "npm run lint", diff --git a/src/osx/CDVCamera.h b/src/osx/CDVCamera.h deleted file mode 100644 index 0b456aa3f..000000000 --- a/src/osx/CDVCamera.h +++ /dev/null @@ -1,80 +0,0 @@ -/* - Licensed to the Apache Software Foundation (ASF) under one - or more contributor license agreements. See the NOTICE file - distributed with this work for additional information - regarding copyright ownership. The ASF licenses this file - to you under the Apache License, Version 2.0 (the - "License"); you may not use this file except in compliance - with the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, - software distributed under the License is distributed on an - "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - KIND, either express or implied. See the License for the - specific language governing permissions and limitations - under the License. - */ -#import -#import -#import - - - -enum CDVDestinationType { - DestinationTypeDataUrl = 0, - DestinationTypeFileUri -}; -typedef NSUInteger CDVDestinationType; - -enum CDVSourceType { - SourceTypePhotoLibrary = 0, - SourceTypeCamera, - SourceTypePhotoAlbum -}; -typedef NSUInteger CDVSourceType; - -enum CDVEncodingType { - EncodingTypeJPEG = 0, - EncodingTypePNG -}; -typedef NSUInteger CDVEncodingType; - -enum CDVMediaType { - MediaTypePicture = 0, - MediaTypeVideo, - MediaTypeAll -}; -typedef NSUInteger CDVMediaType; - - -// ======================================================================= // - - -@interface CDVPictureOptions : NSObject - -@property (strong) NSNumber *quality; -@property (assign) CDVDestinationType destinationType; -@property (assign) CDVSourceType sourceType; -@property (assign) CGSize targetSize; -@property (assign) CDVEncodingType encodingType; -@property (assign) CDVMediaType mediaType; -@property (assign) BOOL allowsEditing; -@property (assign) BOOL correctOrientation; -@property (assign) BOOL saveToPhotoAlbum; - -+ (instancetype) createFromTakePictureArguments:(CDVInvokedUrlCommand *)command; - -@end - - -// ======================================================================= // - - -@interface CDVCamera : CDVPlugin - -- (void)takePicture:(CDVInvokedUrlCommand *)command; -- (void)cleanup:(CDVInvokedUrlCommand *)command; - -@end diff --git a/src/osx/CDVCamera.m b/src/osx/CDVCamera.m deleted file mode 100644 index f85e7f712..000000000 --- a/src/osx/CDVCamera.m +++ /dev/null @@ -1,258 +0,0 @@ -/* - Licensed to the Apache Software Foundation (ASF) under one - or more contributor license agreements. See the NOTICE file - distributed with this work for additional information - regarding copyright ownership. The ASF licenses this file - to you under the Apache License, Version 2.0 (the - "License"); you may not use this file except in compliance - with the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, - software distributed under the License is distributed on an - "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - KIND, either express or implied. See the License for the - specific language governing permissions and limitations - under the License. - */ -#import "CDVCamera.h" - - -@implementation CDVPictureOptions - -+ (instancetype) createFromTakePictureArguments:(CDVInvokedUrlCommand*)command { - CDVPictureOptions *pictureOptions = [[CDVPictureOptions alloc] init]; - - pictureOptions.quality = [command argumentAtIndex:0 withDefault:@(50)]; - pictureOptions.destinationType = [[command argumentAtIndex:1 withDefault:@(DestinationTypeFileUri)] unsignedIntegerValue]; - pictureOptions.sourceType = [[command argumentAtIndex:2 withDefault:@(SourceTypeCamera)] unsignedIntegerValue]; - - NSNumber *targetWidth = [command argumentAtIndex:3 withDefault:nil]; - NSNumber *targetHeight = [command argumentAtIndex:4 withDefault:nil]; - pictureOptions.targetSize = CGSizeMake(0, 0); - if ((targetWidth != nil) && (targetHeight != nil)) { - pictureOptions.targetSize = CGSizeMake([targetWidth floatValue], [targetHeight floatValue]); - } - - pictureOptions.encodingType = [[command argumentAtIndex:5 withDefault:@(EncodingTypeJPEG)] unsignedIntegerValue]; - pictureOptions.mediaType = [[command argumentAtIndex:6 withDefault:@(MediaTypePicture)] unsignedIntegerValue]; - pictureOptions.allowsEditing = [[command argumentAtIndex:7 withDefault:@(NO)] boolValue]; - pictureOptions.correctOrientation = [[command argumentAtIndex:8 withDefault:@(NO)] boolValue]; - pictureOptions.saveToPhotoAlbum = [[command argumentAtIndex:9 withDefault:@(NO)] boolValue]; - - return pictureOptions; -} - -@end - - -// ======================================================================= // - - -@implementation CDVCamera - -/*! - Static array that stores the temporary created files allowing to delete them when calling navigator.camera.cleanup(...) - */ -static NSMutableArray *cleanUpFiles; - -+ (void)initialize { - cleanUpFiles = [NSMutableArray array]; -} - -- (void)takePicture:(CDVInvokedUrlCommand *)command { - CDVPictureOptions *pictureOptions = [CDVPictureOptions createFromTakePictureArguments:command]; - if (pictureOptions.sourceType == SourceTypeCamera) { - [self takePictureFromCamera:command withOptions:pictureOptions]; - } else { - [self takePictureFromFile:command withOptions:pictureOptions]; - } -} - -- (void)cleanup:(CDVInvokedUrlCommand*)command { - [self.commandDelegate runInBackground:^{ - if (cleanUpFiles.count > 0) { - for (int i=0; i 0 && pictureOptions.targetSize.height > 0) { - sourceImage = [self resizeImage:sourceImage toSize:pictureOptions.targetSize]; - } - - CGImageRef cgRef = [sourceImage CGImageForProposedRect:NULL context:nil hints:nil]; - NSBitmapImageRep *imageRepresentation = [[NSBitmapImageRep alloc] initWithCGImage:cgRef]; - - NSData *data = (pictureOptions.encodingType == EncodingTypeJPEG) - ? [imageRepresentation representationUsingType:NSJPEGFileType properties:@{NSImageCompressionFactor: [NSNumber numberWithFloat:pictureOptions.quality.floatValue/100.f]}] - : [imageRepresentation representationUsingType:NSPNGFileType properties:@{NSImageCompressionFactor: @1.0}]; - - return data; -} - -/*! - Auxiliar method to resize an image. - */ -- (NSImage *)resizeImage:(NSImage *)image toSize:(CGSize)newSize { - CGFloat aspectWidth = newSize.width / image.size.width; - CGFloat aspectHeight = newSize.height / image.size.height; - CGFloat aspectRatio = MIN(aspectWidth, aspectHeight); - - CGSize scaledSize = NSMakeSize(image.size.width*aspectRatio, image.size.height*aspectRatio); - - NSImage *smallImage = [[NSImage alloc] initWithSize: scaledSize]; - [smallImage lockFocus]; - [image setSize: scaledSize]; - [[NSGraphicsContext currentContext] setImageInterpolation:NSImageInterpolationHigh]; - [image drawAtPoint:NSZeroPoint fromRect:CGRectMake(0, 0, scaledSize.width, scaledSize.height) operation:NSCompositeCopy fraction:1.0]; - [smallImage unlockFocus]; - return smallImage; -} - -/*! - Auxiliar method to know if a given file is an image or not. - */ -- (BOOL)fileIsImage:(NSURL *)fileURL { - NSString *type; - BOOL isImage = NO; - - if ([fileURL getResourceValue:&type forKey:NSURLTypeIdentifierKey error:nil]) { - isImage = [[NSImage imageTypes] containsObject:type]; - } - - return isImage; -} - -/*! - Auxiliar method that generates an unique filename for an image in the temporary directory. - */ -- (NSString *)uniqueImageName:(CDVPictureOptions *)pictureOptions { - NSString *tempDir = NSTemporaryDirectory(); - NSString *guid = [[NSProcessInfo processInfo] globallyUniqueString] ; - NSString *extension = (pictureOptions.encodingType == EncodingTypeJPEG) ? @"jpeg" : @"png"; - NSString *uniqueFileName = [NSString stringWithFormat:@"%@%@.%@", tempDir, guid, extension]; - return uniqueFileName; -} - -@end \ No newline at end of file diff --git a/src/windows/CameraProxy.js b/src/windows/CameraProxy.js deleted file mode 100644 index 0dda16816..000000000 --- a/src/windows/CameraProxy.js +++ /dev/null @@ -1,861 +0,0 @@ -/* - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * -*/ - -/* global Windows:true, URL:true, module:true, require:true, WinJS:true */ - -const Camera = require('./Camera'); - -const getAppData = function () { - return Windows.Storage.ApplicationData.current; -}; -const encodeToBase64String = function (buffer) { - return Windows.Security.Cryptography.CryptographicBuffer.encodeToBase64String(buffer); -}; -const OptUnique = Windows.Storage.CreationCollisionOption.generateUniqueName; -const CapMSType = Windows.Media.Capture.MediaStreamType; -const webUIApp = Windows.UI.WebUI.WebUIApplication; -const fileIO = Windows.Storage.FileIO; -const pickerLocId = Windows.Storage.Pickers.PickerLocationId; - -module.exports = { - - // args will contain : - // ... it is an array, so be careful - // 0 quality:50, - // 1 destinationType:Camera.DestinationType.FILE_URI, - // 2 sourceType:Camera.PictureSourceType.CAMERA, - // 3 targetWidth:-1, - // 4 targetHeight:-1, - // 5 encodingType:Camera.EncodingType.JPEG, - // 6 mediaType:Camera.MediaType.PICTURE, - // 7 allowEdit:false, - // 8 correctOrientation:false, - // 9 saveToPhotoAlbum:false, - // 10 popoverOptions:null - // 11 cameraDirection:0 - - takePicture: function (successCallback, errorCallback, args) { - const sourceType = args[2]; - - if (sourceType !== Camera.PictureSourceType.CAMERA) { - takePictureFromFile(successCallback, errorCallback, args); - } else { - takePictureFromCamera(successCallback, errorCallback, args); - } - } -}; - -// https://msdn.microsoft.com/en-us/library/windows/apps/ff462087(v=vs.105).aspx -const windowsVideoContainers = ['.avi', '.flv', '.asx', '.asf', '.mov', '.mp4', '.mpg', '.rm', '.srt', '.swf', '.wmv', '.vob']; -const windowsPhoneVideoContainers = ['.avi', '.3gp', '.3g2', '.wmv', '.3gp', '.3g2', '.mp4', '.m4v']; - -// Default aspect ratio 1.78 (16:9 hd video standard) -const DEFAULT_ASPECT_RATIO = '1.8'; - -// Highest possible z-index supported across browsers. Anything used above is converted to this value. -const HIGHEST_POSSIBLE_Z_INDEX = 2147483647; - -// Resize method -function resizeImage (successCallback, errorCallback, file, targetWidth, targetHeight, encodingType) { - let tempPhotoFileName = ''; - let targetContentType = ''; - - if (encodingType === Camera.EncodingType.PNG) { - tempPhotoFileName = 'camera_cordova_temp_return.png'; - targetContentType = 'image/png'; - } else { - tempPhotoFileName = 'camera_cordova_temp_return.jpg'; - targetContentType = 'image/jpeg'; - } - - const storageFolder = getAppData().localFolder; - file.copyAsync(storageFolder, file.name, Windows.Storage.NameCollisionOption.replaceExisting) - .then(function (storageFile) { - return fileIO.readBufferAsync(storageFile); - }) - .then(function (buffer) { - const strBase64 = encodeToBase64String(buffer); - const imageData = 'data:' + file.contentType + ';base64,' + strBase64; - const image = new Image(); /* eslint no-undef : 0 */ - image.src = imageData; - image.onload = function () { - const ratio = Math.min(targetWidth / this.width, targetHeight / this.height); - const imageWidth = ratio * this.width; - const imageHeight = ratio * this.height; - - const canvas = document.createElement('canvas'); - let storageFileName; - - canvas.width = imageWidth; - canvas.height = imageHeight; - - canvas.getContext('2d').drawImage(this, 0, 0, imageWidth, imageHeight); - - const fileContent = canvas.toDataURL(targetContentType).split(',')[1]; - - const storageFolder = getAppData().localFolder; - - storageFolder.createFileAsync(tempPhotoFileName, OptUnique) - .then(function (storagefile) { - const content = Windows.Security.Cryptography.CryptographicBuffer.decodeFromBase64String(fileContent); - storageFileName = storagefile.name; - return fileIO.writeBufferAsync(storagefile, content); - }) - .done(function () { - successCallback('ms-appdata:///local/' + storageFileName); - }, errorCallback); - }; - }) - .done(null, function (err) { - errorCallback(err); - }); -} - -// Because of asynchronous method, so let the successCallback be called in it. -function resizeImageBase64 (successCallback, errorCallback, file, targetWidth, targetHeight) { - fileIO.readBufferAsync(file).done(function (buffer) { - const strBase64 = encodeToBase64String(buffer); - const imageData = 'data:' + file.contentType + ';base64,' + strBase64; - - const image = new Image(); /* eslint no-undef : 0 */ - image.src = imageData; - - image.onload = function () { - const ratio = Math.min(targetWidth / this.width, targetHeight / this.height); - const imageWidth = ratio * this.width; - const imageHeight = ratio * this.height; - const canvas = document.createElement('canvas'); - - canvas.width = imageWidth; - canvas.height = imageHeight; - - const ctx = canvas.getContext('2d'); - ctx.drawImage(this, 0, 0, imageWidth, imageHeight); - - // The resized file ready for upload - const finalFile = canvas.toDataURL(file.contentType); - - // Remove the prefix such as "data:" + contentType + ";base64," , in order to meet the Cordova API. - const arr = finalFile.split(','); - const newStr = finalFile.substr(arr[0].length + 1); - successCallback(newStr); - }; - }, function (err) { errorCallback(err); }); -} - -function takePictureFromFile (successCallback, errorCallback, args) { - // Detect Windows Phone - if (navigator.appVersion.indexOf('Windows Phone 8.1') >= 0) { - takePictureFromFileWP(successCallback, errorCallback, args); - } else { - takePictureFromFileWindows(successCallback, errorCallback, args); - } -} - -function takePictureFromFileWP (successCallback, errorCallback, args) { - const mediaType = args[6]; - const destinationType = args[1]; - const targetWidth = args[3]; - const targetHeight = args[4]; - const encodingType = args[5]; - - /* - Need to add and remove an event listener to catch activation state - Using FileOpenPicker will suspend the app and it's required to catch the PickSingleFileAndContinue - https://msdn.microsoft.com/en-us/library/windows/apps/xaml/dn631755.aspx - */ - const filePickerActivationHandler = function (eventArgs) { - if (eventArgs.kind === Windows.ApplicationModel.Activation.ActivationKind.pickFileContinuation) { - const file = eventArgs.files[0]; - if (!file) { - errorCallback("User didn't choose a file."); - webUIApp.removeEventListener('activated', filePickerActivationHandler); - return; - } - if (destinationType === Camera.DestinationType.FILE_URI) { - if (targetHeight > 0 && targetWidth > 0) { - resizeImage(successCallback, errorCallback, file, targetWidth, targetHeight, encodingType); - } else { - const storageFolder = getAppData().localFolder; - file.copyAsync(storageFolder, file.name, Windows.Storage.NameCollisionOption.replaceExisting).done(function (storageFile) { - successCallback(URL.createObjectURL(storageFile)); - }, function () { - errorCallback("Can't access localStorage folder."); - }); - } - } else { - if (targetHeight > 0 && targetWidth > 0) { - resizeImageBase64(successCallback, errorCallback, file, targetWidth, targetHeight); - } else { - fileIO.readBufferAsync(file).done(function (buffer) { - const strBase64 = encodeToBase64String(buffer); - successCallback(strBase64); - }, errorCallback); - } - } - webUIApp.removeEventListener('activated', filePickerActivationHandler); - } - }; - - const fileOpenPicker = new Windows.Storage.Pickers.FileOpenPicker(); - if (mediaType === Camera.MediaType.PICTURE) { - fileOpenPicker.fileTypeFilter.replaceAll(['.png', '.jpg', '.jpeg']); - fileOpenPicker.suggestedStartLocation = pickerLocId.picturesLibrary; - } else if (mediaType === Camera.MediaType.VIDEO) { - fileOpenPicker.fileTypeFilter.replaceAll(windowsPhoneVideoContainers); - fileOpenPicker.suggestedStartLocation = pickerLocId.videosLibrary; - } else { - fileOpenPicker.fileTypeFilter.replaceAll(['*']); - fileOpenPicker.suggestedStartLocation = pickerLocId.documentsLibrary; - } - - webUIApp.addEventListener('activated', filePickerActivationHandler); - fileOpenPicker.pickSingleFileAndContinue(); -} - -function takePictureFromFileWindows (successCallback, errorCallback, args) { - const mediaType = args[6]; - const destinationType = args[1]; - const targetWidth = args[3]; - const targetHeight = args[4]; - const encodingType = args[5]; - - const fileOpenPicker = new Windows.Storage.Pickers.FileOpenPicker(); - if (mediaType === Camera.MediaType.PICTURE) { - fileOpenPicker.fileTypeFilter.replaceAll(['.png', '.jpg', '.jpeg']); - fileOpenPicker.suggestedStartLocation = pickerLocId.picturesLibrary; - } else if (mediaType === Camera.MediaType.VIDEO) { - fileOpenPicker.fileTypeFilter.replaceAll(windowsVideoContainers); - fileOpenPicker.suggestedStartLocation = pickerLocId.videosLibrary; - } else { - fileOpenPicker.fileTypeFilter.replaceAll(['*']); - fileOpenPicker.suggestedStartLocation = pickerLocId.documentsLibrary; - } - - fileOpenPicker.pickSingleFileAsync().done(function (file) { - if (!file) { - errorCallback("User didn't choose a file."); - return; - } - if (destinationType === Camera.DestinationType.FILE_URI) { - if (targetHeight > 0 && targetWidth > 0) { - resizeImage(successCallback, errorCallback, file, targetWidth, targetHeight, encodingType); - } else { - const storageFolder = getAppData().localFolder; - file.copyAsync(storageFolder, file.name, Windows.Storage.NameCollisionOption.replaceExisting).done(function (storageFile) { - successCallback(URL.createObjectURL(storageFile)); - }, function () { - errorCallback("Can't access localStorage folder."); - }); - } - } else { - if (targetHeight > 0 && targetWidth > 0) { - resizeImageBase64(successCallback, errorCallback, file, targetWidth, targetHeight); - } else { - fileIO.readBufferAsync(file).done(function (buffer) { - const strBase64 = encodeToBase64String(buffer); - successCallback(strBase64); - }, errorCallback); - } - } - }, function () { - errorCallback("User didn't choose a file."); - }); -} - -function takePictureFromCamera (successCallback, errorCallback, args) { - // Check if necessary API available - if (!Windows.Media.Capture.CameraCaptureUI) { - takePictureFromCameraWP(successCallback, errorCallback, args); - } else { - takePictureFromCameraWindows(successCallback, errorCallback, args); - } -} - -function takePictureFromCameraWP (successCallback, errorCallback, args) { - // We are running on WP8.1 which lacks CameraCaptureUI class - // so we need to use MediaCapture class instead and implement custom UI for camera - const destinationType = args[1]; - const targetWidth = args[3]; - const targetHeight = args[4]; - const encodingType = args[5]; - const saveToPhotoAlbum = args[9]; - const cameraDirection = args[11]; - let capturePreview = null; - let cameraCaptureButton = null; - let cameraCancelButton = null; - let capture = null; - let captureSettings = null; - const CaptureNS = Windows.Media.Capture; - let sensor = null; - - function createCameraUI () { - // create style for take and cancel buttons - const 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;'; - - // Create fullscreen preview - // z-order style element for capturePreview and cameraCancelButton elts - // is necessary to avoid overriding by another page elements, -1 sometimes is not enough - capturePreview = document.createElement('video'); - capturePreview.style.cssText = 'position: fixed; left: 0; top: 0; width: 100%; height: 100%; z-index: ' + (HIGHEST_POSSIBLE_Z_INDEX - 1) + ';'; - - // Create capture button - cameraCaptureButton = document.createElement('button'); - cameraCaptureButton.innerText = 'Take'; - cameraCaptureButton.style.cssText = buttonStyle + 'position: fixed; left: 0; bottom: 0; margin: 20px; z-index: ' + HIGHEST_POSSIBLE_Z_INDEX + ';'; - - // Create cancel button - cameraCancelButton = document.createElement('button'); - cameraCancelButton.innerText = 'Cancel'; - cameraCancelButton.style.cssText = buttonStyle + 'position: fixed; right: 0; bottom: 0; margin: 20px; z-index: ' + HIGHEST_POSSIBLE_Z_INDEX + ';'; - - capture = new CaptureNS.MediaCapture(); - - captureSettings = new CaptureNS.MediaCaptureInitializationSettings(); - captureSettings.streamingCaptureMode = CaptureNS.StreamingCaptureMode.video; - } - - function continueVideoOnFocus () { - // if preview is defined it would be stuck, play it - if (capturePreview) { - capturePreview.play(); - } - } - - function startCameraPreview () { - // Search for available camera devices - // This is necessary to detect which camera (front or back) we should use - const DeviceEnum = Windows.Devices.Enumeration; - const expectedPanel = cameraDirection === 1 ? DeviceEnum.Panel.front : DeviceEnum.Panel.back; - - // Add focus event handler to capture the event when user suspends the app and comes back while the preview is on - window.addEventListener('focus', continueVideoOnFocus); - - DeviceEnum.DeviceInformation.findAllAsync(DeviceEnum.DeviceClass.videoCapture).then(function (devices) { - if (devices.length <= 0) { - destroyCameraPreview(); - errorCallback('Camera not found'); - return; - } - - devices.forEach(function (currDev) { - if (currDev.enclosureLocation.panel && currDev.enclosureLocation.panel === expectedPanel) { - captureSettings.videoDeviceId = currDev.id; - } - }); - - captureSettings.photoCaptureSource = Windows.Media.Capture.PhotoCaptureSource.photo; - - return capture.initializeAsync(captureSettings); - }).then(function () { - // create focus control if available - const VideoDeviceController = capture.videoDeviceController; - const FocusControl = VideoDeviceController.focusControl; - - if (FocusControl.supported === true) { - capturePreview.addEventListener('click', function () { - // Make sure function isn't called again before previous focus is completed - if (this.getAttribute('clicked') === '1') { - return false; - } else { - this.setAttribute('clicked', '1'); - } - const preset = Windows.Media.Devices.FocusPreset.autoNormal; - const parent = this; - FocusControl.setPresetAsync(preset).done(function () { - // set the clicked attribute back to '0' to allow focus again - parent.setAttribute('clicked', '0'); - }); - }); - } - - // msdn.microsoft.com/en-us/library/windows/apps/hh452807.aspx - capturePreview.msZoom = true; - capturePreview.src = URL.createObjectURL(capture); - capturePreview.play(); - - // Bind events to controls - sensor = Windows.Devices.Sensors.SimpleOrientationSensor.getDefault(); - if (sensor !== null) { - sensor.addEventListener('orientationchanged', onOrientationChange); - } - - // add click events to capture and cancel buttons - cameraCaptureButton.addEventListener('click', onCameraCaptureButtonClick); - cameraCancelButton.addEventListener('click', onCameraCancelButtonClick); - - // Change default orientation - if (sensor) { - setPreviewRotation(sensor.getCurrentOrientation()); - } else { - setPreviewRotation(Windows.Graphics.Display.DisplayInformation.getForCurrentView().currentOrientation); - } - - // Get available aspect ratios - const aspectRatios = getAspectRatios(capture); - - // Couldn't find a good ratio - if (aspectRatios.length === 0) { - destroyCameraPreview(); - errorCallback('There\'s not a good aspect ratio available'); - return; - } - - // add elements to body - document.body.appendChild(capturePreview); - document.body.appendChild(cameraCaptureButton); - document.body.appendChild(cameraCancelButton); - - if (aspectRatios.indexOf(DEFAULT_ASPECT_RATIO) > -1) { - return setAspectRatio(capture, DEFAULT_ASPECT_RATIO); - } else { - // Doesn't support 16:9 - pick next best - return setAspectRatio(capture, aspectRatios[0]); - } - }).done(null, function (err) { - destroyCameraPreview(); - errorCallback('Camera intitialization error ' + err); - }); - } - - function destroyCameraPreview () { - // If sensor is available, remove event listener - if (sensor !== null) { - sensor.removeEventListener('orientationchanged', onOrientationChange); - } - - // Pause and dispose preview element - capturePreview.pause(); - capturePreview.src = null; - - // Remove event listeners from buttons - cameraCaptureButton.removeEventListener('click', onCameraCaptureButtonClick); - cameraCancelButton.removeEventListener('click', onCameraCancelButtonClick); - - // Remove the focus event handler - window.removeEventListener('focus', continueVideoOnFocus); - - // Remove elements - [capturePreview, cameraCaptureButton, cameraCancelButton].forEach(function (elem) { - if (elem /* && elem in document.body.childNodes */) { - document.body.removeChild(elem); - } - }); - - // Stop and dispose media capture manager - if (capture) { - capture.stopRecordAsync(); - capture = null; - } - } - - function captureAction () { - let encodingProperties; - let fileName; - const tempFolder = getAppData().temporaryFolder; - - if (encodingType === Camera.EncodingType.PNG) { - fileName = 'photo.png'; - encodingProperties = Windows.Media.MediaProperties.ImageEncodingProperties.createPng(); - } else { - fileName = 'photo.jpg'; - encodingProperties = Windows.Media.MediaProperties.ImageEncodingProperties.createJpeg(); - } - - tempFolder.createFileAsync(fileName, OptUnique) - .then(function (tempCapturedFile) { - return new WinJS.Promise(function (complete) { - const photoStream = new Windows.Storage.Streams.InMemoryRandomAccessStream(); - const finalStream = new Windows.Storage.Streams.InMemoryRandomAccessStream(); - capture.capturePhotoToStreamAsync(encodingProperties, photoStream) - .then(function () { - return Windows.Graphics.Imaging.BitmapDecoder.createAsync(photoStream); - }) - .then(function (dec) { - finalStream.size = 0; // BitmapEncoder requires the output stream to be empty - return Windows.Graphics.Imaging.BitmapEncoder.createForTranscodingAsync(finalStream, dec); - }) - .then(function (enc) { - // We need to rotate the photo wrt sensor orientation - enc.bitmapTransform.rotation = orientationToRotation(sensor.getCurrentOrientation()); - return enc.flushAsync(); - }) - .then(function () { - return tempCapturedFile.openAsync(Windows.Storage.FileAccessMode.readWrite); - }) - .then(function (fileStream) { - return Windows.Storage.Streams.RandomAccessStream.copyAndCloseAsync(finalStream, fileStream); - }) - .done(function () { - photoStream.close(); - finalStream.close(); - complete(tempCapturedFile); - }, function () { - photoStream.close(); - finalStream.close(); - throw new Error('An error has occured while capturing the photo.'); - }); - }); - }) - .done(function (capturedFile) { - destroyCameraPreview(); - savePhoto(capturedFile, { - destinationType, - targetHeight, - targetWidth, - encodingType, - saveToPhotoAlbum - }, successCallback, errorCallback); - }, function (err) { - destroyCameraPreview(); - errorCallback(err); - }); - } - - function getAspectRatios (capture) { - const videoDeviceController = capture.videoDeviceController; - const photoAspectRatios = videoDeviceController.getAvailableMediaStreamProperties(CapMSType.photo).map(function (element) { - return (element.width / element.height).toFixed(1); - }).filter(function (element, index, array) { return (index === array.indexOf(element)); }); - - const videoAspectRatios = videoDeviceController.getAvailableMediaStreamProperties(CapMSType.videoRecord).map(function (element) { - return (element.width / element.height).toFixed(1); - }).filter(function (element, index, array) { return (index === array.indexOf(element)); }); - - const videoPreviewAspectRatios = videoDeviceController.getAvailableMediaStreamProperties(CapMSType.videoPreview).map(function (element) { - return (element.width / element.height).toFixed(1); - }).filter(function (element, index, array) { return (index === array.indexOf(element)); }); - - const allAspectRatios = [].concat(photoAspectRatios, videoAspectRatios, videoPreviewAspectRatios); - - const aspectObj = allAspectRatios.reduce(function (map, item) { - if (!map[item]) { - map[item] = 0; - } - map[item]++; - return map; - }, {}); - - return Object.keys(aspectObj).filter(function (k) { - return aspectObj[k] === 3; - }); - } - - function setAspectRatio (capture, aspect) { - // Max photo resolution with desired aspect ratio - const videoDeviceController = capture.videoDeviceController; - const photoResolution = videoDeviceController.getAvailableMediaStreamProperties(CapMSType.photo) - .filter(function (elem) { - return ((elem.width / elem.height).toFixed(1) === aspect); - }) - .reduce(function (prop1, prop2) { - return (prop1.width * prop1.height) > (prop2.width * prop2.height) ? prop1 : prop2; - }); - - // Max video resolution with desired aspect ratio - const videoRecordResolution = videoDeviceController.getAvailableMediaStreamProperties(CapMSType.videoRecord) - .filter(function (elem) { - return ((elem.width / elem.height).toFixed(1) === aspect); - }) - .reduce(function (prop1, prop2) { - return (prop1.width * prop1.height) > (prop2.width * prop2.height) ? prop1 : prop2; - }); - - // Max video preview resolution with desired aspect ratio - const videoPreviewResolution = videoDeviceController.getAvailableMediaStreamProperties(CapMSType.videoPreview) - .filter(function (elem) { - return ((elem.width / elem.height).toFixed(1) === aspect); - }) - .reduce(function (prop1, prop2) { - return (prop1.width * prop1.height) > (prop2.width * prop2.height) ? prop1 : prop2; - }); - - return videoDeviceController.setMediaStreamPropertiesAsync(CapMSType.photo, photoResolution) - .then(function () { - return videoDeviceController.setMediaStreamPropertiesAsync(CapMSType.videoPreview, videoPreviewResolution); - }) - .then(function () { - return videoDeviceController.setMediaStreamPropertiesAsync(CapMSType.videoRecord, videoRecordResolution); - }); - } - - /** - * When Capture button is clicked, try to capture a picture and return - */ - function onCameraCaptureButtonClick () { - // Make sure user can't click more than once - if (this.getAttribute('clicked') === '1') { - return false; - } else { - this.setAttribute('clicked', '1'); - } - captureAction(); - } - - /** - * When Cancel button is clicked, destroy camera preview and return with error callback - */ - function onCameraCancelButtonClick () { - // Make sure user can't click more than once - if (this.getAttribute('clicked') === '1') { - return false; - } else { - this.setAttribute('clicked', '1'); - } - destroyCameraPreview(); - errorCallback('no image selected'); - } - - /** - * When the phone orientation change, get the event and change camera preview rotation - * @param {Object} e - SimpleOrientationSensorOrientationChangedEventArgs - */ - function onOrientationChange (e) { - setPreviewRotation(e.orientation); - } - - /** - * Converts SimpleOrientation to a VideoRotation to remove difference between camera sensor orientation - * and video orientation - * @param {number} orientation - Windows.Devices.Sensors.SimpleOrientation - * @return {number} - Windows.Media.Capture.VideoRotation - */ - function orientationToRotation (orientation) { - // VideoRotation enumerable and BitmapRotation enumerable have the same values - // https://msdn.microsoft.com/en-us/library/windows/apps/windows.media.capture.videorotation.aspx - // https://msdn.microsoft.com/en-us/library/windows/apps/windows.graphics.imaging.bitmaprotation.aspx - - switch (orientation) { - // portrait - case Windows.Devices.Sensors.SimpleOrientation.notRotated: - return Windows.Media.Capture.VideoRotation.clockwise90Degrees; - // landscape - case Windows.Devices.Sensors.SimpleOrientation.rotated90DegreesCounterclockwise: - return Windows.Media.Capture.VideoRotation.none; - // portrait-flipped (not supported by WinPhone Apps) - case Windows.Devices.Sensors.SimpleOrientation.rotated180DegreesCounterclockwise: - // Falling back to portrait default - return Windows.Media.Capture.VideoRotation.clockwise90Degrees; - // landscape-flipped - case Windows.Devices.Sensors.SimpleOrientation.rotated270DegreesCounterclockwise: - return Windows.Media.Capture.VideoRotation.clockwise180Degrees; - // faceup & facedown - default: - // Falling back to portrait default - return Windows.Media.Capture.VideoRotation.clockwise90Degrees; - } - } - - /** - * Rotates the current MediaCapture's video - * @param {number} orientation - Windows.Devices.Sensors.SimpleOrientation - */ - function setPreviewRotation (orientation) { - capture.setPreviewRotation(orientationToRotation(orientation)); - } - - try { - createCameraUI(); - startCameraPreview(); - } catch (ex) { - errorCallback(ex); - } -} - -function takePictureFromCameraWindows (successCallback, errorCallback, args) { - const destinationType = args[1]; - const targetWidth = args[3]; - const targetHeight = args[4]; - const encodingType = args[5]; - const allowCrop = !!args[7]; - const saveToPhotoAlbum = args[9]; - const WMCapture = Windows.Media.Capture; - const cameraCaptureUI = new WMCapture.CameraCaptureUI(); - - cameraCaptureUI.photoSettings.allowCropping = allowCrop; - - if (encodingType === Camera.EncodingType.PNG) { - cameraCaptureUI.photoSettings.format = WMCapture.CameraCaptureUIPhotoFormat.png; - } else { - cameraCaptureUI.photoSettings.format = WMCapture.CameraCaptureUIPhotoFormat.jpeg; - } - - // decide which max pixels should be supported by targetWidth or targetHeight. - let maxRes = null; - const UIMaxRes = WMCapture.CameraCaptureUIMaxPhotoResolution; - const totalPixels = targetWidth * targetHeight; - - if (targetWidth === -1 && targetHeight === -1) { - maxRes = UIMaxRes.highestAvailable; - // Temp fix for CB-10539 - /* else if (totalPixels <= 320 * 240) { - maxRes = UIMaxRes.verySmallQvga; - } */ - } else if (totalPixels <= 640 * 480) { - maxRes = UIMaxRes.smallVga; - } else if (totalPixels <= 1024 * 768) { - maxRes = UIMaxRes.mediumXga; - } else if (totalPixels <= 3 * 1000 * 1000) { - maxRes = UIMaxRes.large3M; - } else if (totalPixels <= 5 * 1000 * 1000) { - maxRes = UIMaxRes.veryLarge5M; - } else { - maxRes = UIMaxRes.highestAvailable; - } - - cameraCaptureUI.photoSettings.maxResolution = maxRes; - - let cameraPicture; - - // define focus handler for windows phone 10.0 - const savePhotoOnFocus = function () { - window.removeEventListener('focus', savePhotoOnFocus); - // call only when the app is in focus again - savePhoto(cameraPicture, { - destinationType, - targetHeight, - targetWidth, - encodingType, - saveToPhotoAlbum - }, successCallback, errorCallback); - }; - - // if windows phone 10, add and delete focus eventHandler to capture the focus back from cameraUI to app - if (navigator.appVersion.indexOf('Windows Phone 10.0') >= 0) { - window.addEventListener('focus', savePhotoOnFocus); - } - - cameraCaptureUI.captureFileAsync(WMCapture.CameraCaptureUIMode.photo).done(function (picture) { - if (!picture) { - errorCallback("User didn't capture a photo."); - // Remove the focus handler if present - window.removeEventListener('focus', savePhotoOnFocus); - return; - } - cameraPicture = picture; - - // If not windows 10, call savePhoto() now. If windows 10, wait for the app to be in focus again - if (navigator.appVersion.indexOf('Windows Phone 10.0') < 0) { - savePhoto(cameraPicture, { - destinationType, - targetHeight, - targetWidth, - encodingType, - saveToPhotoAlbum - }, successCallback, errorCallback); - } - }, function () { - errorCallback('Fail to capture a photo.'); - window.removeEventListener('focus', savePhotoOnFocus); - }); -} - -function savePhoto (picture, options, successCallback, errorCallback) { - // success callback for capture operation - const success = function (picture) { - if (options.destinationType === Camera.DestinationType.FILE_URI) { - if (options.targetHeight > 0 && options.targetWidth > 0) { - resizeImage(successCallback, errorCallback, picture, options.targetWidth, options.targetHeight, options.encodingType); - } else { - // CB-11714: check if target content-type is PNG to just rename as *.jpg since camera is captured as JPEG - if (options.encodingType === Camera.EncodingType.PNG) { - picture.name = picture.name.replace(/\.png$/, '.jpg'); - } - - picture.copyAsync(getAppData().localFolder, picture.name, OptUnique).done(function (copiedFile) { - successCallback('ms-appdata:///local/' + copiedFile.name); - }, errorCallback); - } - } else { - if (options.targetHeight > 0 && options.targetWidth > 0) { - resizeImageBase64(successCallback, errorCallback, picture, options.targetWidth, options.targetHeight); - } else { - fileIO.readBufferAsync(picture).done(function (buffer) { - const strBase64 = encodeToBase64String(buffer); - picture.deleteAsync().done(function () { - successCallback(strBase64); - }, function (err) { - errorCallback(err); - }); - }, errorCallback); - } - } - }; - - if (!options.saveToPhotoAlbum) { - success(picture); - } else { - const savePicker = new Windows.Storage.Pickers.FileSavePicker(); - const saveFile = function (file) { - if (file) { - // Prevent updates to the remote version of the file until we're done - Windows.Storage.CachedFileManager.deferUpdates(file); - picture.moveAndReplaceAsync(file) - .then(function () { - // Let Windows know that we're finished changing the file so - // the other app can update the remote version of the file. - return Windows.Storage.CachedFileManager.completeUpdatesAsync(file); - }) - .done(function (updateStatus) { - if (updateStatus === Windows.Storage.Provider.FileUpdateStatus.complete) { - success(picture); - } else { - errorCallback('File update status is not complete.'); - } - }, errorCallback); - } else { - errorCallback('Failed to select a file.'); - } - }; - savePicker.suggestedStartLocation = pickerLocId.picturesLibrary; - - if (options.encodingType === Camera.EncodingType.PNG) { - savePicker.fileTypeChoices.insert('PNG', ['.png']); - savePicker.suggestedFileName = 'photo.png'; - } else { - savePicker.fileTypeChoices.insert('JPEG', ['.jpg']); - savePicker.suggestedFileName = 'photo.jpg'; - } - - // If Windows Phone 8.1 use pickSaveFileAndContinue() - if (navigator.appVersion.indexOf('Windows Phone 8.1') >= 0) { - /* - Need to add and remove an event listener to catch activation state - Using FileSavePicker will suspend the app and it's required to catch the pickSaveFileContinuation - https://msdn.microsoft.com/en-us/library/windows/apps/xaml/dn631755.aspx - */ - const fileSaveHandler = function (eventArgs) { - if (eventArgs.kind === Windows.ApplicationModel.Activation.ActivationKind.pickSaveFileContinuation) { - const file = eventArgs.file; - saveFile(file); - webUIApp.removeEventListener('activated', fileSaveHandler); - } - }; - webUIApp.addEventListener('activated', fileSaveHandler); - savePicker.pickSaveFileAndContinue(); - } else { - savePicker.pickSaveFileAsync() - .done(saveFile, errorCallback); - } - } -} - -require('cordova/exec/proxy').add('Camera', module.exports); diff --git a/tests/tests.js b/tests/tests.js index 3cbf49aa2..721d41232 100644 --- a/tests/tests.js +++ b/tests/tests.js @@ -19,7 +19,7 @@ * */ -/* globals Camera, resolveLocalFileSystemURL, FileEntry, CameraPopoverOptions, LocalFileSystem, MSApp */ +/* globals Camera, resolveLocalFileSystemURL, FileEntry, CameraPopoverOptions, LocalFileSystem */ /* eslint-env jasmine */ exports.defineAutoTests = function () { @@ -433,15 +433,7 @@ exports.defineManualTests = function (contentEl, createActionButton) { '

' + 'Expected result: Remove image from library.
Status box will show "FileEntry.remove success:["OK"]'; - // We need to wrap this code due to Windows security restrictions - // see http://msdn.microsoft.com/en-us/library/windows/apps/hh465380.aspx#differences for details - if (window.MSApp && window.MSApp.execUnsafeLocalFunction) { - MSApp.execUnsafeLocalFunction(function () { - contentEl.innerHTML = info_div + options_div + getpicture_div + test_procedure + inputs_div + actions_div; - }); - } else { - contentEl.innerHTML = info_div + options_div + getpicture_div + test_procedure + inputs_div + actions_div; - } + contentEl.innerHTML = info_div + options_div + getpicture_div + test_procedure + inputs_div + actions_div; const elements = document.getElementsByClassName('testInputTag'); const listener = function (e) { diff --git a/www/Camera.js b/www/Camera.js index ecc75e472..5c0627a33 100644 --- a/www/Camera.js +++ b/www/Camera.js @@ -114,14 +114,8 @@ for (const key in Camera) { * __Supported Platforms__ * * - Android - * - BlackBerry * - Browser - * - Firefox - * - FireOS * - iOS - * - Windows - * - WP8 - * - Ubuntu * * More examples [here](#camera-getPicture-examples). Quirks [here](#camera-getPicture-quirks). *