×
Namespaces

Variants
Actions

Filter Parameter Harmonization - Dynamic UI generation for filter parameters

From Nokia Developer Wiki
Jump to: navigation, search

This article explains how to harmonize the filter parameters and provide a single control which would allow the user to change the filter parameters.

SignpostIcon XAML 40.png
WP Metro Icon WP8.png
Article Metadata
Code ExampleTested with
SDK: Windows Phone 8.0 SDK, Nokia Imaging SDK Beta 1
Compatibility
Platform(s):
Windows Phone 8
Article
Created: RatishPhilip (27 Aug 2013)
Last edited: hamishwillee (14 Oct 2013)

Contents

Introduction

In the FilterEffects sample, there is a FilterPropertiesControl which acts as a place holder for custom controls used for manipulating the Filter parameters. This control made me wonder if it was possible to generate the UI for each Filter's parameters dynamically. Since each filter has a different set of parameters, this task seemed a bit difficult at first. However, I went through each of the filters available in the SDK and found out that out of the 52 filters:

  • 10 Filters do not require any parameters during creation
  • 29 Filters require either a numeric range, bool, Enum or a combination of these to create the filter. These values can be represented by a Slider control or a ToggleSwitch control.
  • The remaining 13 filters required more complex parameters such as Point, EditingSession, Color to name a few.

As the first step, I decided to harmonize the 29 filters that required either a numeric range, bool, Enum or a combination of these during instantiation. The following is the architecture of the FilterHarmonization sample app which I created for this purpose:

Architecture

The main libraries required for this application are:

  • Nokia Image SDK: For Imaging related features.
  • Reactive Extensions: For efficient subscription to UI events.
  • Windows Phone Toolkit: For the ToggleSwitch control to represent the bool parameter of the Filter.

All these libraries are available through NuGet.

The classes which mainly cater to the purpose of Filter Harmonization are depicted below

Class Diagram

IFiterParam

IFilterParam is the interface which is implemented by all the classes representing the Filter parameters. It defines two read-only properties:

  • Name : Represents the name to be displayed along with the control describing the Filter parameter.
  • ParamType: The type of the Filter parameter that it represents. This is used to find the appropriate method in FilterFactory via Reflection.
public interface IFilterParam
{
string Name { get; }
Type ParamType { get; }
}

FilterParam<T>

FilterParam<T> is a generic and abstract class which implements the IFilterParam interface. It usually represents those Filter parameters which are a numeric range or an Enum type. The control used to represent this class is the Slider. FilterParam<T> defines the following additional read-only properties:

  • Min: the minimum value for the slider.
  • Max: the maximum value for the slider.
  • Step: the small change value for the slider.
  • Default: the default value for the slider.
public abstract class FilterParam<T> : IFilterParam where T : struct, IComparable
{
public string Name
{
get;
protected set;
}
 
public Type ParamType
{
get;
protected set;
}
 
public T Min
{
get;
protected set;
}
 
public T Max
{
get;
protected set;
}
 
public T Step
{
get;
protected set;
}
 
public T Default
{
get;
protected set;
}
}


BoolParam

This class represents Filter parameters of type bool. The ToggleSwitch control is used to represent this class in the UI. It defines the following additional read-only properties:

  • OffText: The content of the ToggleSwitch when it is in Unchecked state.
  • OnText: The content of the ToggleSwitch when it is in Checked state.
public class BoolParam : IFilterParam
{
public string Name
{
get;
private set;
}
 
public Type ParamType
{
get;
private set;
}
 
public string OffText
{
get;
private set;
}
 
public string OnText
{
get;
private set;
}
 
public BoolParam(string name, string offText, string onText)
{
ParamType = typeof(bool);
Name = name;
OffText = offText;
OnText = onText;
}
}


EnumParam

This class derives from FilterParam<int> and it represents the Filter parameters which are of type enum. One constraint is that it expects the enum to be of type int. The control used to represent this class is the Slider with each value representing one of the enum values.

public class EnumParam : FilterParam<int>
{
public EnumParam(string name, Type enumType, int defaultValue)
{
if ((enumType != null) && (enumType.IsEnum))
{
Name = name;
ParamType = enumType;
var enumValues = Enum.GetValues(enumType).Cast<int>();
Min = enumValues.First();
Max = enumValues.Last();
Step = 1;
Default = Min;
}
else
{
throw new ArgumentException("Argument must be of type Enum");
}
}
}

RangeParam<T>

This is a generic class which implements FilterParam<T> and represents the Filter parameters whose values should fall within a numeric range. The Slider control represents this class and provides values in the specified numeric range.

