Wednesday, December 29, 2010

Sending Windows Phone Screenshots in an Email

My Silverlight MVP friend Laurent Bugnion blogged about how to take a screenshot from within a Silverlight Windows Phone application. As he wrote this could be helpful for customer feedback and other purposes. To accomplish this, the Silverlight UIElement is rendered to a WriteableBitmap, which can then be saved to the device's Media Library (Pictures Hub). Afterwards it could be send in an email from the Pictures Hub or the email client.
Wouldn't it be even nicer if the user could send an email with the screenshot directly from the app? I show you how.

The Email Task
The only way to send an email from a Windows Phone app is through the EmailComposeTask. This task provides properties for the Subject, Body, CC, Addressee (To) and launches the email client. In the last blog post I showed how I use it to send customer feedback with system information from my apps.
Unfortunately the EmailComposeTask doesn't provide a way to pass an attachment and therefore it's not possible to attach the screenshot to the email. Fortunately there's a way to send binary data as text inside an email. This is where Base64 encoding comes into play.

Embedding the Screenshot 
Base64 encoding transforms binary data into ASCII text and thereby makes it possible to send binary data as plain old text. This way we can convert the JPEG screenshot to Base64 text and set it as the Body of the EmailComposeTask. The Body text length is limited and I experimented a bit with different sizes to find the limitation. It seems like the Body text is limited to less than 32k Unicode characters. A JPEG encoded screenshot of a whole Windows Phone page is too large for this (480 x 800 pixels). That's why the screenshot is rendered downscaled if the element is too large and a lower compression quality is used for the JPEG encoding.
private static void SendEmailScreenshot(FrameworkElement element)
{
   // Render the element at the maximum possible size
   ScaleTransform transform = null;
   if (element.ActualWidth * element.ActualHeight > 240 * 400)
   {
      // Calculate a uniform scale with the minimum possible size
      var scaleX = 240.0 / element.ActualWidth;
      var scaleY = 400.0 / element.ActualHeight;
      var scale = scaleX < scaleY ? scaleX : scaleY;
      transform = new ScaleTransform { ScaleX = scale, ScaleY = scale };
   }
   var wb = new WriteableBitmap(element, transform);

   using (var memoryStream = new MemoryStream())
   {
      // Encode the screenshot as JPEG with a quality of 60%
      wb.SaveJpeg(memoryStream, wb.PixelWidth, wb.PixelHeight, 0, 60);
      memoryStream.Seek(0, SeekOrigin.Begin);

      // Convert binary data to Base64 string
      var bytes = memoryStream.ToArray();
      var base64String = Convert.ToBase64String(bytes);

      // Invoke email task
      var emailComposeTask = new EmailComposeTask
                              {
                                 Subject = "Screenshot from my app",
                                 Body = base64String,
                                 To = "foo@bar.com",
                              };
      emailComposeTask.Show();
   }
}
The element is downscaled uniformly if it's larger than a certain size. This is done with a ScaleTransform directly when the Silverlight (vector) element is rendered to a WriteableBitmap. Afterwards the bitmap is encoded as JPEG at a quality of 60%. This low compression quality reduces the size of the JPEG image significantly, but the image is still usable. The binary data is then transformed to Base64 text with the Convert.ToBase64String method. Finally the EmailComposeTask is created, the properties are set and the email client is opened.

Decoding the Screenshot
The email will contain the Base64 encoded image. Base64 encoded data typically looks like this:
/9j/4AAQSkZJRgABAQEBBgEGAAD/2wBDAA0JCgsKCA0LCgsODg0PEyAVExISEyccHhcgLikxMC4pLSwzOko
+MzZGNywtQFdBRkxOUlNSMj5aYVpQYEpRUk//2wBDAQ4ODhMREyYVFSZPNS01T09PT09PT09PT09PT09PT09PT09PT
09PT09PT09PT09PT09PT09PT09PT09PT09PT0//wAARCAFsAPADASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAA
AAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM
2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJW
Wl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAw

...

The opposite operation of the ToBase64String method is the Convert.FromBase64String method. So it's easy to write a little tool that extracts the email content, converts it back to JPEG data and saves it as a file for example. Probably it's sufficient to use an online Base64 encoder like this one where the Base64 string can be pasted and the tool will create a binary file which can be downloaded.

The result might look like the below screenshot from the Pictures Lab app. As you can see, the half downscaled version with the 60% quality is usable and even the small text can be read without problems.


Future
There's also room for improvement left. More modern image compression algorithms like they are used in the Hipix or WebP formats can produce better results at lower size. Another way to reduce the size could be a post process step where every odd pixel or line is removed. This technique was described in the Kill Pixel Shader blog post.
Needless to say that the demonstrated, well-known Base64 encoding can be used to send any small binary data inside an email from a Windows Phone app. So it's also possible to send short audio clips, binary serialized data or other data.

Conclusion
For more complex tracking and feedback mechanisms an image upload webservice might be the better way. However there are many scenarios with simple tasks where a webservice isn't feasible and the shown technique can be helpful.

4 comments:

  1. Now in Windows Phone 8, you can:

    void SendImageAsAttachment(Stream imageStream)
    {
    MediaLibrary lib = new MediaLibrary();
    Picture picture = lib.SavePicture("test", imageStream);
    ShareMediaTask task = new ShareMediaTask();
    task.FilePath = picture.GetPath();
    task.Show();
    }

    ReplyDelete
    Replies
    1. void SendImageAsAttachment(Stream imageStream)
      {
      MediaLibrary lib = new MediaLibrary();
      Picture picture = lib.SavePicture("test", imageStream);
      ShareMediaTask task = new ShareMediaTask();
      task.FilePath = picture.GetPath();
      task.Show();
      }

      when i add this code in my app i get a error message "using Microsoft.Xna.Framework.Media" not support Getpath() method so how i can use above code in my program

      Delete
  2. To send emails with any kind of attachment from a WP7 or WP8 app (soon from WinRT too) directly to Microsoft Hotmail SMTP server , you can use this component: http://www.windowsphonegeek.com/marketplace/components/livemailmessage

    ReplyDelete
  3. We just updated MailMessage Library for wp to support GMail and custom SMTP server too :)

    ReplyDelete