Visually Located

XAML and GIS

Crop and resize any image to create a lockscreen for your phone with the [RTM] Nokia Imaging SDK

Awhile back I blogged about this same topic but with the beta version of the Nokia Imaging SDK. When Nokia released the RTM of their Imaging SDK, this functionality changed. We’ll take a look at how to accomplish this same functionality with the new Imaging SDK.The good news we will be able to reuse the GetRandomImage method that picked a photo from the phones library.

private Picture GetRandomImage()
{
    var rand = new Random(DateTime.Now.Millisecond);
 
    MediaLibrary library = new MediaLibrary();
    var album = library.RootPictureAlbum;
 
    int albumIndex = rand.Next(0, album.Albums.Count - 1);
    album = album.Albums[albumIndex];
 
    var pictureIndex = rand.Next(0, album.Pictures.Count - 1);
    var picture = album.Pictures[pictureIndex];
 
    return picture;
}

We will also be able to reuse the GetCropArea method without any changes. This method decides where we need to crop the given picture so it will be the same dimensions as the phone. This method may return a size larger or smaller than the phone itself, but it will be the same ratio of width/height. The ratio is the important part as we can shrink or grow the image with the Imaging SDK.

/// <summary>
/// Returns the area needed to crop an image to the desired height and width.
/// </summary>
/// <param name="imageSize">The size of the image.</param>
/// <param name="desiredSize">The desired size to crop the image to.</param>
/// <returns></returns>
private static Rect? GetCropArea(Size imageSize, Size desiredSize)
{
    // how big is the picture compared to the phone?
    var widthRatio = desiredSize.Width / imageSize.Width;
    var heightRatio = desiredSize.Height / imageSize.Height;
 
    // the ratio is the same, no need to crop it
    if (widthRatio == heightRatio) return null;
 
    double cropWidth;
    double cropheight;
    if (widthRatio < heightRatio)
    {
        cropheight = imageSize.Height;
        cropWidth = desiredSize.Width / heightRatio;
    }
    else
    {
        cropheight = desiredSize.Height / widthRatio;
        cropWidth = imageSize.Width;
    }
 
    int left = (int)(imageSize.Width - cropWidth) / 2;
    int top = (int)(imageSize.Height - cropheight) / 2;
 
    var rect = new Windows.Foundation.Rect(left, top, cropWidth, cropheight);
    return rect;
}

Because we made the CropPicture method in a nice generic way, we will be able to reuse the signature of that method as well. We will just need to modify the implementation of it to use the new SDK. This is one of the best parts of coding to a generic platform or contracts rather than to the SDK. When the SDK breaks it’s API, your code doesn’t have to.

The logic for this method is still the same, but Nokia completely changed the classes involved. In the beta of the Imaging SDK, you worked with an EditingSession, giving the session a Stream to work with. You could create filters from the FilterFactory or just create your own implementation of IFilter.

With the new SDK, you work with an IImageProvider. There are many out of the box image providers. For our method, we need to work with a StreamImageSource. This class gives you the ability to apply filters to a Stream. To apply filters you create a FilterEffect class, and pass in the image provider.

IImageProvider imageProvider = new StreamImageSource(stream)
IFilterEffect effect = new FilterEffect(imageProvider);

Filters can be applied by setting the Filters property with a collection of filters you want to apply. For our case we only want to use the CropFilter.

Windows.Foundation.Rect? rect = GetCropArea(new Windows.Foundation.Size(picture.Width, picture.Height), desiredSize);
if (rect.HasValue)
{
    var filters = new List<IFilter>();
    filters.Add(new CropFilter(rect.Value));
    effect.Filters = filters;
}

When you want to save a new image with filters applied, you create a new IImageConsumer. For our case, we want to save a jpeg image so we’ll use the JpegRenderer. To ensure that the image is the correct size of the phone, you need to set the Size property.

JpegRenderer renderer = new JpegRenderer(effect);
 
// We went through a lot of trouble to crop this at the proper ratio
renderer.OutputOption = OutputOption.PreserveAspectRatio;
renderer.Size = desiredSize;

And when you are ready to make the new picture, simply call the RenderAsync method of the renderer.

IBuffer buffer = await renderer.RenderAsync();

Putting all of the pieces together for our method we now have our code to crop and resize any image to fit the dimensions of the phone.

