As many of you guys may already know, Telerik RadImageEditor for Windows Phone offers wide extensibility allowing you to modify its current tools and even to create new ones. With the release of Nokia Imaging SDK, which offers wide range of effects designed with low-memory footprint in mind, now it is even easier to extend and create tools for RadImageEditor.
This blog post will show in details how to improve the performance of our ColorEffectsTool in particular and also how to create a new tool which will produce the look of a sketched image. Let’s do some coding.
Improve the ColorEffectsTool
All tools of RadImageEditor are public so we can directly inherit from the ColorEffectsTool class. This tool offers three independent effects (ColorFix, GreyScale and InvertColor) which affect the PreviewImage, and of course the Apply button. This means that we have four key steps for our implementation.
Before we proceed with our ColorEffectsTool, we will create a helper class which will render images using the Nokia Imaging SDK (following its documentation):
public
static
class
NokiaRenderer
{
public
async
static
Task<WriteableBitmap> Render(WriteableBitmap actualImage, List<IFilter> filters)
{
var bitmap = actualImage.AsBitmap();
BitmapImageSource bitmapSource =
new
BitmapImageSource(bitmap);
FilterEffect effects =
new
FilterEffect(bitmapSource);
effects.Filters = filters;
WriteableBitmapRenderer renderer =
new
WriteableBitmapRenderer(effects, actualImage);
return
await renderer.RenderAsync();
}
}
For our CustomColorEffectsTool, we will need a collection of filters which will hold our effects: GrayScaleFilter(Greyscale effect), NegativeFilter(InvertColor effect) and AutoLevelsFilter(ColorFix effect):
public
List<IFilter> filters =
new
List<IFilter>();
public
GrayscaleFilter grayFilter =
new
GrayscaleFilter();
public
AutoLevelsFilter autoLevelsFilter =
new
AutoLevelsFilter();
public
NegativeFilter negativeFilter =
new
NegativeFilter();
The next step would be to override the OnIsInvertedChanged(..), OnIsGrayScaleChanged(..) and OnIsColorFixedChanged(..) methods. Their implementation is similar, so we will go into details only for the first method:
protected
override
async
void
OnIsColorInvertedChanged(
bool
newValue,
bool
oldValue)
{
if
(newValue)
{
this
.IsGreyscale =
false
;
this
.IsColorFixed =
false
;
this
.ResetWorkingBitmap();
filters.Add(negativeFilter);
this
.ModifiedImage = await NokiaRenderer.Render(
this
.workingBitmap, filters);
}
else
{
this
.ResetWorkingBitmap();
filters.Clear();
this
.ModifiedImage =
this
.workingBitmap;
}
}
The three effects are not supposed to be combined so if this tool is selected, we should deselect the others. Resetting the image is needed because it could be previously modified by another effect. Then we add the NegativeFilter to the IFilter collection and render the image using our NokiaRenderer helper class. On the other hand, if the tool has been unselected, we should again reset the image and clear the filters. We will implement the other tools in a similar manner. See the full source below.
The last step is to override the ApplyCore(..) method which modifies the actual image. It is pretty straightforward as we should only render the actual image using our NokiaRenderer helper class:
protected
override
async Task<WriteableBitmap> ApplyCore(WriteableBitmap actualImage)
{
return
await NokiaRenderer.Render(actualImage,
this
.filters);
}
Finally, we can show the whole picture. Here’s the implementation of the CustomColorEffects class:
public
class
CustomColorEffects : ColorEffectsTool
{
public
List<IFilter> filters =
new
List<IFilter>();
public
GrayscaleFilter grayFilter =
new
GrayscaleFilter();
public
AutoLevelsFilter autoLevelsFilter =
new
AutoLevelsFilter();
public
NegativeFilter negativeFilter =
new
NegativeFilter();
protected
override
async
void
OnIsColorInvertedChanged(
bool
newValue,
bool
oldValue)
{
if
(newValue)
{
this
.IsGreyscale =
false
;
this
.IsColorFixed =
false
;
this
.ResetWorkingBitmap();
this
.filters.Add(negativeFilter);
this
.ModifiedImage = await NokiaRenderer.Render(
this
.workingBitmap, filters);
}
else
{
this
.ResetWorkingBitmap();
this
.filters.Clear();
this
.ModifiedImage =
this
.workingBitmap;
}
}
protected
override
async
void
OnIsGreyscaleChanged(
bool
newValue,
bool
oldValue)
{
if
(newValue)
{
this
.IsColorInverted =
false
;
this
.IsColorFixed =
false
;
this
.ResetWorkingBitmap();
this
.filters.Add(grayFilter);
this
.ModifiedImage = await NokiaRenderer.Render(
this
.workingBitmap, filters);
}
else
{
this
.ResetWorkingBitmap();
this
.filters.Clear();
this
.ModifiedImage =
this
.workingBitmap;
}
}
protected
override
async
void
OnIsColorFixedChanged(
bool
newValue,
bool
oldValue)
{
if
(newValue)
{
this
.IsColorInverted =
false
;
this
.IsGreyscale =
false
;
this
.ResetWorkingBitmap();
this
.filters.Add(autoLevelsFilter);
this
.ModifiedImage = await NokiaRenderer.Render(
this
.workingBitmap, filters);
}
else
{
this
.ResetWorkingBitmap();
this
.filters.Clear();
this
.ModifiedImage =
this
.workingBitmap;
}
}
protected
override
async Task<WriteableBitmap> ApplyCore(WriteableBitmap actualImage)
{
return
await NokiaRenderer.Render(actualImage,
this
.filters);
}
}
And here’s the XAML definition of our RadImageEditor control:
<
telerikImageEditor:RadImageEditor
Source
=
"LasVegas.jpg"
>
<
telerikImageEditor:RadImageEditor.Tools
>
<
local:CustomColorEffects
/>
</
telerikImageEditor:RadImageEditor.Tools
>
</
telerikImageEditor:RadImageEditor
>
The improved tool is ready for use!
The second part of this blog is dedicated on creating new tool for RadImageEditor. Again, we will take advantage of the fast algorithms of the Nokia Imaging SDK. Our goal is to create a Sketch Tool which will imitate the look of a sketched image.
We can inherit from ImageEditorTool and RangeTool classes. In this case we do not have values that can vary in a range, so our choice is the ImageEditorTool class.
The ImageEditorTool class requires implementation for its ApplyCore(..) method and for its Name property. But first, let’s shape the look of our Sketch Tool. RadImageEditor exposes ToolUISelector property which can helps us choose the default UI for our tool:
public
class
ToolUISelector : ImageEditorToolUISelector
{
public
DataTemplate SketchToolTemplate {
get
;
set
; }
public
override
System.Windows.DataTemplate SelectTemplate(
object
tool, System.Windows.DependencyObject container)
{
if
(tool
is
SketchTool)
{
return
this
.SketchToolTemplate;
}
return
base
.SelectTemplate(tool, container);
}
}
The tool should allow us to choose the SketchMode of the filter between Gray and Color. So here is an example:
<
local:ToolUISelector
x:Key
=
"selector"
>
<
local:ToolUISelector.SketchToolTemplate
>
<
DataTemplate
>
<
Grid
VerticalAlignment
=
"Bottom"
HorizontalAlignment
=
"Center"
Width
=
"200"
>
<
Grid.ColumnDefinitions
>
<
ColumnDefinition
Width
=
"*"
/>
<
ColumnDefinition
Width
=
"*"
/>
</
Grid.ColumnDefinitions
>
<
Grid.RowDefinitions
>
<
RowDefinition
Height
=
"100"
/>
<
RowDefinition
Height
=
"50"
/>
</
Grid.RowDefinitions
>
<
RadioButton
IsChecked
=
"{Binding IsGrayChecked, Mode=TwoWay}"
GroupName
=
"sketch"
Height
=
"100"
HorizontalAlignment
=
"Center"
/>
<
TextBlock
Grid.Row
=
"1"
Width
=
"50"
Text
=
"Gray"
HorizontalAlignment
=
"Center"
/>
<
RadioButton
IsChecked
=
"{Binding IsColorChecked, Mode=TwoWay}"
Grid.Column
=
"1"
Height
=
"100"
GroupName
=
"sketch"
HorizontalAlignment
=
"Center"
/>
<
TextBlock
HorizontalAlignment
=
"Center"
Width
=
"50"
Text
=
"Color"
Grid.Row
=
"1"
Grid.Column
=
"1"
/>
</
Grid
>
</
DataTemplate
>
</
local:ToolUISelector.SketchToolTemplate
>
</
local:ToolUISelector
>
We have a vision for the UI of our tool and now we can proceed with its implementation. Our UI requires that we have two properties to track the SketchMode(IsColorChecked and IsGrayChecked properties) of our effect. So, let’s see the entire implementation, then we will go into the details:
public
class
SketchTool : ImageEditorTool
{
public
List<IFilter> filters =
new
List<IFilter>();
public
SketchFilter sketchFilter =
new
SketchFilter();
public
SketchTool()
{
filters.Add(sketchFilter);
}
public
override
string
Name
{
get
{
return
"Sketch Tool"
; }
}
public
override
string
Icon
{
get
{
return
@
"/ImageEditorBlogPost;Component/Assets/sketch-light.png"
;}
}
private
bool
isGrayChecked;
public
bool
IsGrayChecked
{
get
{
return
this
.isGrayChecked;
}
set
{
if
(
this
.isGrayChecked == value)
{
return
;
}
this
.isGrayChecked = value;
if
(value)
{
this
.OnSketchModeChanged(SketchMode.Gray);
}
this
.OnPropertyChanged(
"IsGrayChecked"
);
}
}
private
bool
isColorChecked;
public
bool
IsColorChecked
{
get
{
return
this
.isColorChecked;
}
set
{
if
(
this
.isColorChecked == value)
{
return
;
}
this
.isColorChecked = value;
if
(value)
{
this
.OnSketchModeChanged(SketchMode.Color);
}
this
.OnPropertyChanged(
"IsColorChecked"
);
}
}
protected
override
async Task<System.Windows.Media.Imaging.WriteableBitmap> ApplyCore(System.Windows.Media.Imaging.WriteableBitmap actualImage)
{
actualImage = await NokiaRenderer.Render(actualImage, filters);
return
actualImage;
}
protected
async
void
OnSketchModeChanged(SketchMode mode)
{
this
.sketchFilter.SketchMode = mode;
this
.ResetWorkingBitmap();
this
.workingBitmap = await NokiaRenderer.Render(
this
.workingBitmap, filters);
this
.ModifiedImage =
this
.workingBitmap;
}
}
Finally we are ready to test our new tool:
<
telerikImageEditor:RadImageEditor
Source
=
"LasVegas.jpg"
ToolUISelector
=
"{StaticResource selector}"
>
<
telerikImageEditor:RadImageEditor.Tools
>
<
local:SketchTool
/>
</
telerikImageEditor:RadImageEditor.Tools
>
</
telerikImageEditor:RadImageEditor
>