public class RangeParam<T> : FilterParam<T> where T : struct, IComparable
{
public RangeParam(string name, T min, T max, T step, T defaultValue)
{
ParamType = typeof(T);
Name = name;
Min = min;
Max = max;
Step = step;
Default = defaultValue;
}
}

FilterEngine

FilterEngine is a static class which is responsible for providing a list of IFilterParam for a particular filter. This list is used by the FilterSettingsControl to generate the UI for the Filter settings.

public static class FilterEngine
{
public static List<IFilterParam> GetFilterParams(string filterName)
{
List<IFilterParam> result = null;
 
switch (filterName)
{
case "Blur":
result = CreateBlurFilterParams();
break;
 
case "Brightness":
result = CreateBrightnessFilterParams();
break;
 
case "Cartoon":
result = CreateCartoonFilterParams();
break;
 
case "ColorAdjust":
result = CreatColorAdjustFilterParams();
break;
 
...
 
default:
break;
}
 
return result;
}
 
/// <summary>
/// Blur Filter
/// </summary>
/// <returns>List of IFilterParam</returns>
private static List<IFilterParam> CreateBlurFilterParams()
{
List<IFilterParam> filterParams = new List<IFilterParam>();
 
filterParams.Add(new EnumParam("Blur", typeof(BlurLevel), 0));
 
return filterParams;
}
 
/// <summary>
/// Brightness Filter
/// </summary>
/// <returns>List of IFilterParam</returns>
private static List<IFilterParam> CreateBrightnessFilterParams()
{
List<IFilterParam> filterParams = new List<IFilterParam>();
 
filterParams.Add(new RangeParam<double>("Brightness", -1.0, 1.0, 0.05, 0.0));
 
return filterParams;
}
 
/// <summary>
/// Cartoon Filter
/// </summary>
/// <returns>List of IFilterParam</returns>
private static List<IFilterParam> CreateCartoonFilterParams()
{
List<IFilterParam> filterParams = new List<IFilterParam>();
 
filterParams.Add(new BoolParam("Distinct Edges", "No", "Yes"));
 
return filterParams;
}
 
/// <summary>
/// ColorAdjust Filter
/// </summary>
/// <returns>List of IFilterParam</returns>
private static List<IFilterParam> CreatColorAdjustFilterParams()
{
List<IFilterParam> filterParams = new List<IFilterParam>();
 
filterParams.Add(new RangeParam<double>("Red", -1.0, 1.0, 0.05, 0.0));
filterParams.Add(new RangeParam<double>("Green", -1.0, 1.0, 0.05, 0.0));
filterParams.Add(new RangeParam<double>("Blue", -1.0, 1.0, 0.05, 0.0));
 
return filterParams;
}
 
...
}

FilterSettingsControl

FilterSettingsControl is a UserControl which dynamically generates the UI encapsulating the parameters for a particular filter and is displayed to the user. By using Reactive Extension library, any changes made to the parameters by the user is converted to a new IFilter and is notified to the parent control or page via the FilterChanged event. The parent page upon receiving the new Filter applies it to the current image by using the technique specified in my previous article Effective Nokia Imaging SDK 2 : Handling rapid Filter parameter changes.

This control also employs the use of the Reactive Extensions Library. Reactive Extensions (Rx) is a library for composing asynchronous and event-based programs using observable sequences and LINQ-style query operators. It provides an easy way of subscribing to UI events and also perform LINQ operations on them.

The Rx library v2.1 can be easily installed through NuGet. Just search for Reactive Extensions in the Manange NuGet Packages dialog. Select the Reactive Extensions - Main library. It will install everything related Rx that you will need (Interfaces, Core, Linq and Platform Services).

Note.pngNote: When you select the Reactive Extensions- Main Library it installs the System.Reactive.Core.dll also. This is not required for the current project. Remove it from the Reference section of your project, otherwise it would give an ambiguous call error while compiling.

Getting Rx through NuGet

Also by using the Throttle method in Rx, it is possible to buffer the changes made to the filter properties by the user and obtain the latest change at regular intervals.

Note.pngNote: For more information on Throttle method, refer this MSDN article