/// <summary>
/// Crops a Picture to the desired size.
/// </summary>
/// <param name="picture">The Picture to crop.</param>
/// <returns>A copy of the Picture which is cropped.</returns>
public static async Task<IBuffer> CropPicture(Picture picture, Windows.Foundation.Size desiredSize)
{
    using (var stream = picture.GetImage())
    {
        using (var imageProvider = new StreamImageSource(stream))
        {
            IFilterEffect effect = new FilterEffect(imageProvider);
 
            // Get the crop area of the image, we need to ensure that
            // the image does not get skewed
            Windows.Foundation.Rect? rect = GetCropArea(new Windows.Foundation.Size(picture.Width, picture.Height), desiredSize);
            if (rect.HasValue)
            {
                var filters = new List<IFilter>();
                // Define the effects to apply
                filters.Add(new CropFilter(rect.Value));
                effect.Filters = filters;
            }
 
            using (var renderer = new JpegRenderer(effect))
            {
                renderer.OutputOption = OutputOption.PreserveAspectRatio;
                renderer.Size = desiredSize;
 
                IBuffer buffer = await renderer.RenderAsync();
 
                return buffer;
            }
        }
    }
}

And as before, we can use the GetRandomLockscreen method that was created before.

private async Task<Stream> GetRandomLockscreen()
{
    // Get the width and height of the phone
    double phoneWidth = Resolution.PhoneWidth;
    double phoneHeight = Resolution.PhoneHeight;
 
    Picture mediaPicture  = GetRandomImage();
    IBuffer croppedImage = await CropPicture(mediaPicture, new Size(phoneWidth, phoneHeight));
 
    if (croppedImage == null) return null;
 
    return croppedImage.AsStream();
}

I was really hoping the new SDK would work better in background agents, but it will still use up the 10 megs of memory that are allowed to background agents. If you have not heard, with GDR3 background agents for low memory phones has been increased to 11 megs and non-low memory phones have been increased to 20 MB. This is pretty exciting news if you are creating lockscreens from in a background agent. Just remember that low memory phones are still low memory phones.

Synching the scroll position of two LongListSelectors

I was looking at Stackoverflow and found a question asking about how to sync two LongListSelectors so that their scroll position was always the same. I thought this was so cool that it was worth sharing it with the masses.

First create a new class called MyLongListSelector. Unlike the ListBox, the LLS does not use a ScrollViewer to scroll the content. Instead, it uses a ViewportControl. We need to override the OnApplyTemplate and hook into the ViewportChanged event of the ViewportControl .

public class MyLongListSelector : LongListSelector
{
    private ViewportControl _viewport;
 
    public override void OnApplyTemplate()
    {
        base.OnApplyTemplate();
 
        _viewport = (ViewportControl)GetTemplateChild("ViewportControl");
        _viewport.ViewportChanged += OnViewportChanged;
    }
}

Within the event handler for the ViewportChanged event, we’ll set a DependencyProperty that represent the top of the scroll position.

private void OnViewportChanged(object sender, ViewportChangedEventArgs args)
{
    ScrollPosition = _viewport.Viewport.Top;
}

The ScrollPosition property will represent the top of our own scroll position. We’ll create a DependencyProperty so that other MyLLS controls can bind to it and set it from xaml.

public double ScrollPosition
{
    get { return (double)GetValue(ViewPortProperty); }
    set { SetValue(ViewPortProperty, value); }
}
 
public static readonly DependencyProperty ViewPortProperty = DependencyProperty.Register(
    "ScrollPosition", 
    typeof(double), 
    typeof(MyLongListSelector), 
    new PropertyMetadata(0d, OnViewPortChanged));

When our own ScrollPosition changes, we’ll attempt to change the Viewport of the ViewportControl. We’ll do this because the value could be changing from xaml, where another control is setting it based on it’s value. We cannot set the Viewport directly, and we cannot set the top of the Viewport. Luckily the ViewportControl does have a SetViewportOrigin method that allows us to set the top of the scroll.

private static void OnViewPortChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    var lls = (MyLongListSelector) d;
    
    if (lls._viewport.Viewport.Top.Equals(lls.ScrollPosition)) return;
 
    lls._viewport.SetViewportOrigin(new Point(0, lls.ScrollPosition));
}

Now we can place two MyLLS controls within a Grid in our page. I used the default DataboundApp from the Windows Phone 8 template and duplicated the existing LLS.

