Fran├žais English

Developping a Lens app for Windows Phone 8

Tags: Windows Phone 8, WinRT, WP8, .NET 4.5

 

Some WP devices are known for their excellent camera (*hum* Lumia 920 *hum*), so let’s play a bit with a feature introduced with Windows Phone 8 : the camera lenses.

Basically, it is a software that can be called from the camera Lens page and that will (most probably) process images taken with the camera in a certain way. It can be effect like black/white, panorama composition,etc…. In fact, it is not especially different from a regular app, though. It still appears on the application list of your phone, and can be launched like any other app.

Declaring the app as Lens

 

In the app manifest, you must add those two capabilities : “ID_CAP_ISV_CAMERA” and “ID_CAP_MEDIALIB_PHOTO” so you can have access to the camera and the Medial Library to save images.

Open now the manifest in XML and, after the Tokens element, declare your app as Lens app with :

<Extensions>
  <Extension ExtensionName="Camera_Capture_App" ConsumerID="{5B04B775-356B-4AA0-AAF8-6491FFEA5631}" TaskID="_default" />
</Extensions>

 

One more thing to do, is to add the icon that will appears in the Lens page. You must add 3 icons images in the Assets directory :

  • Lens.Screen-720p.png (173 x 173)
  • Lens.Screen-WVGA.png (259 x 259)
  • Lens.Screen-WXGA.png (277 x 277)

 

If you deploy the app now and go to the Lens page, you’ll see the icon :

