×
Namespaces

Variants
Actions

Tiltshift effect using Nokia Imaging SDK

From Nokia Developer Wiki
Jump to: navigation, search
Featured Article
26 Jan
2014

This article explains Miniature faking ("the Tilt-shift effect") and how to re-create it using Nokia Imaging SDK.

Note.pngNote: This is an entry in the Nokia Imaging and Big UI Wiki Competition 2013Q4.

SignpostIcon XAML 40.png
WP Metro Icon WP8.png
Article Metadata
Code ExampleTested with
SDK: Windows Phone 8.0 SDK
Devices(s): Nokia Lumia 920
Compatibility
Platform(s):
Windows Phone 8
Dependencies: Nokia Imaging SDK 1.0
Article
Created: MaMi (13 Dec 2013)
Last edited: kiran10182 (27 Jan 2014)

Contents

Introduction

Miniature faking is a process where an image of a full sized object or scene is modified to make it look like a picture of a miniature scale model. In traditional photography, miniature faking was done using special lenses to "tilt" and "shift" the image (hence the title Tilt-shift effect). With modern software, miniature faking is now done by digitally processing the image.

The Nokia Imaging SDK provides a lot of filter effects and allows developers to mix them to create new filter groups (and even to create completely new filters). This article shows how to create a Tiltshift effect using existing filters in the SDK, making big things look like miniatures direct from your phone.

A very basic knowledge on the Imaging SDK can help to better understand this article. The Quick start guide is recommended reading.

Final result of our TiltShift effect - Original photo from unicellular on Flickr


Project setup

Let's start by creating a new project, select Windows Phone App template and Windows Phone 8 as target version.