<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="*"/>
        <ColumnDefinition Width="*"/>
    </Grid.ColumnDefinitions>
    <dataBoundApp1:MyLongListSelector x:Name="MainLongListSelector" Margin="0,0,-12,0" ItemsSource="{Binding Items}" SelectionChanged="MainLongListSelector_SelectionChanged"
                                      ScrollPosition="{Binding ScrollPosition, ElementName=MainLongListSelector2, Mode=TwoWay}">
        <dataBoundApp1:MyLongListSelector.ItemTemplate>
            <DataTemplate>
                <StackPanel Margin="0,0,0,17">
                    <TextBlock Text="{Binding LineOne}" TextWrapping="Wrap" Style="{StaticResource PhoneTextExtraLargeStyle}"/>
                    <TextBlock Text="{Binding LineTwo}" TextWrapping="Wrap" Margin="12,-6,12,0" Style="{StaticResource PhoneTextSubtleStyle}"/>
                </StackPanel>
            </DataTemplate>
        </dataBoundApp1:MyLongListSelector.ItemTemplate>
    </dataBoundApp1:MyLongListSelector>
    <dataBoundApp1:MyLongListSelector x:Name="MainLongListSelector2" Grid.Column="1" Margin="0,0,-12,0" ItemsSource="{Binding Items}" 
                                      ScrollPosition="{Binding ScrollPosition, ElementName=MainLongListSelector, Mode=TwoWay}">
        <dataBoundApp1:MyLongListSelector.ItemTemplate>
            <DataTemplate>
                <StackPanel Margin="0,0,0,17">
                    <TextBlock Text="{Binding LineOne}" TextWrapping="Wrap" Style="{StaticResource PhoneTextExtraLargeStyle}"/>
                    <TextBlock Text="{Binding LineTwo}" TextWrapping="Wrap" Margin="12,-6,12,0" Style="{StaticResource PhoneTextSubtleStyle}"/>
                </StackPanel>
            </DataTemplate>
        </dataBoundApp1:MyLongListSelector.ItemTemplate>
    </dataBoundApp1:MyLongListSelector>
</Grid>

Notice that the two LLS are binding to each others ScrollPosition property and are using TwoWay binding. This allows each control to set the other controls position.

This approach unfortunately causes some choppy scrolling and causes the scrolling to abruptly stop. We can help control that by checking the ManipulationState of the ViewportControl before we attempt to change the value of the Viewport. If the ManipulationState is Idle, we can be pretty sure that we should set the value because the ViewportControl is not the one being animated. We want to make sure that we do not set the Viewport for the LLS that is being animated.

private static void OnViewPortChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    var lls = (MyLongListSelector) d;
 
    if (lls._viewport.Viewport.Top.Equals(lls.ScrollPosition)) return;
 
    if (lls._viewport.ManipulationState == ManipulationState.Idle)
    {
        lls._viewport.SetViewportOrigin(new Point(0, lls.ScrollPosition));
    }
}
With this approach we have a much smoother experience. Download a working sample.

Strongly type your settings saved in IsolatedStorageSettings

When creating an app you’ll need some way to save user settings. In Windows Phone (and Windows Store) Apps there are four possible ways to save settings.

  1. Key/Value pairs in IsolatedStorageSettings
  2. Save a file in IsolatedStorage
  3. Save values in a local database
  4. Save values in the cloud

Usually everyone starts by saving values into IsolatedStorageSettings, and for the most part, it’s probably the best way to go. If you are creating a music app, you can not use IsolatedStorageSettings. The AudioPlayerAgent simply will not get the correct setting values. If you are creating an app with a background agent, you should consider not using IsolatedStorageSettings for the same reason as before. You are less likely to have a problem with incorrect values with a normal background agent than you are when using an audio agent.

While using the IsolatedStorageSettings can be easy, the code can get ugly fast. You may have usage scattered throughout your code.

public MainPage()
{
    InitializeComponent();
 
    IsolatedStorageSettings settings = IsolatedStorageSettings.ApplicationSettings;
 
    bool useLocation;
    if (settings.TryGetValue("UseLocation", out useLocation) == false)
    {
        useLocation = true;
    }
 
    // do something with the setting
}

Eventually you’ll get tired of all of this code everywhere and probably create a class and wrap the access within the properties.

public class Settings
{
    public bool UseLocation
    {
        get
        {
            IsolatedStorageSettings settings = IsolatedStorageSettings.ApplicationSettings;
 
            bool useLocation;
            if (settings.TryGetValue("UseLocation", out useLocation) == false)
            {
                useLocation = true;
            }
            return useLocation;
        }
        set
        {
            IsolatedStorageSettings settings = IsolatedStorageSettings.ApplicationSettings;
 
            settings["UseLocation"] = value;
            settings.Save();
        }
    }
 
