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.

blog comments powered by Disqus