Then import the Nokia Imaging SDK into the project as explained in Download and add the libraries to the project (Lumia Developers' Library).

Finally we will create a basic interface in MainPage.xaml with just an image and a button in the ApplicationBar in order to apply the effect.

Be sure to set the page orientation to landscape and hide the system tray in order to have as much space as possible on screen.

<phone:PhoneApplicationPage
x:Class="TiltShift.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
FontFamily="{StaticResource PhoneFontFamilyNormal}"
FontSize="{StaticResource PhoneFontSizeNormal}"
Foreground="{StaticResource PhoneForegroundBrush}"
SupportedOrientations="Landscape" Orientation="Landscape"
shell:SystemTray.IsVisible="false">
 
<Grid x:Name="LayoutRoot" Background="White">
<Image x:Name="CurrentImage" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Stretch="Uniform" />
</Grid>
 
<phone:PhoneApplicationPage.ApplicationBar>
<shell:ApplicationBar>
<shell:ApplicationBarIconButton Text="Apply" IconUri="/Assets/AppBar/check.png" Click="ApplyFilterIconButton_Click" />
</shell:ApplicationBar>
</phone:PhoneApplicationPage.ApplicationBar>
</phone:PhoneApplicationPage>

Load your image

We will load an image from the library when the application start, to do so we will use the Photo chooser task from Windows phone SDK.

In MainPage.xaml.cs file and add a reference to

using Microsoft.Phone.Tasks;

and override the OnNavigatedTo method like this:

protected override void OnNavigatedTo(NavigationEventArgs e)
{
if (e.NavigationMode == NavigationMode.New)
{
PhotoChooserTask chooser = new PhotoChooserTask();
chooser.Completed += PickImageCallback;
chooser.Show();
}
base.OnNavigatedTo(e);
}

Now we need to handle the image once it was selected by the user, to do so we will store its data in a dedicated stream and then display it on screen.

Start by declaring some variables:

 private MemoryStream m_originalImageData = null;
private WriteableBitmap m_displayedImage = null;

And implement the PickImageCallback method

private void PickImageCallback(object sender, PhotoResult e)
{
if (e.TaskResult != TaskResult.OK || e.ChosenPhoto == null)
{
//Exit application if no photo were chosen or if something bad happened
NavigationService.GoBack();
}
 
try
{
//Save image data in stream
m_originalImageData = new MemoryStream();
e.ChosenPhoto.CopyTo(m_originalImageData);
 
//Display image
e.ChosenPhoto.Position = 0;
m_displayedImage = new WriteableBitmap((int)CurrentImage.Width, (int)CurrentImage.Height);
m_displayedImage.SetSource(e.ChosenPhoto);
CurrentImage.Source = m_displayedImage;
}
catch (Exception exception)
{
MessageBox.Show(exception.Message);
NavigationService.GoBack();
}
}

Recreate the effect

Now that we have loaded a photo we can apply the effect to it. The tilt-shift effect is created by mixing two effects already available in the Nokia Imaging SDK: the Color boost effect and the Blur effect. Boosting the colour of the entire photo makes it look more "toy like". Combined with the blur effect, which simulates the shallow depth of field often seen in close up photograhpy, this creates the illusion that the image is a miniature.

First we apply the colour boost.

Declare a FilterEffect and a variable that will handle the boost value, and set up the filter in the ApplyFilterIconButton_Click method.

FilterEffect m_filterEffect = null;
double m_userColorBoostValue = 1.5;
 
 
private async void ApplyFilterIconButton_Click(object sender, EventArgs e)
{
//List of filters that will be applied to the photo
var l_filters = new List<IFilter>();
 
//Color boost effect
l_filters.Add(new ColorBoostFilter(m_userColorBoostValue));
}


Then we need to blur the top and bottom of the photo, using decreasing blur values in multiple effects (ie create a "blue gradient". As usual start by defining some variables and constants:

int m_userMaxBlurValue = 35;
 
//Number of steps in blur gradient
const int NB_BLUR_GRADIENT_STEPS = 20;
 
//Min blur value
const int MIN_BLUR = 3;

NB_BLUR_GRADIENT_STEPS represent the number of blur effect we will apply at the top and bottom of the photo in order to achieve our gradient. The more steps the smoother the gradient will look - but the longer it will take to process.

We also set a minimum blur value in order to have our gradient always look blurry.

Then inside the ApplyFilterIconButton_Click() method add the following:

//Retrieve Image dimensions
int l_imgW = m_displayedImage.PixelWidth;
int l_imgH = m_displayedImage.PixelHeight;
 
//Define the range of blur to be applied
int l_blurRange = m_userMaxBlurValue - MIN_BLUR;
 
//Define blur area height
int l_blurredAreaHeight = l_imgH / 3;
 
//And the height of each gradient step
int l_blurGradientStepHeight = l_blurredAreaHeight / NB_BLUR_GRADIENT_STEPS;
 
//Top blur gradient
for (int l_y = 0; l_y <= l_blurredAreaHeight; l_y += l_blurGradientStepHeight)
{
int l_blurValue = l_blurRange * (l_blurredAreaHeight - l_y ) / l_blurredAreaHeight + MIN_BLUR;
l_filters.Add(new BlurFilter(l_blurValue, new Windows.Foundation.Rect(0, l_y , l_imgW, l_blurGradientStepHeight), BlurRegionShape.Rectangular));
}
 
//Bottom blur gradient
for (int l_y = 0; l_y <= l_blurredAreaHeight; l_y += l_blurGradientStepHeight)
{
int l_blurValue = l_blurRange * (l_blurredAreaHeight - l_y ) / l_blurredAreaHeight + MIN_BLUR;
l_filters.Add(new BlurFilter(l_blurValue, new Windows.Foundation.Rect(0, l_imgH - (l_y + l_blurGradientStepHeight), l_imgW, l_blurGradientStepHeight), BlurRegionShape.Rectangular));
}

We keep top and bottom blur in a different loop as we will later process them with different values.

Finally apply the effect to the image:

m_originalImageData.Position = 0;
var l_imageStream = new StreamImageSource(m_originalImageData);
 
m_filterEffect = new FilterEffect(l_imageStream )
{
Filters = l_filters
};
 
var l_renderer = new WriteableBitmapRenderer(m_filterEffect, m_displayedImage);
await l_renderer.RenderAsync();
 
CurrentImage.Source = m_displayedImage;

You should now be able to launch the application, pick a picture, apply the effect, and enter in a small small world!

Allow user to change the effect parameters

So far we specified fixed values for the effects parameters. This section explains how the code is modified to allow the user to define these values in the way that is most appropriate for their image.

Define blurred area

First we will let user define the area that he want to blur on the photo, so we will add two grids with semi transparent background that the user will be able to resize with touch.

Blurred areas definition interface
<Grid x:Name="LayoutRoot" Background="White">
<Image x:Name="CurrentImage" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Stretch="Uniform" />
 
<Grid x:Name="TopBlurAreaGrid" Background="#7F000000" Height="120" VerticalAlignment="Top" ManipulationDelta="TopBlurAreaGrid_ManipulationDelta" />
<Grid x:Name="BottomBlurAreaGrid" Background="#7F000000" Height="120" VerticalAlignment="Bottom" ManipulationDelta="BottomBlurAreaGrid_ManipulationDelta" />
</Grid>

The code behind:

private void TopBlurAreaGrid_ManipulationDelta(object sender, System.Windows.Input.ManipulationDeltaEventArgs e)
{
var l_verticalOffset = (int)e.DeltaManipulation.Translation.Y;
 
//Clamp the grid size
if (TopBlurAreaGrid.Height + l_verticalOffset > 0 && TopBlurAreaGrid.Height + l_verticalOffset + BottomBlurAreaGrid.Height < INTERFACE_HEIGHT)
TopBlurAreaGrid.Height += l_verticalOffset;
}
 
private void BottomBlurAreaGrid_ManipulationDelta(object sender, System.Windows.Input.ManipulationDeltaEventArgs e)
{
var l_verticalOffset = (int)-e.DeltaManipulation.Translation.Y;
 
//Clamp the grid size
if (BottomBlurAreaGrid.Height + l_verticalOffset > 0 && BottomBlurAreaGrid.Height + l_verticalOffset + TopBlurAreaGrid.Height < INTERFACE_HEIGHT)
BottomBlurAreaGrid.Height += l_verticalOffset;
}

Now we can define the area to blur using those grid size we applying the effect.

Define a new constant for the interface height (or in a real app retrieve using code).

const int INTERFACE_HEIGHT = 480; // hard coded for convenience

Then change some code in ApplyFilterIconButton_Click()

//Define blur area height based on grid height
int l_blurredAreaHeight = (int)(TopBlurAreaGrid.Height * l_imgH / INTERFACE_HEIGHT);
 
//And the height of each gradient step
int l_blurGradientStepHeight = l_blurredAreaHeight / NB_BLUR_GRADIENT_STEPS;
 
//Top blur gradient
for (int l_y = 0; l_y <= l_blurredAreaHeight; l_y += l_blurGradientStepHeight)
{
int l_blurValue = l_blurRange * (l_blurredAreaHeight - l_y ) / l_blurredAreaHeight + MIN_BLUR;
l_filters.Add(new BlurFilter(l_blurValue, new Windows.Foundation.Rect(0, l_y , l_imgW, l_blurGradientStepHeight), BlurRegionShape.Rectangular));
}
 
//Redefine blur area height for the bottom part
l_blurredAreaHeight = (int)(BottomBlurAreaGrid.Height * l_imgH / INTERFACE_HEIGHT);
l_blurGradientStepHeight = l_blurredAreaHeight / NB_BLUR_GRADIENT_STEPS;
 
//Bottom blur gradient
for (int l_y = 0; l_y <= l_blurredAreaHeight; l_y += l_blurGradientStepHeight)
{
int l_blurValue = l_blurRange * (l_blurredAreaHeight - l_y ) / l_blurredAreaHeight + MIN_BLUR;
l_filters.Add(new BlurFilter(l_blurValue, new Windows.Foundation.Rect(0, l_imgH - (l_y + l_blurGradientStepHeight), l_imgW, l_blurGradientStepHeight), BlurRegionShape.Rectangular));
}

Remember to hide the interface once the effect is applied.

At the end of ApplyFilterIconButton_Click method add:

 SetBlurAreaUiVisibility(false);

This is defined as:

Boolean IsBlurAreasUIVisible = true;
public void SetBlurAreaUIVisibility(Boolean p_isVisible)
{
if (p_isVisible)
{
ApplicationBar.IsVisible = true;
TopBlurAreaGrid.Visibility = Visibility.Visible;
BottomBlurAreaGrid.Visibility = Visibility.Visible;
}
else
{
ApplicationBar.IsVisible = false;
TopBlurAreaGrid.Visibility = Visibility.Collapsed;
BottomBlurAreaGrid.Visibility = Visibility.Collapsed;
}
 
IsBlurAreasUIVisible = p_isVisible;
}

Finally, show the UI again if back key is pressed

protected override void OnBackKeyPress(System.ComponentModel.CancelEventArgs e)
{
if (!IsBlurAreasUIVisible)
{
SetBlurAreaUIVisibility(true);
e.Cancel = true;
}
 
base.OnBackKeyPress(e);
}


Define color boost and blur value

We will also add two sliders to let the user define how blurry the effect is and the amount of colour boost

Define color boost and blur value interface

First define the XAML

<Grid x:Name="SettingsPanel" Background="#7F000000" Visibility="Collapsed">
<StackPanel VerticalAlignment="Center" Margin="12">
<TextBlock Text="Blurriness:" Margin="12"/>
<Slider x:Name="BlurSlider" ValueChanged="BlurSlider_ValueChanged" Value="5" />
<TextBlock Text="Color boost:" Margin="12"/>
<Slider x:Name="ColorSlider" ValueChanged="ColorSlider_ValueChanged" Value="5"/>
</StackPanel>
</Grid>

Add also a button in the application bar

<shell:ApplicationBarIconButton Text="Settings" IconUri="/Assets/AppBar/feature.settings.png" Click="SettingsIconButton_Click" />

The code behind to manage the panel visibility:

Boolean IsSettingVisible = false;
 
private void SettingsIconButton_Click(object sender, EventArgs e)
{
SetSettingsVisibility(true);
}
 
public void SetSettingsVisibility(Boolean p_isVisible)
{
if (p_isVisible)
{
SettingsPanel.Visibility = Visibility.Visible;
ApplicationBar.IsVisible = false;
}
else
{
SettingsPanel.Visibility = Visibility.Collapsed;
ApplicationBar.IsVisible = true;
}
IsSettingVisible = p_isVisible;
}

Update the back key management too

protected override void OnBackKeyPress(System.ComponentModel.CancelEventArgs e)
{
if (!IsBlurAreasUIVisible)
{
SetBlurAreaUIVisibility(true);
e.Cancel = true;
}
else if (IsSettingVisible)
{
SetSettingsVisibility(false);
e.Cancel = true;
}
 
base.OnBackKeyPress(e);
}

Define some constants to manage the slider values

//Max blur value
const int MAX_BLUR = 60;
 
const double MIN_COLOR_BOOST = 0;
const double MAX_COLOR_BOOST = 3;

Finally, use the value directly from the sliders when those value are changed

private void BlurSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
m_userMaxBlurValue = (int)((MAX_BLUR-MIN_BLUR) * e.NewValue / 10) + MIN_BLUR;
}
 
private void ColorSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
m_userColorBoostValue = (double)((MAX_COLOR_BOOST-MIN_COLOR_BOOST) * e.NewValue / 10) + MIN_COLOR_BOOST;
}

Screenshots with different parameters

Here is our sample photo with the various parameters applied

More samples

This page was last modified on 27 January 2014, at 03:11.
140 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.

×