IT tutorials
 
Mobile
 

Windows Phone 8 : Creating a Photos Extras Application (part 2) - An Edge-Tracing Extras Application

2/22/2014 2:28:34 AM
- Windows 10 Product Activation Keys Free 2019 (All Versions)
- How To Bypass Torrent Connection Blocking By Your ISP
- How To Install Actual Facebook App On Kindle Fire

2. An Edge-Tracing Extras Application

This section looks at building an Extras application that converts an image to a line drawing by tracing its edges, and then allows the user to save the image to the Saved Pictures folder in the Pictures Hub. The example app for this section is located in the WPUnleashed.ExtrasExample project in the downloadable sample code.

When your app is selected from the Extras menu, your app is launched, and the main page of your app has its query string populated with a string identifier named token. The token value is then used to retrieve the image from the phone’s image library, as shown later in this section.

To hasten the pace of development and to enable testing of your app without deploying to a Windows Phone device, it is useful to feed your app with a dummy image so that it does not have to rely on the Pictures Hub.

In the following excerpt from MainPage.xaml.cs, you see how to detect whether the app is executing on the emulator using the Microsoft.Devices.Environment.DeviceType property. If so, substitute the image, which would otherwise be supplied by the picture viewer, with a sample image stored as an embedded resource.

Conversely, if not executing within the emulator, attempt to extract the token from the page’s query string, as shown:

protected override void OnNavigatedTo(NavigationEventArgs e)
{
    base.OnNavigatedTo(e);

    /* Equivalent to: Environment.DeviceType == DeviceType.Emulator */
    if (EnvironmentValues.UsingEmulator)
    {
        ProcessTestImage();
    }
    else
    {
        string token;
        if (NavigationContext.QueryString.TryGetValue("token", out token))
        {
            ProcessImageUsingToken(token);
        }
        else
        {
            if (image.Source == null)
            {
                photoChooserTask.Show();
            }
        }
    }
}

When the query string does not contain a token, it indicates that the app has been launched from either the Start Experience (if the app has been pinned there), or from the Application List.


Note

It is a Windows Phone Marketplace certification requirement that if your Extras application is launched from the application list (not from the Extras menu) it must invoke the PhotoChooser task to allow the user to select an image or take a photo. Conversely, it is also a requirement that if your app is launched from the Extras menu, it should not require further image selection steps.


To retrieve an image from the phone’s image library, we leverage the XNA framework—in particular, the Microsoft.Xna.Framework.Media.MediaLibrary class located in the Microsoft.Xna.Framework assembly.

The XNA MediaLibrary class is used to retrieve the image with the specified token. The MediaLibrary class implements IDisposable, and hence it is wrapped in a using statement, as shown in the following excerpt:

void ProcessImageUsingToken(string token)
{
    imageName = token;

    Picture picture;
    using (MediaLibrary mediaLibrary = new MediaLibrary())
    {
        picture = mediaLibrary.GetPictureFromToken(token);
    }
    Stream imageStream = picture.GetImage();
    ProcessImage(imageStream);
}

When the image Stream has been retrieved, the ProcessImage method is called, which creates a BitmapImage object. The BitmapImage is assigned to an Image control on the page (see Figure 3).

Image

FIGURE 3 The sample Extras application.

The image is displayed until processing has completed. Then the Image control’s Source property is assigned to the processed image in the HandleEdgesFound method.

The ProcessImage method creates a new BitmapImage object and passes it to the BitmapUtility.FindEdgesUsingSobel method (see Listing 2).


Note

The CreateOptions property of the BitmapImage class allows you to specify when the image is to be created. By default, the value is BitmapCreationOptions.BackgroundCreation, which causes the image to be created on a background thread and frees the UI thread from processing the image.

Because we require the BitmapImage immediately, the CreateOptions property is explicitly set to BitmapCreateOptions.None.


The saveCommand is disabled by setting the canSave flag to false.

LISTING 2. MainPage.ProcessImage Method


void ProcessImage(Stream imageStream)
{
    BitmapImage sourceImage = new BitmapImage();
    sourceImage.CreateOptions = BitmapCreateOptions.None;
    sourceImage.SetSource(imageStream);
    image.Source = sourceImage;

    canSave = false;
    saveCommand.RaiseCanExecuteChanged();

    progressBar.Visibility = Visibility.Visible;
    BitmapUtility.FindEdgesUsingSobel(sourceImage, HandleEdgesFound);
}


The custom BitmapUtility class contains a FindEdgesUsingSobel method, which attempts to reduce an image to a line drawing by locating the distinct edges within the image. Unlike Silverlight for the browser, Windows Phone does not support UIElement pixel shader effects. You therefore must either switch to Direct3D, which supports pixel shaders, or resort to processing the image in code, which is unfortunately much slower than using a custom pixel shader.

To allow the app to remain responsive during processing, the image is processed asynchronously using a thread from the thread pool. When processing completes, the resultAction is invoked on the UI thread using the default Dispatcher, as shown:

public static void FindEdgesUsingSobel(BitmapSource bitmapSource,
    Action<ResultEventArgs<WriteableBitmap>> resultAction)
{
    ArgumentValidator.AssertNotNull(bitmapSource, "bitmapSource");
    ArgumentValidator.AssertNotNull(resultAction, "resultAction");

    WriteableBitmap sourceBitmap = new WriteableBitmap(bitmapSource);
    int[] sourcePixels = sourceBitmap.Pixels;
    int imageWidth = bitmapSource.PixelWidth;
    int imageHeight = bitmapSource.PixelHeight;

    ThreadPool.QueueUserWorkItem(
        delegate
        {
            try
            {
                int[] resultPixels = FindEdgesUsingSobelCore(
                                        sourcePixels,
                                        imageWidth,
                                        imageHeight);
                Deployment.Current.Dispatcher.BeginInvoke(
                    delegate
                    {
                        WriteableBitmap resultBitmap
                            = new WriteableBitmap(bitmapSource);

                        for (int i = 0; i < resultPixels.Length; i++)
                        {
                            resultBitmap.Pixels[i] = resultPixels[i];
                        }

                        resultAction(new ResultEventArgs<WriteableBitmap>(
                                               resultBitmap));
                    });

            }
            catch (Exception ex)
            {
                resultAction(new ResultEventArgs<WriteableBitmap>(null, ex));
            }
        });
}

The FindEdgesUsingSobelCore method is where the main processing takes place. FindEdgesUsingSobelCore produces an int array representing the image pixels of the traced image. On completion, these pixels are copied to a new WriteableBitmap and returned to the caller via the resultAction.

A Sobel filter is implemented within the FindEdgesUsingSobelCore method. The method produces either white or black pixels, depending on a pixel’s intensity and that of its neighbors. See the following excerpt:

static int[] FindEdgesUsingSobelCore(
    int[] sourcePixels, int imageWidth, int imageHeight)
{
    /* Sobel Matrices */
    int[,] gx = new[,] { { -1, 0, 1 }, { -2, 0, 2 }, { -1, 0, 1 } };
    int[,] gy = new[,] { { 1, 2, 1 }, { 0, 0, 0 }, { -1, -2, -1 } };

    int[] resultPixels = new int[sourcePixels.Length];

    for (int i = 1; i < imageHeight - 1; i++)
    {
        for (int j = 1; j < imageWidth - 1; j++)
        {
            float newX = 0;
            float newY = 0;
            float c;
            for (int hw = -1; hw < 2; hw++)
            {
                for (int wi = -1; wi < 2; wi++)
                {
                    int row = j + wi;
                    int column = i + hw;
                    int position = column * imageWidth + row;
                    int pixel = sourcePixels[position];
                    var bytes = BitConverter.GetBytes(pixel);
                    c = (bytes[0] + bytes[1] + bytes[2]) / 3;
                    int index0 = hw + 1;
                    int index1 = wi + 1;
                    newX += gx[index0, index1] * c;
                    newY += gy[index0, index1] * c;
                }
            }

            int resultPosition = i * imageWidth + j;
            int colorIntValue;

            if (newX * newX + newY * newY > 16384 /* 128 * 128 */)
            {
                colorIntValue = ToInt(Color.White);
            }
            else
            {
                colorIntValue = ToInt(Color.Black);
            }
            resultPixels[resultPosition] = colorIntValue;
        }
    }
    return resultPixels;
}

To produce either a black or white pixel, convert a Microsoft.Xna.Framework.Color value to an integer value by combining the A,R,G, and B byte color components, as shown:

static int ToInt(Color color)
{
    return (color.A << 24) | (color.R << 16)
                | (color.G << 8) | (color.B << 0);
}

When the asynchronous portion of the FindEdgesUsingSobel method completes, the resulting WriteableBitmap is passed back to the caller within an instance of the ResultEventArgs. The ResultEventArgs contains a generic Result property, which, in this case, holds the resulting WriteableBitmap and an Error property if something goes awry:

public class ResultEventArgs<T> : EventArgs
{
    public T Result { get; private set; }
    public Exception Error { get; private set; }

    public ResultEventArgs(T result, Exception error = null)
    {
        Result = result;
        Error = error;
    }
}

The HandleEdgesFound method within the MainPage class handles the result of the FindEdgesUsingSobel method. If no errors occurred during processing, the WriteableBitmap is assigned to the Source property of the Image control:

void HandleEdgesFound(ResultEventArgs<WriteableBitmap> obj)
{
    progressBar.Visibility = Visibility.Collapsed;

    if (obj.Error != null)
    {
        MessageBox.Show("Unable to find edges.");
        return;
    }
    image.Source = obj.Result;
    canSave = true;
    saveCommand.RaiseCanExecuteChanged();
}

When processing completes, the image is displayed (see Figure 4).

Image

FIGURE 4 The sample Extras application after processing of the image is complete.

