Telerik blogs

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!

Create SketchTool using the Nokia Imaging SDK

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;
    }
 
}
  • First, following the Nokia Imaging SDK documentation, we will need a collection of filters to hold our SketchFilter.
  • Choose the name for our new tool through the Name property and choose an Icon through the Icon property
  • Create IsColorChecked and IsGrayChecked properties to track the SketchMode of our filter. When the value has been changed we immediately render the new look of the image.
  • The ApplyCore(..) method simply renders the actual image.
  • Optionally, we can add an icon for our tool overriding the Icon property.

 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>

 


Comments

Comments are disabled in preview mode.