From 69525b9b98a8f94ee99431d9cda519e306ecfa1e Mon Sep 17 00:00:00 2001 From: Benn Mapes Date: Fri, 7 Jun 2013 11:37:25 -0700 Subject: [PATCH] first pass at wp7 support --- plugin.xml | 11 ++ src/wp7/Camera.cs | 490 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 501 insertions(+) create mode 100644 src/wp7/Camera.cs diff --git a/plugin.xml b/plugin.xml index 81c450a..7162678 100644 --- a/plugin.xml +++ b/plugin.xml @@ -52,5 +52,16 @@ id="org.apache.cordova.core.CameraLauncher" + + + + + + + + + + + diff --git a/src/wp7/Camera.cs b/src/wp7/Camera.cs new file mode 100644 index 0000000..5ff8045 --- /dev/null +++ b/src/wp7/Camera.cs @@ -0,0 +1,490 @@ +/* + Licensed 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. +*/ + +using System; +using System.Net; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Documents; +using System.Windows.Ink; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Animation; +using System.Collections.Generic; +using Microsoft.Phone.Tasks; +using System.Runtime.Serialization; +using System.IO; +using System.IO.IsolatedStorage; +using System.Windows.Media.Imaging; +using Microsoft.Phone; +using Microsoft.Xna.Framework.Media; +using System.Diagnostics; + +namespace WPCordovaClassLib.Cordova.Commands +{ + public class Camera : BaseCommand + { + + /// + /// Return base64 encoded string + /// + private const int DATA_URL = 0; + + /// + /// Return file uri + /// + private const int FILE_URI = 1; + + /// + /// Choose image from picture library + /// + private const int PHOTOLIBRARY = 0; + + /// + /// Take picture from camera + /// + + private const int CAMERA = 1; + + /// + /// Choose image from picture library + /// + private const int SAVEDPHOTOALBUM = 2; + + /// + /// Take a picture of type JPEG + /// + private const int JPEG = 0; + + /// + /// Take a picture of type PNG + /// + private const int PNG = 1; + + /// + /// Folder to store captured images + /// + private const string isoFolder = "CapturedImagesCache"; + + /// + /// Represents captureImage action options. + /// + [DataContract] + public class CameraOptions + { + /// + /// Source to getPicture from. + /// + [DataMember(IsRequired = false, Name = "sourceType")] + public int PictureSourceType { get; set; } + + /// + /// Format of image that returned from getPicture. + /// + [DataMember(IsRequired = false, Name = "destinationType")] + public int DestinationType { get; set; } + + /// + /// Quality of saved image + /// + [DataMember(IsRequired = false, Name = "quality")] + public int Quality { get; set; } + + /// + /// Controls whether or not the image is also added to the device photo album. + /// + [DataMember(IsRequired = false, Name = "saveToPhotoAlbum")] + public bool SaveToPhotoAlbum { get; set; } + + /// + /// Ignored + /// + [DataMember(IsRequired = false, Name = "correctOrientation")] + public bool CorrectOrientation { get; set; } + + + + /// + /// Ignored + /// + [DataMember(IsRequired = false, Name = "allowEdit")] + public bool AllowEdit { get; set; } + + /// + /// Height in pixels to scale image + /// + [DataMember(IsRequired = false, Name = "encodingType")] + public int EncodingType { get; set; } + + /// + /// Height in pixels to scale image + /// + [DataMember(IsRequired = false, Name = "mediaType")] + public int MediaType { get; set; } + + + /// + /// Height in pixels to scale image + /// + [DataMember(IsRequired = false, Name = "targetHeight")] + public int TargetHeight { get; set; } + + + /// + /// Width in pixels to scale image + /// + [DataMember(IsRequired = false, Name = "targetWidth")] + public int TargetWidth { get; set; } + + /// + /// Creates options object with default parameters + /// + public CameraOptions() + { + this.SetDefaultValues(new StreamingContext()); + } + + /// + /// Initializes default values for class fields. + /// Implemented in separate method because default constructor is not invoked during deserialization. + /// + /// + [OnDeserializing()] + public void SetDefaultValues(StreamingContext context) + { + PictureSourceType = CAMERA; + DestinationType = FILE_URI; + Quality = 80; + TargetHeight = -1; + TargetWidth = -1; + SaveToPhotoAlbum = false; + CorrectOrientation = true; + AllowEdit = false; + MediaType = -1; + EncodingType = -1; + } + } + + /// + /// Used to open photo library + /// + PhotoChooserTask photoChooserTask; + + /// + /// Used to open camera application + /// + CameraCaptureTask cameraTask; + + /// + /// Camera options + /// + CameraOptions cameraOptions; + + public void takePicture(string options) + { + try + { + string[] args = JSON.JsonHelper.Deserialize(options); + // ["quality", "destinationType", "sourceType", "targetWidth", "targetHeight", "encodingType", + // "mediaType", "allowEdit", "correctOrientation", "saveToPhotoAlbum" ] + this.cameraOptions = new CameraOptions(); + this.cameraOptions.Quality = int.Parse(args[0]); + this.cameraOptions.DestinationType = int.Parse(args[1]); + this.cameraOptions.PictureSourceType = int.Parse(args[2]); + this.cameraOptions.TargetWidth = int.Parse(args[3]); + this.cameraOptions.TargetHeight = int.Parse(args[4]); + this.cameraOptions.EncodingType = int.Parse(args[5]); + this.cameraOptions.MediaType = int.Parse(args[6]); + this.cameraOptions.AllowEdit = bool.Parse(args[7]); + this.cameraOptions.CorrectOrientation = bool.Parse(args[8]); + this.cameraOptions.SaveToPhotoAlbum = bool.Parse(args[9]); + + //this.cameraOptions = String.IsNullOrEmpty(options) ? + // new CameraOptions() : JSON.JsonHelper.Deserialize(options); + } + catch (Exception ex) + { + this.DispatchCommandResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION, ex.Message)); + return; + } + + //TODO Check if all the options are acceptable + + + if (cameraOptions.PictureSourceType == CAMERA) + { + cameraTask = new CameraCaptureTask(); + cameraTask.Completed += onCameraTaskCompleted; + cameraTask.Show(); + } + else + { + if ((cameraOptions.PictureSourceType == PHOTOLIBRARY) || (cameraOptions.PictureSourceType == SAVEDPHOTOALBUM)) + { + photoChooserTask = new PhotoChooserTask(); + photoChooserTask.Completed += onPickerTaskCompleted; + photoChooserTask.Show(); + } + else + { + DispatchCommandResult(new PluginResult(PluginResult.Status.NO_RESULT)); + } + } + + } + + public void onCameraTaskCompleted(object sender, PhotoResult e) + { + if (e.Error != null) + { + DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR)); + return; + } + + switch (e.TaskResult) + { + case TaskResult.OK: + try + { + string imagePathOrContent = string.Empty; + + if (cameraOptions.DestinationType == FILE_URI) + { + // Save image in media library + if (cameraOptions.SaveToPhotoAlbum) + { + MediaLibrary library = new MediaLibrary(); + Picture pict = library.SavePicture(e.OriginalFileName, e.ChosenPhoto); // to save to photo-roll ... + } + + int orient = ImageExifHelper.getImageOrientationFromStream(e.ChosenPhoto); + int newAngle = 0; + switch (orient) + { + case ImageExifOrientation.LandscapeLeft: + newAngle = 90; + break; + case ImageExifOrientation.PortraitUpsideDown: + newAngle = 180; + break; + case ImageExifOrientation.LandscapeRight: + newAngle = 270; + break; + case ImageExifOrientation.Portrait: + default: break; // 0 default already set + } + + Stream rotImageStream = ImageExifHelper.RotateStream(e.ChosenPhoto, newAngle); + + // we should return stream position back after saving stream to media library + rotImageStream.Seek(0, SeekOrigin.Begin); + + WriteableBitmap image = PictureDecoder.DecodeJpeg(rotImageStream); + + imagePathOrContent = this.SaveImageToLocalStorage(image, Path.GetFileName(e.OriginalFileName)); + + + } + else if (cameraOptions.DestinationType == DATA_URL) + { + imagePathOrContent = this.GetImageContent(e.ChosenPhoto); + } + else + { + // TODO: shouldn't this happen before we launch the camera-picker? + DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "Incorrect option: destinationType")); + return; + } + + DispatchCommandResult(new PluginResult(PluginResult.Status.OK, imagePathOrContent)); + + } + catch (Exception) + { + DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "Error retrieving image.")); + } + break; + + case TaskResult.Cancel: + DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "Selection cancelled.")); + break; + + default: + DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "Selection did not complete!")); + break; + } + + } + + public void onPickerTaskCompleted(object sender, PhotoResult e) + { + if (e.Error != null) + { + DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR)); + return; + } + + switch (e.TaskResult) + { + case TaskResult.OK: + try + { + string imagePathOrContent = string.Empty; + + if (cameraOptions.DestinationType == FILE_URI) + { + WriteableBitmap image = PictureDecoder.DecodeJpeg(e.ChosenPhoto); + imagePathOrContent = this.SaveImageToLocalStorage(image, Path.GetFileName(e.OriginalFileName)); + } + else if (cameraOptions.DestinationType == DATA_URL) + { + imagePathOrContent = this.GetImageContent(e.ChosenPhoto); + + } + else + { + // TODO: shouldn't this happen before we launch the camera-picker? + DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "Incorrect option: destinationType")); + return; + } + + DispatchCommandResult(new PluginResult(PluginResult.Status.OK, imagePathOrContent)); + + } + catch (Exception) + { + DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "Error retrieving image.")); + } + break; + + case TaskResult.Cancel: + DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "Selection cancelled.")); + break; + + default: + DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "Selection did not complete!")); + break; + } + } + + /// + /// Returns image content in a form of base64 string + /// + /// Image stream + /// Base64 representation of the image + private string GetImageContent(Stream stream) + { + int streamLength = (int)stream.Length; + byte[] fileData = new byte[streamLength + 1]; + stream.Read(fileData, 0, streamLength); + + //use photo's actual width & height if user doesn't provide width & height + if (cameraOptions.TargetWidth < 0 && cameraOptions.TargetHeight < 0) + { + stream.Close(); + return Convert.ToBase64String(fileData); + } + else + { + // resize photo + byte[] resizedFile = ResizePhoto(stream, fileData); + stream.Close(); + return Convert.ToBase64String(resizedFile); + } + } + + /// + /// Resize image + /// + /// Image stream + /// File data + /// resized image + private byte[] ResizePhoto(Stream stream, byte[] fileData) + { + int streamLength = (int)stream.Length; + int intResult = 0; + + byte[] resizedFile; + + stream.Read(fileData, 0, streamLength); + + BitmapImage objBitmap = new BitmapImage(); + MemoryStream objBitmapStream = new MemoryStream(fileData); + MemoryStream objBitmapStreamResized = new MemoryStream(); + WriteableBitmap objWB; + objBitmap.SetSource(stream); + objWB = new WriteableBitmap(objBitmap); + + // resize the photo with user defined TargetWidth & TargetHeight + Extensions.SaveJpeg(objWB, objBitmapStreamResized, cameraOptions.TargetWidth, cameraOptions.TargetHeight, 0, cameraOptions.Quality); + + //Convert the resized stream to a byte array. + streamLength = (int)objBitmapStreamResized.Length; + resizedFile = new Byte[streamLength]; //-1 + objBitmapStreamResized.Position = 0; + //for some reason we have to set Position to zero, but we don't have to earlier when we get the bytes from the chosen photo... + intResult = objBitmapStreamResized.Read(resizedFile, 0, streamLength); + + return resizedFile; + } + + /// + /// Saves captured image in isolated storage + /// + /// image file name + /// Image path + private string SaveImageToLocalStorage(WriteableBitmap image, string imageFileName) + { + + if (image == null) + { + throw new ArgumentNullException("imageBytes"); + } + try + { + + + var isoFile = IsolatedStorageFile.GetUserStoreForApplication(); + + if (!isoFile.DirectoryExists(isoFolder)) + { + isoFile.CreateDirectory(isoFolder); + } + + string filePath = System.IO.Path.Combine("///" + isoFolder + "/", imageFileName); + + using (var stream = isoFile.CreateFile(filePath)) + { + // resize image if Height and Width defined via options + if (cameraOptions.TargetHeight > 0 && cameraOptions.TargetWidth > 0) + { + image.SaveJpeg(stream, cameraOptions.TargetWidth, cameraOptions.TargetHeight, 0, cameraOptions.Quality); + } + else + { + image.SaveJpeg(stream, image.PixelWidth, image.PixelHeight, 0, cameraOptions.Quality); + } + } + + return new Uri(filePath, UriKind.Relative).ToString(); + } + catch (Exception) + { + //TODO: log or do something else + throw; + } + } + + } +}