public partial class FilterSettingsControl : UserControl
{
#region Delegates and Events
 
public delegate void FilterUpdatedHandler(IFilter filter);
public event FilterUpdatedHandler FilterChanged = delegate { };
 
#endregion
 
#region Fields
 
Dictionary<FrameworkElement, IFilterParam> _filterControls = null;
List<IDisposable> _subscribers = null;
List<Type> _types = null;
 
string _filterName;
List<IFilterParam> _filterParams;
TimeSpan _interval;
 
#endregion
 
#region Construction / Initialization
 
public FilterSettingsControl()
{
InitializeComponent();
}
 
#endregion
 
#region APIs
 
public void Create(string name, List<IFilterParam> filterParams, TimeSpan interval)
{
_filterName = name;
_filterParams = filterParams;
_interval = interval;
 
CreateUI();
}
 
#endregion
 
#region Helpers
 
private void CreateUI()
{
// Clear all previous content
CleanUp();
 
// The container for all settings controls
StackPanel panel = new StackPanel()
{
HorizontalAlignment = HorizontalAlignment.Stretch,
VerticalAlignment = VerticalAlignment.Stretch
};
 
LayoutRoot.Children.Add(panel);
 
_filterControls = new Dictionary<FrameworkElement, IFilterParam>();
_subscribers = new List<IDisposable>();
_types = new List<Type>();
 
foreach (IFilterParam param in _filterParams)
{
if (param.ParamType == typeof(bool))
{
// Add a ToggleSwitch for boolean parameters
CreateControlsForBoolParam(panel, param);
}
else
{
// Add a slider for the rest of the parameter types
CreateControls(panel, param);
}
 
// Add the param Type to the types list so that it can be used
// to obtain the correct method in the CreateFilter method
_types.Add(param.ParamType);
}
 
// Apply the filter with current values
OnValueChanged();
}
 
/// <summary>
/// Creates controls for the BoolParam.
/// </summary>
/// <param name="panel">Container</param>
/// <param name="param">IFilterParam</param>
private void CreateControlsForBoolParam(StackPanel panel, IFilterParam param)
{
BoolParam bParam = param as BoolParam;
ToggleSwitch togSwitch = new ToggleSwitch()
{
Header = param.Name
};
togSwitch.Content = bParam.OffText;
togSwitch.IsChecked = false;
 
// Subscription for the Checked event
var checkedSubscriber = Observable.FromEvent<RoutedEventArgs>(togSwitch, "Checked")
.Subscribe(x =>
{
Dispatcher.BeginInvoke(new Action(() =>
{
togSwitch.Content = bParam.OnText;
OnValueChanged();
}));
});
 
// Subscription for the Unchecked event
var uncheckedSubscriber = Observable.FromEvent<RoutedEventArgs>(togSwitch, "Unchecked")
.Subscribe(x =>
{
Dispatcher.BeginInvoke(new Action(() =>
{
togSwitch.Content = bParam.OffText;
OnValueChanged();
}));
});
 
panel.Children.Add(togSwitch);
_subscribers.Add(checkedSubscriber);
_subscribers.Add(uncheckedSubscriber);
_filterControls[togSwitch] = param;
}
 
/// <summary>
/// Creates controls for all IFilterParams except BoolParam.
/// </summary>
/// <param name="panel">Container</param>
/// <param name="param">IFilterParam</param>
private void CreateControls(StackPanel panel, IFilterParam param)
{
Grid grid = new Grid()
{
HorizontalAlignment = HorizontalAlignment.Stretch,
VerticalAlignment = VerticalAlignment.Stretch
};
 
// Shows the name of the parameter
TextBlock tb = new TextBlock()
{
Text = param.Name,
Margin = new Thickness(15, 2, 15, 0),
HorizontalAlignment = HorizontalAlignment.Left
};
 
// Displays the current value of the parameter in case of Enum parameter
TextBlock valueTB = new TextBlock
{
Margin = new Thickness(15, 2, 15, 0),
HorizontalAlignment = HorizontalAlignment.Right
};
grid.Children.Add(tb);
grid.Children.Add(valueTB);
 
panel.Children.Add(grid);
 
Type paramType = param.GetType();
double min = (double)Convert.ChangeType(paramType.GetProperty("Min").GetValue(param), typeof(double));
double max = (double)Convert.ChangeType(paramType.GetProperty("Max").GetValue(param), typeof(double));
double step = (double)Convert.ChangeType(paramType.GetProperty("Step").GetValue(param), typeof(double));
double defaultValue = (double)Convert.ChangeType(paramType.GetProperty("Default").GetValue(param), typeof(double));
 
Slider slider = new Slider
{
Margin = new Thickness(10, 2, 10, 0),
Minimum = min,
Maximum = max,
SmallChange = step,
Value = defaultValue,
};
 
// Update the current value of the enum (in case of Enum Param) in the textblock
if (param.ParamType.IsEnum)
{
Dispatcher.BeginInvoke(new Action(() =>
{
valueTB.Text = Enum.ToObject(param.ParamType, (int)(slider.Value)).ToString();
}));
}
 
// Subscribe to the Value Changed event using RX
var subscriber = Observable.FromEvent<RoutedPropertyChangedEventArgs<double>>(slider, "ValueChanged")
.Throttle(_interval)
.Subscribe(x =>
{
Dispatcher.BeginInvoke(new Action(() =>
{
// Update the enum value shown in the ValueTB textblock
if (param.ParamType.IsEnum)
{
valueTB.Text = Enum.ToObject(param.ParamType, (int)(slider.Value)).ToString();
}
 
// Update the filter
OnValueChanged();
}));
});
 
// Add the slider to the panel
panel.Children.Add(slider);
_subscribers.Add(subscriber);
_filterControls[slider] = param;
}
 
/// <summary>
/// Cleans up the resources utilized
/// </summary>
private void CleanUp()
{
if (_filterControls != null)
{
_filterControls.Clear();
}
 
if (_subscribers != null)
{
foreach (var sub in _subscribers)
{
sub.Dispose();
}
}
 
if (_types != null)
{
_types.Clear();
}
 
LayoutRoot.Children.Clear();
}
 
/// <summary>
/// Handler for the value changed in any of the Filter
/// Settings controls.
/// </summary>
private void OnValueChanged()
{
List<object> filterParamList = new List<object>();
 
foreach (FrameworkElement ctrl in _filterControls.Keys)
{
if (ctrl is ToggleSwitch)
{
filterParamList.Add((ctrl as ToggleSwitch).IsChecked == true);
}
else if (ctrl is Slider)
{
if (_filterControls[ctrl].ParamType.IsEnum)
{
filterParamList.Add(Enum.ToObject(_filterControls[ctrl].ParamType, (int)((ctrl as Slider).Value)));
}
else
{
filterParamList.Add(Convert.ChangeType((ctrl as Slider).Value, _filterControls[ctrl].ParamType));
}
}
}
 
// Create a new filter based on the new values
IFilter filter = CreateFilter(filterParamList);
 
// Raise the event
FilterChanged(filter);
}
 
private IFilter CreateFilter(List<object> filterParamList)
{
Type filterFactoryType = typeof(FilterFactory);
string methodName = String.Format("Create{0}Filter", _filterName);
// Find the method in FilterFactory with the given name
// and having the given types as parameters
MethodInfo mi = filterFactoryType.GetMethod(methodName, _types.ToArray());
return mi.Invoke(null, filterParamList.ToArray()) as IFilter;
}
 
#endregion
}

