Creating a WinRT component using C++/CX: DeForm, a Direct2D effect toolkit

Back to my first love, I’m thrilled to present you DeForm which is a WinRT component that uses Direct2D to apply bitmap effect to a source image.

The library and a sample C# client can be found there:

https://deform.codeplex.com/

 

The goal of this article is to describe how I proceeded to write DeForm component.

Revised on 2012/08/24: Thanks to James McNellis, I fixed some allocation bugs.

Creating a WinRT component

A WinRT component is a DLL you can call from any WinRT compliant language (C++, C#, VB.NET and Javascript).

You can create WinRT components using C++ or a .NET language.

Details can be found there:

https://msdn.microsoft.com/en-us/library/windows/apps/hh441572(v=vs.110).aspx.aspx “https://msdn.microsoft.com/en-us/library/windows/apps/hh441572(v=vs.110).aspx")

For DeForm, I decided to create my component using C++ because I wanted to use Direct2D in a really efficient way.

Actually, I used Visual C++ component extensions (C++/CX) which is a set of extensions to the C++ language that enable the creation of WinRT in a way that is close to modern C++.

To do so, you just have to create a new C++ project for Windows Store in Visual Studio 2012:

This project will generate a .dll and a .winmd files. These files are required if you want to reference your code from a WinRT compliant language:

https://msdn.microsoft.com/en-us/library/windows/apps/hh441569(v=vs.110).aspx.aspx “https://msdn.microsoft.com/en-us/library/windows/apps/hh441569(v=vs.110).aspx")

A WinRT component written with C++/CX can contains standard native code and specific code for the WinRT component.

For instance the following class is a standard C++ class:

class Direct2DManager
{
}

And the following class is a WinRT component:

namespace DeForm
{
    public ref class ImageManipulator sealed
    {
    }
}

To be considered as a WinRT component the ImageManipulator class is defined as ref and sealed and must take place inside a namespace.

The class is then projected to WinRT meaning it will become a component available to C#, VB.NET and Javascript.

The ImageManipulator class is the main component of my DeForm Library and is completely defined as follows:

#pragma once

#include "Windows.UI.Xaml.Media.DXInterop.h"
#include "IEffectDescription.h"
#include "Effect.h"

using namespace Windows::Storage::Streams;
using namespace Windows::UI::Xaml::Media::Imaging;
using namespace Windows::Foundation::Collections;
using namespace Platform::Collections;

namespace DeForm
{
    public ref class ImageManipulator sealed
    {
        // Members
        std::vector<Effect>                effectDescriptions;
        ComPtr<ID2D1Effect>                    m_source;
        SurfaceImageSource^                    m_surfaceImageSource;        
        ComPtr<ISurfaceImageSourceNative>    m_nativeSurfaceImageSource;
        ComPtr<ID2D1Image>                    m_d2dImage;
        UINT                                m_frameWidth;
        UINT                                m_frameHeight;

        // Methods
        ComPtr<IWICFormatConverter> GetConverter(IRandomAccessStream^ sourceStream);

    public:
        ImageManipulator(IRandomAccessStream^ sourceStream);
        virtual ~ImageManipulator();

        void AddEffectDescription(IEffectDescription ^effectDescription);
        void RemoveEffectDescription(IEffectDescription ^effectDescription);
        void ClearEffectDescriptions();

        SurfaceImageSource^ CreateSurfaceImageSource();
        void DeleteSurfaceImageSource();

        void Manipulate(bool updateSurfaceImageSource);

        void SaveToStream(IRandomAccessStream^ destinationStream);
        void UpdateSurfaceImageSource();
    };
}

As you can see, a WinRT component can contain native pointers (std::vector, ComPtr) but all the public members must be built-in types, WinRT interfaces or WinRT components.

In this case, The ImageManipulator class uses the following components:

  • IEffectDescription: An interface used to define an effect to apply to a bitmap
  • SurfaceImageSource: A XAML control (which is obviously a WinRT component) used to display a Direct2D image

 

Using the ImageManipulator for C# can be like this:

var imageManipulator = new ImageManipulator(stream);
imageManipulator.AddEffectDescription(new GaussianBlurEffectDescription());
myImageControl.Source = imageManipulator.CreateSurfaceImageSource();
imageManipulator.Manipulate(true);

In this sample, myImageControl is a XAML image control and stream is a IRandomAccessStream produced from a file for instance.

We will see later how to develop the GaussianBlurEffectDescription.

Initializing Direct2D

Before creating our effects, we need to instantiate Direct2D objects. I decided to create a Direct2DManager native class to centralize the access to Direct2D in one class using a singleton:

#pragma once

using namespace Microsoft::WRL;
using namespace Windows::Foundation::Collections;
using namespace Platform::Collections;

class Direct2DManager
{
    // Instance
    static Direct2DManager*                m_instance;

    // D2D members
    ComPtr<ID2D1Factory1>                m_d2dFactory;
    ComPtr<IWICImagingFactory2>            m_wicFactory;
    ComPtr<IDXGIDevice>                    m_dxgiDevice;
    ComPtr<ID2D1Device>                    m_d2dDevice;
    ComPtr<ID2D1DeviceContext>            m_d2dContext;

    // Private constructor
    Direct2DManager(void);

public:

    // Instance
    static Direct2DManager* GetInstance();

    // Properties
    inline ComPtr<IWICImagingFactory2> GetWICFactory()
    {
        return m_wicFactory;
    }

    inline ComPtr<ID2D1Device> GetD2DDevice()
    {
        return m_d2dDevice;
    }

    inline ComPtr<ID2D1DeviceContext> GetD2DContext()
    {
        return m_d2dContext;
    }

    inline ComPtr<IDXGIDevice> GetDXGIDevice()
    {
        return m_dxgiDevice;
    }
};

As you can see, this class is mainly responsible of giving access to Direct2D objects. All the work is made by the constructor:

#include "pch.h"
#include "Direct2DManager.h"

using namespace D2D1;

Direct2DManager* Direct2DManager::m_instance;

Direct2DManager::Direct2DManager(void)
{
    D2D1_FACTORY_OPTIONS options;
    ZeroMemory(&options, sizeof(D2D1_FACTORY_OPTIONS));

#if defined(_DEBUG)
    options.debugLevel = D2D1_DEBUG_LEVEL_INFORMATION;
#endif

    // D2D Factory
    Tools::Check(
        D2D1CreateFactory(
        D2D1_FACTORY_TYPE_SINGLE_THREADED,
        __uuidof(ID2D1Factory1),
        &options,
        &m_d2dFactory
        )
        );

    // WIC Factory
    Tools::Check(
        CoCreateInstance(
        CLSID_WICImagingFactory,
        nullptr,
        CLSCTX_INPROC_SERVER,
        IID_PPV_ARGS(&m_wicFactory)
        )
        );

    // D3D stuff
    UINT creationFlags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;    

#if defined(_DEBUG)
    creationFlags |= D3D11_CREATE_DEVICE_DEBUG;
#endif

    D3D_FEATURE_LEVEL featureLevels[] =
    {
        D3D_FEATURE_LEVEL_11_1,
        D3D_FEATURE_LEVEL_11_0,
        D3D_FEATURE_LEVEL_10_1,
        D3D_FEATURE_LEVEL_10_0,
        D3D_FEATURE_LEVEL_9_3,
        D3D_FEATURE_LEVEL_9_2,
        D3D_FEATURE_LEVEL_9_1
    };

    ComPtr<ID3D11Device> device;
    ComPtr<ID3D11DeviceContext> context;
    D3D_FEATURE_LEVEL returnedFeatureLevel;

    Tools::Check(
        D3D11CreateDevice(
            nullptr,                  
            D3D_DRIVER_TYPE_HARDWARE,
            0,                        
            creationFlags,            
            featureLevels,            
            ARRAYSIZE(featureLevels), 
            D3D11_SDK_VERSION,        
            &device,                  
            &returnedFeatureLevel,    
            &context                  
            )
        );

    Tools::Check(
        device.As(&m_dxgiDevice)
        );

    Tools::Check(
        m_d2dFactory->CreateDevice(m_dxgiDevice.Get(), &m_d2dDevice)
        );

    Tools::Check(
        m_d2dDevice->CreateDeviceContext(
            D2D1_DEVICE_CONTEXT_OPTIONS_NONE,
            &m_d2dContext
            )
        );
}

Direct2DManager* Direct2DManager::GetInstance()
{
    if (m_instance == NULL)
    {
        m_instance = new Direct2DManager();
    }

    return m_instance;
}

As Direct2D is based on Direct3D, you need to instantiate a Direct3D device. Then you have to create a Direct2D device and context.

To learn more about Direct2D, you can go there:

https://msdn.microsoft.com/en-us/library/windows/desktop/dd370990(v=vs.85).aspx.aspx “https://msdn.microsoft.com/en-us/library/windows/desktop/dd370990(v=vs.85).aspx")

Please note that I also created a WIC (Windows Imaging Component) factory object in order to help me manipulate (load, transform and save) pictures. To learn more about WIC, please go there:

https://msdn.microsoft.com/en-us/library/windows/desktop/ee719655(v=vs.85).aspx.aspx “https://msdn.microsoft.com/en-us/library/windows/desktop/ee719655(v=vs.85).aspx")

Direct2D Effects

The Direct2D effects API is an addition to Direct2D that provides image effect processing. The great thing about Direct2D effects is that they are GPU accelerated!

For DeForm, I decided to create a WinRT component for each effect I want to support. These components allow the user to configure effects added to the effects pipeline. Indeed, you can chain Direct2D effects to apply more complex transformations to a picture.

Every effect has to implement the following interface:

#pragma once

#include "EffectParameterValue.h"

namespace DeForm
{
    public enum class EffectType {Blur, ConvolveMatrix, ColorMatrix, Morphology};

    public interface class IEffectDescription
    {
        EffectType GetType();
        int GetParametersCount();
        EffectParameterValue^ GetParameter(int index);
    };
}

A Direct2D effect is created with the following native code (context is the Direct2D context):

    ComPtr<ID2D1Effect> d2dEffect;
    Tools::Check(
        context->CreateEffect(CLSID_D2D1GaussianBlur, &d2dEffect)
        );

You can notice that the CreateEffect method requires a GUID (D2D1GaussianBlur) to identify the effect. So basically every IEffectDescription should have to return a GUID. But it is not permitted for WinRT components because GUID is a non built-in native type.

So every IEffectDescription must have a GetType method which returns a EffectType enum:

public enum class EffectType {Blur, ConvolveMatrix, ColorMatrix, Morphology};

This enum is defined to be compatible with WinRT.

Using this enum, a tool method returns the associated GUIDs:

inline GUID GetEffectGUID(EffectType type)
{
    switch (type)
    {
        case EffectType::Blur:
            return CLSID_D2D1GaussianBlur;
        case EffectType::ConvolveMatrix:
            return CLSID_D2D1ConvolveMatrix;
        case EffectType::ColorMatrix:
            return CLSID_D2D1ColorMatrix;
        case EffectType::Morphology:
            return CLSID_D2D1Morphology;
        default:
            break;
    }
}

In a same way, every effect has parameters which are defined by a key (an int enum) and a value that can be an int, a float, a boolean, a vector2 or an array of float.

To expose these parameters, I added a new WinRT class called EffectParameterValue:

#pragma once

#include "pch.h"
#include "IEffectDescription.h"
#include "Direct2DManager.h"
#include "Vector2.h"

using namespace Platform;

namespace DeForm
{
    public enum class EffectParameterType {Float, Int, FloatArray, Vector2, Bool};

    union ValueType
    {
        int     asInt;
        float   asFloat[2];
        bool    asBool;
    };

    public ref class EffectParameterValue sealed
    {
        int                  m_key;
        EffectParameterType  m_type;
        ValueType            m_value;
        Array<float>^        m_array;

    public:
        EffectParameterValue(int key, int value);
        EffectParameterValue(int key, float value);
        EffectParameterValue(int key, const Array<float>^ array);
        EffectParameterValue(int key, Vector2^ vector);
        EffectParameterValue(int key, bool value);

        property EffectParameterType Type {
            EffectParameterType get() {
                return m_type;
            }
        }

        property int Key {
            int get() {
                return m_key;
            }
        }

        property int ValueAsInt {
            int get() {
                return m_value.asInt;
            }
        }

        property float ValueAsFloat {
            float get() {
                return m_value.asFloat[0];
            }
        }

        property Array<float>^ ValueAsArray {
            Array<float>^ get() {
                return m_array;
            }
        }

        property bool ValueAsBool {
            bool get() {
                return m_value.asBool;
            }
        }
    };
}

This class uses a union to store standard values, a WinRT array to store float array and a WinRT enum to define the type of the stored value. The code associated is as follows:

#include "pch.h"
#include "EffectParameterValue.h"


DeForm::EffectParameterValue::EffectParameterValue(int key, int value): m_key(key), 
m_type(EffectParameterType::Int) { m_value.asInt = value; } DeForm::EffectParameterValue::EffectParameterValue(
int key, float value): m_key(key),
m_type(EffectParameterType::Float) { m_value.asFloat[0] = value; } DeForm::EffectParameterValue::EffectParameterValue(
int key, const Array<float>^ value): m_key(key),
m_type(EffectParameterType::FloatArray) { m_array =
ref new Array<float>(value); } DeForm::EffectParameterValue::EffectParameterValue(int key, Vector2^ vector): m_key(key),
m_type(EffectParameterType::Vector2) { m_value.asFloat[0] = vector->X; m_value.asFloat[1] = vector->Y; } DeForm::EffectParameterValue::EffectParameterValue(
int key, bool value): m_key(key),
m_type(EffectParameterType::Bool) { m_value.asBool = value; }

So defining a new effect can be done like that:

#pragma once

namespace DeForm
{
    public enum struct MorphologyMode { Erode, Dilate};

    public ref class MorphologyEffectDescription sealed : IEffectDescription
    {
    public:        
        MorphologyEffectDescription(MorphologyMode mode, float width, float height);
        MorphologyEffectDescription();

        virtual EffectType GetType()
        {
            return EffectType::Morphology;
        }

        virtual int GetParametersCount()
        {
            return 3;
        }

        virtual EffectParameterValue^ GetParameter(int index)
        {
            switch (index)
            {
            case 0:
                return ref new EffectParameterValue(D2D1_MORPHOLOGY_PROP_MODE, (int)Mode);
            case 1:
                return ref new EffectParameterValue(D2D1_MORPHOLOGY_PROP_WIDTH, Width);
            case 2:
                return ref new EffectParameterValue(D2D1_MORPHOLOGY_PROP_HEIGHT, Height);
            }

            return nullptr;
        }

        property MorphologyMode Mode;
        property float Width;
        property float Height;
    };
}

Using the new C++/CX extensions, it is pretty simple to create properties (https://msdn.microsoft.com/en-us/library/windows/apps/hh755807(v=vs.110).aspx.aspx “https://msdn.microsoft.com/en-us/library/windows/apps/hh755807(v=vs.110).aspx"))

 

The ImageManipulator class

The ImageManipulator stores all activated effects using the following methods:

void DeForm::ImageManipulator::AddEffectDescription(IEffectDescription ^effectDescription)
{
    auto effect = new Effect(effectDescription);
    effectDescriptions.push_back(effect);
}

void DeForm::ImageManipulator::RemoveEffectDescription(IEffectDescription ^effectDescription)
{
    int index;
    auto ite = effectDescriptions.begin();
    auto endIte = effectDescriptions.end();

    for (int index = 0; ite != endIte; index++, ite++)
    {
        Effect effect = *ite;
        if (effect.GetEffectDescription() == effectDescription)
        {
            effectDescriptions.erase(ite);
            return;
        }
    }
}

You can note that every IEffectDescription is embedded in a Effect native class. This class is used to connect Direct2D effects (the WinRT component is unable to do that because it cannot expose native types (such as Direct2D COM interfaces)).

When the user calls Manipulate, the following code is executed:

void DeForm::ImageManipulator::Manipulate(bool updateSurfaceImageSource)
{    
    // Effets
    ComPtr<ID2D1Effect> previousD2DEffect = m_source;
    std::for_each(effectDescriptions.begin(), effectDescriptions.end(), 
[&previousD2DEffect](
Effect effect) { previousD2DEffect = effect.Apply(previousD2DEffect); }); previousD2DEffect->GetOutput(&m_d2dImage); if (updateSurfaceImageSource) UpdateSurfaceImageSource(); }

The key point here is the usage of the Apply method of the Effect class:

ComPtr<ID2D1Effect> Effect::Apply(ComPtr<ID2D1Effect> previousD2DEffect)
{
    auto context = Direct2DManager::GetInstance()->GetD2DContext();

        // Create the effect
    ComPtr<ID2D1Effect> d2dEffect;
    Tools::Check(
        context->CreateEffect(Tools::GetEffectGUID(m_effectDescription->GetType()), &d2dEffect)
        );

    d2dEffect->SetInputEffect(0, previousD2DEffect.Get());

    // Properties
    for (int index = 0; index < m_effectDescription->GetParametersCount(); index++)
    {
        auto parameter = m_effectDescription->GetParameter(index);

        switch (parameter->Type)
        {
        case EffectParameterType::Float:
            Tools::Check(
                d2dEffect->SetValue(parameter->Key, parameter->ValueAsFloat)
                );
            break;
        case EffectParameterType::Int:
            Tools::Check(
                d2dEffect->SetValue(parameter->Key, parameter->ValueAsInt)
                );
            break;
        case EffectParameterType::Bool:
            Tools::Check(
                d2dEffect->SetValue(parameter->Key, (BOOL)parameter->ValueAsBool)
                );
            break;
        case EffectParameterType::FloatArray:
            auto vector = parameter->ValueAsArray;
            UINT byteArraySize = vector->Length * sizeof(float);
            auto byteArray = std::vector<byte>(byteArraySize);

            memcpy(&byteArray[0], &vector[0], byteArraySize);

            Tools::Check(
                d2dEffect->SetValue(parameter->Key, &byteArray[0], byteArraySize)
                );

            delete[] byteArray;
        }
    }

    return d2dEffect.Get();
}

The ImageManipulator class is created using a stream (the picture to load) thanks to WIC:

DeForm::ImageManipulator::ImageManipulator(IRandomAccessStream^ sourceStream)
{
    auto context = Direct2DManager::GetInstance()->GetD2DContext();
    auto wicFactory = Direct2DManager::GetInstance()->GetWICFactory();

    // Converter
    ComPtr<IWICFormatConverter> converter = GetConverter(sourceStream);

    // Create source effect
    Tools::Check(
        context->CreateEffect(CLSID_D2D1BitmapSource, &m_source)
        );


    Tools::Check(
        m_source->SetValue(D2D1_BITMAPSOURCE_PROP_WIC_BITMAP_SOURCE, converter.Get())
        );

Tools::Check( m_source->SetValue(D2D1_PROPERTY_CACHED, TRUE) );

    // Size        
    converter->GetSize(&m_frameWidth, &m_frameHeight);
}

 

Then, the UpdateSurfaceImageSource method of the ImageManipulator is in charge of updating the SurfaceImageSource:

void DeForm::ImageManipulator::UpdateSurfaceImageSource()
{
    if (m_nativeSurfaceImageSource == nullptr)
        throw Platform::NullReferenceException::CreateException(1);

    auto context = Direct2DManager::GetInstance()->GetD2DContext();

    RECT updateRect;
    POINT offset;

    updateRect.left = 0;
    updateRect.top = 0;
    updateRect.right = m_frameWidth - 1;
    updateRect.bottom = m_frameHeight - 1;

    offset.x = 0;
    offset.y = 0;

    ComPtr<IDXGISurface> surface;
    Tools::Check(
        m_nativeSurfaceImageSource->BeginDraw(updateRect, &surface, &offset)
        );

    ComPtr<ID2D1Bitmap1> targetBitmap;
    D2D1_BITMAP_PROPERTIES1 bitmapProperties = {};
    bitmapProperties.pixelFormat.format = DXGI_FORMAT_B8G8R8A8_UNORM;
    bitmapProperties.pixelFormat.alphaMode = D2D1_ALPHA_MODE_PREMULTIPLIED;
    bitmapProperties.bitmapOptions = D2D1_BITMAP_OPTIONS_TARGET | D2D1_BITMAP_OPTIONS_CANNOT_DRAW;
    bitmapProperties.colorContext = nullptr;
    bitmapProperties.dpiX = Windows::Graphics::Display::DisplayProperties::LogicalDpi;
    bitmapProperties.dpiY = Windows::Graphics::Display::DisplayProperties::LogicalDpi;

    Tools::Check(
        context->CreateBitmapFromDxgiSurface(
        surface.Get(),
        &bitmapProperties,
        &targetBitmap
        )
        );

    context->SetTarget(targetBitmap.Get());

    context->BeginDraw();

    context->DrawImage(
        m_d2dImage.Get(),
        D2D1_INTERPOLATION_MODE_LINEAR,
        D2D1_COMPOSITE_MODE_SOURCE_COPY
        );

    context->EndDraw();

    m_nativeSurfaceImageSource->EndDraw();

    context->SetTarget(nullptr);
    targetBitmap = nullptr;
    surface = nullptr;
}

Please note that the ImageManipulator provides the CreateSurfaceImageSource method to create the SurfaceImageSource:

SurfaceImageSource^ DeForm::ImageManipulator::CreateSurfaceImageSource()
{
    if (m_surfaceImageSource == nullptr)
    {
        auto context = Direct2DManager::GetInstance()->GetD2DContext();

        m_surfaceImageSource = ref new SurfaceImageSource(m_frameWidth, m_frameHeight);
        IInspectable* inspectable = (IInspectable*) reinterpret_cast<IInspectable*>(m_surfaceImageSource);
        inspectable->QueryInterface(__uuidof(ISurfaceImageSourceNative), 
(
void **)&m_nativeSurfaceImageSource); m_nativeSurfaceImageSource->SetDevice(Direct2DManager::GetInstance()->GetDXGIDevice().Get()); } return m_surfaceImageSource; }

The SurfaceImageSource must be cast to IInspectable (every WinRT component derives from IInspectable interface which in turn derives from IUnknown) to get access to QueryInterface to get acess to ISurfaceImageSourceNative to connect the SurfaceImageSource with our Direct2D pipeline.

Finally you can call the SaveToStream method to save the picture to a stream (thanks to WIC):

void DeForm::ImageManipulator::SaveToStream(IRandomAccessStream^ destinationStream)
{
    auto wicFactory = Direct2DManager::GetInstance()->GetWICFactory();
    auto d2dDevice = Direct2DManager::GetInstance()->GetD2DDevice();

    // Output stream
    ComPtr<IStream> outputStream;
    Tools::Check(
        CreateStreamOverRandomAccessStream(destinationStream, IID_PPV_ARGS(&outputStream))
        );

    // Png encoder
    ComPtr<IWICBitmapEncoder> wicBitmapEncoder;
    Tools::Check(
        wicFactory->CreateEncoder(
        GUID_ContainerFormatPng,
        nullptr,
        &wicBitmapEncoder
        )
        );

    Tools::Check(
        wicBitmapEncoder->Initialize(
        outputStream.Get(),
        WICBitmapEncoderNoCache
        )
        );

    // Frame encoder
    ComPtr<IWICBitmapFrameEncode> wicFrameEncode;
    Tools::Check(
        wicBitmapEncoder->CreateNewFrame(
        &wicFrameEncode,
        nullptr     
        )
        );

    Tools::Check(
        wicFrameEncode->Initialize(nullptr)
        );

    // Create IWICImageEncoder.
    ComPtr<IWICImageEncoder> imageEncoder;
    Tools::Check(
        wicFactory->CreateImageEncoder(
        d2dDevice.Get(),
        &imageEncoder
        )
        );

    Tools::Check(
        imageEncoder->WriteFrame(
        m_d2dImage.Get(),
        wicFrameEncode.Get(),
        nullptr 
        )
        );

    Tools::Check(
        wicFrameEncode->Commit()
        );

    Tools::Check(
        wicBitmapEncoder->Commit()
        );

    // Flush all memory buffers to the next-level storage object.
    Tools::Check(
        outputStream->Commit(STGC_DEFAULT)
        );
}

Using DeForm from C# or Javascript

To use the DeForm Library, you just have to reference the .winmd file from your client project:

Please note that you will become depend on a specific architecture (x86, x64 or Arm) when you reference a C++/CX WinRT component.

You have to provide a specific version of your component for each architecture.

The DeForm Codeplex project

The current version of the DeForm project supports the following effects (which can be mixed):

Gaussian Blur

imageManipulator.AddEffectDescription(
new GaussianBlurEffectDescription(2.0f, GaussianBlurOptimization.Quality, GaussianBlurBorderMode.Hard));

Convolve Matrix

 

var convolve = new ConvolveMatrixEffectDescription();
convolve.SetKernelMatrix(new[] { -1.0f, -1.0f, -1.0f, -1.0f, 9.0f, -1.0f, -1.0f, -1.0f, -1.0f }, 3, 3);

imageManipulator.AddEffectDescription(convolve);

Black and White

// B&W
imageManipulator.AddEffectDescription(new ColorMatrixEffectDescription(
    new[] {
            0.2125f, 0.2125f, 0.2125f, 0.0f,
            0.7154f, 0.7154f, 0.7154f, 0.0f,
            0.0721f, 0.0721f, 0.0721f, 0.0f,
            0.0f, 0.0f, 0.0f, 1.0f,
            0.0f, 0.0f, 0.0f, 0.0f
    },
    ColorMatrixAlphaMode.Straight, true));

Sepia

// B&W
imageManipulator.AddEffectDescription(new ColorMatrixEffectDescription(
    new[] {
            0.2125f, 0.2125f, 0.2125f, 0.0f,
            0.7154f, 0.7154f, 0.7154f, 0.0f,
            0.0721f, 0.0721f, 0.0721f, 0.0f,
            0.0f, 0.0f, 0.0f, 1.0f,
            0.0f, 0.0f, 0.0f, 0.0f
    },
    ColorMatrixAlphaMode.Straight, true)); 

// Sepia
imageManipulator.AddEffectDescription(new ColorMatrixEffectDescription(
    new[] {
            0.90f, 0.0f, 0.0f, 0.0f,
            0.0f, 0.70f, 0.0f, 0.0f,
            0.0f, 0.0f, 0.30f, 0.0f,
            0.0f, 0.0f, 0.0f, 1.0f,
            0.0f, 0.0f, 0.0f, 0.0f
    },
    ColorMatrixAlphaMode.Straight, true));

Morphology

imageManipulator.AddEffectDescription(new MorphologyEffectDescription(MorphologyMode.Dilate, 1.0f, 0.0f));