As mentioned earlier in this section, when debugging using the emulator, a dummy image is used. The location of this image is defined as a constant within the ProcessTestImage method, as shown:

void ProcessTestImage()
{
    const string testImageUrl = @"/DanielVaughan.WPUnleashed"
        + ".ExtrasExample;component/Images/TestImage.jpg";

    var imageUri = new Uri(testImageUrl, UriKind.Relative);

    using (Stream stream = Application.GetResourceStream(imageUri).Stream)
    {
        BitmapSource bitmapSource = new BitmapImage();
        bitmapSource.SetSource(stream);
        image.Source = bitmapSource;

        progressBar.Visibility = Visibility.Visible;
        BitmapUtility.FindEdgesUsingSobel(bitmapSource, HandleEdgesFound);
    }
}

When the app is launched from the Application List, rather than from the Extras menu, the app must use the PhotoChooserTask to allow the user to select an image. Accordingly, a PhotoChooserTask is defined as a field, and its Completed event is subscribed to in the page constructor, as shown:

readonly PhotoChooserTask photoChooserTask = new PhotoChooserTask();

public MainPage()
{
    InitializeComponent();
    ...
    photoChooserTask.Completed += HandlePhotoChooserTaskCompleted;
}

The HandlePhotoChooserTaskCompleted method takes the resulting image Stream and, like the ProcessImageUsingToken method shown earlier, calls the ProcessImage method, as shown:

void HandlePhotoChooserTaskCompleted(object sender, PhotoResult e)
{
    if (e.TaskResult != TaskResult.OK)
    {
        return;
    }
    imageName = e.OriginalFileName;

    if (!string.IsNullOrWhiteSpace(imageName))
    {
        int index = imageName.LastIndexOf(@"\");
        if (index >= 0)
        {
            imageName = "Unleashed_" + imageName.Substring(index + 1);
        }
    }
    else
    {
        imageName = string.Format(
            "Unleashed_{0:yyyy-MM-dd-HH-mm-ss}.jpg", DateTime.Now);
    }
    Stream imageStream = e.ChosenPhoto;
    ProcessImage(imageStream);
}

The imageName field is constructed using the original filename of the image.

 
Others
 
- Windows Phone 8 : Creating a Photos Extras Application (part 1) - Adding Your App to the Extras Menu
- Java ME Subsystem Architecture : Mean and Lean Virtual Machine
- Java ME Subsystem Architecture : Application Management Software, MIDP Push
- Java ME Subsystem Architecture : Java Applications and Symbian OS
- BlackBerry Bold 9700 and 9650 Series : Surfing the Web - Going to a Web Page
- BlackBerry Bold 9700 and 9650 Series : Surfing the Web - Web Browsing on Your BlackBerry
- Windows Phone 8 : Windows Phone Toolkit (part 6) - WrapPanel Layout Container
- Windows Phone 8 : Windows Phone Toolkit (part 5) - CustomMessageBox
- Windows Phone 8 : Windows Phone Toolkit (part 4) - ToggleSwitch Control, ExpanderView Control, PhoneTextBox Control
- Windows Phone 8 : Windows Phone Toolkit (part 3) - ListPicker Control, LongListSelector Control
 
 
Top 10
 
- Microsoft Visio 2013 : Adding Structure to Your Diagrams - Finding containers and lists in Visio (part 2) - Wireframes,Legends
- Microsoft Visio 2013 : Adding Structure to Your Diagrams - Finding containers and lists in Visio (part 1) - Swimlanes
- Microsoft Visio 2013 : Adding Structure to Your Diagrams - Formatting and sizing lists
- Microsoft Visio 2013 : Adding Structure to Your Diagrams - Adding shapes to lists
- Microsoft Visio 2013 : Adding Structure to Your Diagrams - Sizing containers
- Microsoft Access 2010 : Control Properties and Why to Use Them (part 3) - The Other Properties of a Control
- Microsoft Access 2010 : Control Properties and Why to Use Them (part 2) - The Data Properties of a Control
- Microsoft Access 2010 : Control Properties and Why to Use Them (part 1) - The Format Properties of a Control
- Microsoft Access 2010 : Form Properties and Why Should You Use Them - Working with the Properties Window
- Microsoft Visio 2013 : Using the Organization Chart Wizard with new data
programming4us programming4us
 
Popular tags
 
Video Tutorail Microsoft Access Microsoft Excel Microsoft OneNote Microsoft PowerPoint Microsoft Project Microsoft Visio Microsoft Word Active Directory Biztalk Exchange Server Microsoft LynC Server Microsoft Dynamic Sharepoint Sql Server Windows Server 2008 Windows Server 2012 Windows 7 Windows 8 Adobe Indesign Adobe Flash Professional Dreamweaver Adobe Illustrator Adobe After Effects Adobe Photoshop Adobe Fireworks Adobe Flash Catalyst Corel Painter X CorelDRAW X5 CorelDraw 10 QuarkXPress 8 windows Phone 7 windows Phone 8 BlackBerry Android Ipad Iphone iOS