Apache Cordova
  1. Apache Cordova
  2. CB-570

WP7 takePicture DATA_URL Not Resizing Image

    Details

    • Type: Bug Bug
    • Status: Resolved
    • Priority: Major Major
    • Resolution: Fixed
    • Affects Version/s: 1.6.1
    • Fix Version/s: 1.9.0
    • Component/s: WP7 (defunct)
    • Labels:
      None
    • Environment:

      Cordova 1.6.1, VS.NET 2010, WP7.5 emulators

      Description

      The code in Camera.cs that should resize the picture file before returning the base64 encoded string has not been completed as of 1.6.1. The result is that the full 5 megapixel or 8 megapixel file gets base64 encoded and returned. Das ist vady badst yah! I wrote some code that uses a solid approach to resizing the image and I'll share it here so that Jesse can restructure it a bit using forms that match the rest of the project. This code works great and it was used in basically this same format on another Silverlight app that has been in the WP7 Marketplace for over a year with 8,000 installs and no errors with this code. Enjoy!

      /*
      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;

      namespace WP7CordovaClassLib.Cordova.Commands
      {
      public class Camera : BaseCommand
      {

      /// <summary>
      /// Return base64 encoded string
      /// </summary>
      private const int DATA_URL = 0;

      /// <summary>
      /// Return file uri
      /// </summary>
      private const int FILE_URI = 1;

      /// <summary>
      /// Choose image from picture library
      /// </summary>
      private const int PHOTOLIBRARY = 0;

      /// <summary>
      /// Take picture from camera
      /// </summary>

      private const int CAMERA = 1;

      /// <summary>
      /// Choose image from picture library
      /// </summary>
      private const int SAVEDPHOTOALBUM = 2;

      /// <summary>
      /// Take a picture of type JPEG
      /// </summary>
      private const int JPEG = 0;

      /// <summary>
      /// Take a picture of type PNG
      /// </summary>
      private const int PNG = 1;

      /// <summary>
      /// Folder to store captured images
      /// </summary>
      private const string isoFolder = "CapturedImagesCache";

      /// <summary>
      /// Represents captureImage action options.
      /// </summary>
      [DataContract]
      public class CameraOptions
      {
      /// <summary>
      /// Source to getPicture from.
      /// </summary>
      [DataMember(IsRequired = false, Name = "sourceType")]
      public int PictureSourceType

      { get; set; }

      /// <summary>
      /// Format of image that returned from getPicture.
      /// </summary>
      [DataMember(IsRequired = false, Name = "destinationType")]
      public int DestinationType { get; set; }

      /// <summary>
      /// Quality of saved image
      /// </summary>
      [DataMember(IsRequired = false, Name = "quality")]
      public int Quality

      { get; set; }


      /// <summary>
      /// Height in pixels to scale image
      /// </summary>
      [DataMember(IsRequired = false, Name = "targetHeight")]
      public int TargetHeight { get; set; }

      /// <summary>
      /// Width in pixels to scale image
      /// </summary>
      [DataMember(IsRequired = false, Name = "targetWidth")]
      public int TargetWidth

      { get; set; }

      /// <summary>
      /// Creates options object with default parameters
      /// </summary>
      public CameraOptions()

      { this.SetDefaultValues(new StreamingContext()); }

      /// <summary>
      /// Initializes default values for class fields.
      /// Implemented in separate method because default constructor is not invoked during deserialization.
      /// </summary>
      /// <param name="context"></param>
      [OnDeserializing()]
      public void SetDefaultValues(StreamingContext context)

      { PictureSourceType = CAMERA; DestinationType = FILE_URI; Quality = 80; TargetHeight = -1; TargetWidth = -1; }

      }

      /// <summary>
      /// Used to open photo library
      /// </summary>
      PhotoChooserTask photoChooserTask;

      /// <summary>
      /// Used to open camera application
      /// </summary>
      CameraCaptureTask cameraTask;

      /// <summary>
      /// Camera options
      /// </summary>
      CameraOptions cameraOptions;

      public void takePicture(string options)
      {
      try

      { this.cameraOptions = String.IsNullOrEmpty(options) ? new CameraOptions() : JSON.JsonHelper.Deserialize<CameraOptions>(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 += onTaskCompleted; cameraTask.Show(); }

      else
      {
      if ((cameraOptions.PictureSourceType == PHOTOLIBRARY) || (cameraOptions.PictureSourceType == SAVEDPHOTOALBUM))

      { photoChooserTask = new PhotoChooserTask(); photoChooserTask.Completed += onTaskCompleted; photoChooserTask.Show(); }

      else

      { DispatchCommandResult(new PluginResult(PluginResult.Status.NO_RESULT)); }

      }

      }

      public void onTaskCompleted(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)

      { Byte[] bytFile; Byte[] bytFileResized; int intResult = 0; long lngLength; BitmapImage objBitmap; System.IO.MemoryStream objBitmapStream; System.IO.MemoryStream objBitmapStreamResized = new System.IO.MemoryStream(); WriteableBitmap objWB; //Get the JPEG bytes from the source chosen photo, probably 5 megapixel! lngLength = e.ChosenPhoto.Length; bytFile = new Byte[lngLength]; intResult = e.ChosenPhoto.Read(bytFile, 0, (int)lngLength); //Convert the bytes to a WriteableBitmap objBitmap = new BitmapImage(); objBitmapStream = new System.IO.MemoryStream(bytFile); objBitmap.SetSource(objBitmapStream); objWB = new WriteableBitmap(objBitmap); //Resize the image System.Windows.Media.Imaging.Extensions.SaveJpeg(objWB, objBitmapStreamResized, cameraOptions.TargetWidth, cameraOptions.TargetHeight, 0, cameraOptions.Quality); //Convert the resized stream to a byte array. lngLength = objBitmapStreamResized.Length; bytFileResized = new Byte[lngLength]; //-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(bytFileResized, 0, (int)lngLength); //Convert the byte array to base64 string. imagePathOrContent = Convert.ToBase64String(bytFileResized); }

      else

      { 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;
      }
      }

      /// <summary>
      /// Returns image content in a form of base64 string
      /// </summary>
      /// <param name="stream">Image stream</param>
      /// <returns>Base64 representation of the image</returns>
      private string GetImageContent(Stream stream)

      { int streamLength = (int)stream.Length; byte[] fileData = new byte[streamLength + 1]; stream.Read(fileData, 0, streamLength); stream.Close(); return Convert.ToBase64String(fileData); }

      /// <summary>
      /// Saves captured image in isolated storage
      /// </summary>
      /// <param name="imageFileName">image file name</param>
      /// <returns>Image path</returns>
      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; }

      }

      }
      }

        Activity

        Hide
        Simon MacDonald added a comment -

        I'm pretty sure that Android does not support width/height on DATA_URL's. We should check this from cross platform inconsistencies.

        Show
        Simon MacDonald added a comment - I'm pretty sure that Android does not support width/height on DATA_URL's. We should check this from cross platform inconsistencies.
        Hide
        Alan Neveu added a comment -

        It is working just fine for my app. AN

        Sent from my iPhone

        Show
        Alan Neveu added a comment - It is working just fine for my app. AN Sent from my iPhone
        Show
        Herm Wong added a comment - https://git-wip-us.apache.org/repos/asf?p=incubator-cordova-wp7.git;a=commit;h=c7a075e3b1b07f6931f030219272c6fc9844528d

          People

          • Assignee:
            Herm Wong
            Reporter:
            Alan Neveu
          • Votes:
            0 Vote for this issue
            Watchers:
            3 Start watching this issue

            Dates

            • Created:
              Updated:
              Resolved:

              Time Tracking

              Estimated:
              Original Estimate - 1h
              1h
              Remaining:
              Remaining Estimate - 1h
              1h
              Logged:
              Time Spent - Not Specified
              Not Specified

                Development