Filter Explorer for Windows and Windows Phone 8.1

The Filter Explorer app demonstrates some of the image editing capabilities and performance of the Lumia Imaging SDK by allowing the user to apply a number of filter layers to existing or newly captured photos.

Compatibility

  • Compatible with Windows and Windows Phone 8.1.
  • Tested with Nokia Lumia 2520, Windows 8.1, and Windows Phone 8.1 emulator.
  • Developed with Visual Studio 2013 Express for Windows.
  • Compiling the project requires the Lumia Imaging SDK.

One of the targets of this project was to create a universal app for Windows and Windows Phone 8.1, sharing most of the code between the two. Therefore most of the application business logic and image processing code was implemented in a shared code project, leaving only the UI layer to be implemented separately for the specific target platforms.

Design

Filter Explorer opens up into a grid of photos from either the device's Camera Roll (if there is content) or Pictures library, and displays the photos from there with a random filter applied on each of the photos. It randomly updates photos with new filters in a rotation animation. The user can then either open some of the randomly filtered suggestions, open a specific photo from some other folder, or capture a photo with the device's camera. The user can also open another folder to the randomly filtered grid view, and it is also possible to refresh/re-randomize the filters by tapping Refresh.

After selecting a photo, the app navigates to an editing view in which the selected photo is displayed full screen, and the application buttons and menu items provide functionality to add and remove filters, and also to save and share the photo. Tapping Add filter takes the user to a filter selection view in which a quick preview of all the filters are displayed. The idea of Filter Explorer is that you can add many filters on top of each other, creating a custom combo filter stack!

Windows 8.1

Dn859587.windows-1-stream(en-us,WIN.10).png Dn859587.windows-2-unfiltered(en-us,WIN.10).png
Dn859587.windows-3-filters(en-us,WIN.10).png Dn859587.windows-5-filtered(en-us,WIN.10).png

Windows Phone 8.1

Dn859587.phone-1-stream(en-us,WIN.10).png Dn859587.phone-2-unfiltered(en-us,WIN.10).png Dn859587.phone-3-filters(en-us,WIN.10).png Dn859587.phone-4-filtered(en-us,WIN.10).png

Architecture overview

Filter Explorer for Windows has been implemented in a Model-View-ViewModel fashion. Briefly, this means that the data classes and platform data source abstractions are packaged in the Models layer, the main application business logic is in the ViewModels layer, and the UI is in the Views layer. These layers have only soft dependencies on each other in pull-only fashion, meaning only in the Views -> ViewModels -> Models direction. Views are connected to ViewModels mostly with XAML bindings, and they interact with the application business logic using Commands exposed by the ViewModels. This kind of architecture makes the layers separate enough to be replaceable with little work, making things such as "porting" the app from the initially written Windows 8.1 tablet version to the Windows Phone 8.1 version relatively easy.

The app works so that photos are loaded in StreamPageViewModel and wrapped into PhotoModels (access to the data), that are again wrapped into FilteredPhotoModels (uses the PhotoModel to get the image data, and applies Filters on the images). The Lumia Imaging SDK is used in the PhotoModel to resize unfiltered images from the platform, and in FilteredPhotoModel to render the filtered images.

Reading and filtering a photo

Filter Explorer for Windows wraps the Lumia Imaging SDK IFilters to an abstract custom Filter base class in order to easily add them more properties like translatable names for the UI.

namespace FilterExplorer.Filters
{
    public abstract class Filter
    {
        public string Id
        {
            get
            {
                return GetType().ToString();
            }
        }

        public string Name
        {
            get
            {
                // In Filter Explorer for Windows each filter has a localizable name in resources,
                // and the key for the name is for example AntiqueFilterName
                var resourceLoader = new Windows.ApplicationModel.Resources.ResourceLoader();

                return resourceLoader.GetString(Id.Split('.').Last() + "Name");
            }
        }
    
        public abstract Lumia.Imaging.IFilter GetFilter();
    }

    public class AntiqueFilter : Filter
    {
        public override Lumia.Imaging.IFilter GetFilter()
        {
            return new Lumia.Imaging.Artistic.AntiqueFilter();
        }
    }

    ...
}

Each FilteredPhotoModel contains a PhotoModel, and in this partnership the PhotoModel is responsible for reading the image data from the platform, and the FilteredPhotoModel is responsible for applying filters on the data.

namespace FilterExplorer.Models
{
    public class PhotoModel
    {
        private Windows.Storage.StorageFile _file = null;

        ...

        public PhotoModel(Windows.Storage.StorageFile file)
        {
            _file = file;
        }

        public async Task<IRandomAccessStream> GetPhotoAsync()
        {
            ... // omitted, calls GetPhotoStreamAsync() and caches the result for sequential calls
        }

        private async Task<IRandomAccessStream> GetPhotoStreamAsync()
        {
            return await _file.OpenReadAsync();
        }

        ...
    }
}

When FilteredPhotoModel.GetFilteredPhotoAsync is called, it causes PhotoModel's GetPhotoAsync to be called for getting the image data as stream from the platform, and this data is used as input for rendering the filtered photo.

namespace FilterExplorer.Models
{
    public class FilteredPhotoModel
    {
        private PhotoModel _photo = null;

        ...

        public ObservableList<Filter> Filters { get; private set; }

        public FilteredPhotoModel(Windows.Storage.StorageFile file)
        {
            _photo = new PhotoModel(file);

            Filters = new ObservableList<Filter>();

            ...
        }

        public async Task<IRandomAccessStream> GetFilteredPhotoAsync()
        {
            ... // omitted, calls GetFilteredPhotoStreamAsync() and caches the result for sequential calls
        }

        private async Task<IRandomAccessStream> GetFilteredPhotoStreamAsync()
        {
            IRandomAccessStream filteredStream = null;

            using (var stream = await _photo.GetPhotoAsync())
            {
                if (Filters.Count > 0)
                {
                    var list = new List<IFilter>();

                    foreach (var filter in Filters)
                    {
                        list.Add(filter.GetFilter());
                    }

                    filteredStream = new InMemoryRandomAccessStream();

                    using (var source = new RandomAccessStreamImageSource(stream))
                    using (var effect = new FilterEffect(source) { Filters = list })
                    using (var renderer = new JpegRenderer(effect))
                    {
                        var buffer = await renderer.RenderAsync();

                        await filteredStream.WriteAsync(buffer);
                    }
                }
                else
                {
                    filteredStream = stream.CloneStream();
                }
            }

            return filteredStream;
        }

        ...
    }
}

See also

Downloads

Filter Explorer for Windows and Windows Phone 8.1 project filter-explorer-master.zip

This example application is hosted in GitHub, where you can check the latest activities, report issues, browse source, ask questions, or even contribute to the project yourself.