    public double SomeOtherValue
    {
        get
        {
            IsolatedStorageSettings settings = IsolatedStorageSettings.ApplicationSettings;
 
            double someOtherValue;
            settings.TryGetValue("SomeOtherValue", out someOtherValue);
            return someOtherValue;
        }
        set
        {
            IsolatedStorageSettings settings = IsolatedStorageSettings.ApplicationSettings;
 
            settings["SomeOtherValue"] = value;
            settings.Save();
        }
    }
}

As you can see this eventually gets out of control. Let’s take this one step further. Instead of littering our code with all of this dictionary access, why don’t we just create a class with automatic properties that has a Save and Load method.

public class Settings
{
    public bool UseLocation { get; set; }
 
    public double SomeOtherValue { get; set; }
 
    public void Save()
    {
        IsolatedStorageSettings settings = IsolatedStorageSettings.ApplicationSettings;
 
        settings["UseLocation"] = UseLocation;
        settings["SomeOtherValue"] = SomeOtherValue;
        settings.Save();
    }
 
    public void Load()
    {
        IsolatedStorageSettings settings = IsolatedStorageSettings.ApplicationSettings;
 
        bool useLocation;
        if (settings.TryGetValue("UseLocation", out useLocation) == false)
        {
            useLocation = true;
        }
        UseLocation = useLocation;
 
        double someOtherValue;
        settings.TryGetValue("SomeOtherValue", out someOtherValue);
        SomeOtherValue = someOtherValue;
    }
}

Our code got a little better, but it’s not very extensible. Every time you add a new property you have to add keys/values to saving and loading. When you write your next app, you’ll copy and paste the file and change the strings. I enjoy reusing code, not copy/paste. I enjoy doing less work, not adding key/values for every setting property I have.

We can make this even better. We can make a reusable class, in which we do not have to worry about adding more than our auto properties. We do this with reflection and a base class.

public class Settings
{
    public void Save()
    {
        IsolatedStorageSettings settings = IsolatedStorageSettings.ApplicationSettings;
        PropertyInfo[] properties = GetType().GetProperties();
        foreach (var property in properties)
        {
            settings[property.Name] = property.GetValue(this);
        }
 
        settings.Save();
    }
 
    public void Load()
    {
        IsolatedStorageSettings settings = IsolatedStorageSettings.ApplicationSettings;
        PropertyInfo[] properties = GetType().GetProperties();
        foreach (var property in properties)
        {
            if (settings.Contains(property.Name))
            {
                property.SetValue(this, settings[property.Name]);
            }
        }
    }
}

Now that we have a base class for settings, we can create an instance of settings that are unique for our application. All this file needs is the properties that define the application settings.

public class AppSettings : Settings
{
    public AppSettings()
    {
        // default to true, let the user override this value
        UseLocation = true;
    }
 
    public bool UseLocation { get; set; }
 
    public double SomeOtherValue { get; set; }
}

With this approach, you’ll want to make sure that you load and save your settings when needed. I personally like to create a static method/property that creates my settings and loads the values.

Note: You could just save the file itself in iso settings rather than each property, but that is not the point of this post :-)

Visually Located | Use the [beta] Nokia Imaging SDK to crop and resize any image to create a lockscreen for your phone

Visually Located

XAML and GIS

Use the [beta] Nokia Imaging SDK to crop and resize any image to create a lockscreen for your phone

When the new Nokia Imaging SDK was released I was really excited to start using it within one of my apps. Unlike most, I was not interested in the image filters that change how it looks. I was initially interested in using the resize and crop functionality it had. The day after it was released my wife had surgery, so I had a good amount of time to play with the SDK while I sat in the waiting room. What I wanted to accomplish that was to take a random photo from the users phone, crop and resize it to fit the device and set it as the lockscreen. I know that you can set any image to be the lockscreen and  if the image is too big, it will center the image. I needed to do it manually because I wanted to overlay information on the image.

Getting the random image is pretty easy. We’ll just get one from the MediaLibrary.

private Picture GetRandomImage()
{
    var rand = new Random(DateTime.Now.Millisecond);
 
    MediaLibrary library = new MediaLibrary();
    var album = library.RootPictureAlbum;
 
    int albumIndex = rand.Next(0, album.Albums.Count - 1);
    album = album.Albums[albumIndex];
 
    var pictureIndex = rand.Next(0, album.Pictures.Count - 1);
    var picture = album.Pictures[pictureIndex];
 
    return picture;
}