When your app is called from the Lens page, the OS adds “?Action=ViewfinderLaunch” to the start uri of your app (example : “Mainpage.xaml?Action=ViewfinderLaunch”. That is how you can detect if your app was launched from the Lens page or not.

 

If you want the user to land on different page depending of launched from Lens page or from start menu, you can do it using a custom UriMapper. For instance :

class CustomUriMapper : UriMapperBase
    {
        public override Uri MapUri(Uri uri)
        {
            string tempUri = uri.ToString();
            if (tempUri.Contains("ViewfinderLaunch"))
            {
                return new Uri("/LensPage.xaml", UriKind.Relative);
            }
            else
            {
                return uri;
            }
        }
    }

 

This mapper will redirect to the page LensPage.xaml if coming from the Lens page. To activate this wrapper, you can put it in the InitializePhoneApplication method in App.cs :

RootFrame.UriMapper = new CustomUriMapper();

You need now to create a new page in your project called LensPage.xaml.

Lens exemple

Now we have a Lens, app, but it doesn’t to anything…Let’s do something about that.

The example I’ll show here is a Lens app where you can see the camera feed (nothing revolutionary) and can draw on it. Then you can take a snapshot.

For the drawing part, I’ll use the InkPresenter control for the drawing, and the WriteableBitmapEx Nuget package for the image processing.

My LensPage.xaml will look like this :

<phone:PhoneApplicationPage x:Class="DrawLens.LensPage"
                            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                            xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
                            xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
                            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
                            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
                            FontFamily="{StaticResource PhoneFontFamilyNormal}"
                            FontSize="{StaticResource PhoneFontSizeNormal}"
                            Foreground="{StaticResource PhoneForegroundBrush}"
                            SupportedOrientations="Landscape"
                            Orientation="Landscape"
                            mc:Ignorable="d"
                            shell:SystemTray.IsVisible="True">

    <!--LayoutRoot is the root grid where all page content is placed-->
    <Grid x:Name="LayoutRoot"
          Background="Transparent">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto"></ColumnDefinition>
            <ColumnDefinition Width="*"></ColumnDefinition>
        </Grid.ColumnDefinitions>

        <StackPanel Orientation="Vertical"
                    x:Name="ButtonsSP"
                    Visibility="Collapsed">
            <Button Content="Clear"
                    Margin="10,10"
                    Width="100"
                    Click="Clear_OnClick"></Button>
            <Button Content="Save"
                    Width="100"
                    Click="Save_OnClick">
            </Button>
        </StackPanel>
        <TextBlock Text="Processing..."
                   Visibility="Visible"
                   x:Name="ProcessingLabel"></TextBlock>
        <InkPresenter Width="640"
                      Height="480"
                      MouseLeftButtonDown="TheInkPresenter_MouseLeftButtonDown"
                      LostMouseCapture="TheInkPresenter_LostMouseCapture"
                      MouseMove="TheInkPresenter_MouseMove"
                      x:Name="TheInkPresenter"
                      Grid.Column="1">
            <InkPresenter.Background>
                <VideoBrush x:Name="CameraBrush"></VideoBrush>
            </InkPresenter.Background>
        </InkPresenter>
    </Grid>

</phone:PhoneApplicationPage>

 

So I have two columns. One with the buttons Clear and Save and the other with an InkPresenter that has a video brush. I will stream camera feed to this brush. I will not explain how the InkPresenter is drawing the strokes, it is not the purpose of this article and it is explained in the InkPresenter link above.

 

First, when the page is loaded, we initialize the camera and redirect the feed to the InkPresenter videobrush background :

private PhotoCamera _camera;

public LensPage()
{
  InitializeComponent();

  this.Loaded += LensPage_Loaded;
}

void LensPage_Loaded(object sender, RoutedEventArgs e)
{
  if (PhotoCamera.IsCameraTypeSupported(CameraType.Primary))
  {
       // Initialize camera, and show buttons only when camera ready
       _camera = new PhotoCamera(CameraType.Primary);
       // Event launched when the camera as taken a picture
       _camera.CaptureImageAvailable += _camera_CaptureImageAvailable;
        CameraBrush.SetSource(_camera);
  }

}

 

When the user press on the Save button, I capture an image:

private void Save_OnClick(object sender, RoutedEventArgs e)  
{
  _camera.CaptureImage();
}

When the image is captured, the camera CaptureImageAvailable event is launched. There you get the bitmap stream.

I will make a bitmap from that stream. Then I’ll take all the strokes data of the InkPresenter and redraw those strokes on the bitmap (The DrawLineBresenham method in the code is based on the WritebeableBitmapEx one and allow to draw lines thicker than 1 pixel. See source code at the end of the post). As the InkPresenter size is different than the size of the image, I compute the magnification ratio by dividing the image size by the InkPresenter size.

After drawing all the strokes, I save the bitmap to the Media Library.

Enough words, enter code :

/// <summary>
/// Called when image is available
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void _camera_CaptureImageAvailable(object sender, ContentReadyEventArgs e)
{
    Dispatcher.BeginInvoke(() => CreateAndSave(e.ImageStream));
}
/// <summary>
/// Process the image
/// </summary>
/// <param name="stream"></param>
private void CreateAndSave(Stream stream)
{
    // Get bitmap from stream
    var _bitmap = new WriteableBitmap(1, 1);

    _bitmap = _bitmap.FromStream(stream);

    var width = _bitmap.PixelWidth;

    var height = _bitmap.PixelHeight;

    // Get ration between the inkpresenter size and the bitmap size
    var ratiox = width / TheInkPresenter.Width;

    var ratioy = height / TheInkPresenter.Height;

    // Draw each strokes on the bitmap
    foreach (var stroke in TheInkPresenter.Strokes)
    {
        var x1 = stroke.StylusPoints[0].X * ratiox;
        var y1 = stroke.StylusPoints[0].Y * ratioy;

        foreach (var point in stroke.StylusPoints.Skip(1))
        {
            var x2 = point.X * ratiox;

            var y2 = point.Y * ratioy;

            DrawLineBresenham(_bitmap, (int)x1, (int)y1, (int)x2, (int)y2, Colors.Red, (int)ratiox, (int)ratioy);

            x1 = x2;
            y1 = y2;
        }
    }
    // Save into library
    _bitmap.SaveToMediaLibrary("DrawLensGenerated.jpg");


    MessageBox.Show("Image saved !");
}

A pic of the lens in action :

And the result image :

 

The source code is here

Comments powered by Disqus