The FilterHarmonization sample app contains a list of the 29 filters whose settings control is generated dynamically. The user can select any filter and image and upon clicking on Show Settings, the parameters for the filter will be displayed. The user can modify the parameters and see the effect of the modified filter on the image simultaneously. Here are a few screenshots of the app.

Source Code

Download Source Code

Summary

Here I have provided a simple technique for harmonizing the Filters provided by Nokia Imaging SDK. However, it cannot cover all the filters as some filters require more complex arguments. Another improvement towards harmonizing the filter parameters would be to have common data types for various filter parameters that is they should use enums, integers and double data types. Also complex data types would require creation of custom user controls which would help in obtaining the required data from the user.

References

WriteableBitmapEx Library
Reactive Extensions (Rx)
FluidStatusBar
Filter Effects Sample
Optimizing Imaging SDK use for rapidly changing filter parameters

Version Hint

Windows Phone: [[Category:Windows Phone]]
[[Category:Windows Phone 7.5]]
[[Category:Windows Phone 8]]

Nokia Asha: [[Category:Nokia Asha]]
[[Category:Nokia Asha Platform 1.0]]

Series 40: [[Category:Series 40]]
[[Category:Series 40 1st Edition]] [[Category:Series 40 2nd Edition]]
[[Category:Series 40 3rd Edition (initial release)]] [[Category:Series 40 3rd Edition FP1]] [[Category:Series 40 3rd Edition FP2]]
[[Category:Series 40 5th Edition (initial release)]] [[Category:Series 40 5th Edition FP1]]
[[Category:Series 40 6th Edition (initial release)]] [[Category:Series 40 6th Edition FP1]] [[Category:Series 40 Developer Platform 1.0]] [[Category:Series 40 Developer Platform 1.1]] [[Category:Series 40 Developer Platform 2.0]]

Symbian: [[Category:Symbian]]
[[Category:S60 1st Edition]] [[Category:S60 2nd Edition (initial release)]] [[Category:S60 2nd Edition FP1]] [[Category:S60 2nd Edition FP2]] [[Category:S60 2nd Edition FP3]]
[[Category:S60 3rd Edition (initial release)]] [[Category:S60 3rd Edition FP1]] [[Category:S60 3rd Edition FP2]]
[[Category:S60 5th Edition]]
[[Category:Symbian^3]] [[Category:Symbian Anna]] [[Category:Nokia Belle]]

This page was last modified on 14 October 2013, at 04:40.
100 page views in the last 30 days.

Was this page helpful?

Your feedback about this content is important. Let us know what you think.

 

Thank you!

We appreciate your feedback.

×