Now that we have a Picture, we need to crop and resize it. All actions with an image are done through an EditingSession. You create an EditingSession with the EditingSessionFactory. The EditingSession has two key methods that allow us to crop and/or resize any image to fit the phone screen. The AddFilter method will allow us to crop the image while the RenderToJpegAsync method will allow us to resize the image. The AddFilter method is bar far the best part of the SDK. It allows you to do anything to the image., and I do mean anything. The method takes an IFilter as an argument. Lucky for us you can create a cropping filter. To create a cropping filter, you need to specify an area that will be the crop.

/// <summary>
/// Returns the area needed to crop an image to the desired height and width.
/// </summary>
/// <param name="imageSize">The size of the image.</param>
/// <param name="desiredSize">The desired size to crop the image to.</param>
/// <returns></returns>
private static Rect? GetCropArea(Size imageSize, Size desiredSize)
{
    // how big is the picture compared to the phone?
    var widthRatio = desiredSize.Width / imageSize.Width;
    var heightRatio = desiredSize.Height / imageSize.Height;
 
    // the ratio is the same, no need to crop it
    if (widthRatio == heightRatio) return null;
 
    double cropWidth;
    double cropheight;
    if (widthRatio < heightRatio)
    {
        cropheight = imageSize.Height;
        cropWidth = desiredSize.Width / heightRatio;
    }
    else
    {
        cropheight = desiredSize.Height / widthRatio;
        cropWidth = imageSize.Width;
    }
 
    int left = (int)(imageSize.Width - cropWidth) / 2;
    int top = (int)(imageSize.Height - cropheight) / 2;
 
    var rect = new Windows.Foundation.Rect(left, top, cropWidth, cropheight);
    return rect;
}
I like to keep things generic and reusable, so the method above will crop any image to any size. If the desired size is larger than the image size it will crop to the same dimensions. 

Now, given the Picture from the MediaLibrary, we can crop it using the Nokia Imaging SDK.

/// <summary>
/// Crops a Picture to the desired size.
/// </summary>
/// <param name="picture">The Picture to crop.</param>
/// <returns>A copy of the Picture which is cropped.</returns>
private static async Task<IBuffer> CropPicture(Picture picture, Size desiredSize)
{
    using (var stream = picture.GetImage())
    {
        using (EditingSession session = await EditingSessionFactory.CreateEditingSessionAsync(stream))
        {
            // Get the crop area of the image, we need to ensure that
            // the image does not get skewed
            Rect? rect = GetCropArea(new Size(picture.Width, picture.Height), desiredSize);
            if (rect.HasValue)
            {
                IFilter filter = FilterFactory.CreateCropFilter(rect.Value);
                session.AddFilter(filter);
            }
 
            // We always want the image to be the size of the phone. 
            // That may mean that it needs to be scaled up also
            var finalImageSize = new Size(desiredSize.Width, desiredSize.Height);
 
            return await session.RenderToJpegAsync(finalImageSize, OutputOption.PreserveAspectRatio);
        }
    }
}

Again, the CropPicture method is generic, allowing for any size. This method could easily be an extension method on the Picture. Now we need to wrap everything up by calling these methods with the phone size. There are already examples of getting the resolution of the phone, so I won’t go into that. Now just put the pieces together.

private async Task<Stream> GetRandomLockscreen()
{
    // Get the width and height of the phone
    double phoneWidth = Resolution.PhoneWidth;
    double phoneHeight = Resolution.PhoneHeight;
 
    Picture mediaPicture  = GetRandomImage();
    IBuffer croppedImage = await CropPicture(mediaPicture, new Size(phoneWidth, phoneHeight));
 
    if (croppedImage == null) return null;
 
    return croppedImage.AsStream();
}

From there you can save the image to disk, and then maybe save it as a lockscreen. While this code works great, do not expect it to work in a background task. Here is the one place where the imaging SDK falls short. I was wanting to use the SDK in a background task. Background tasks are limited on the amount of resources allowed. The biggest limitation is the memory constraint. Working with images is very memory intensive, especially in C#. So when trying to manipulate an image in a background task, you can quickly run out of memory. I was hoping the imaging SDK would help alleviate some of these issues by working with images smarter. Instead of reading in every pixel of the image and then processing it, it should read n rows of pixels from the stream and then process those rows. This is a hard thing to accomplish, but I’m hoping the awesome people at Nokia can update their SDK to allow for this.

blog comments powered by Disqus