The previous article introduced the DeForm Library: A WinRT component that used Direct2D to apply filters on a picture:
http://blogs.msdn.com/b/eternalcoding/archive/2012/08/13/creating-a-winrt-component-using-c-cx-deform-a-direct2d-effect-toolkit.aspx

The complete component can be found there:
https://deform.codeplex.com/
This article will show you how to use the Direct2D effect pipeline to create a custom Direct2D effect. This effect will try to apply some kind of Polaroïd effect by applying many filters in a row:
- Black&White
- Sepia tone
- Saturation
- Brightness
To do so, you have to create a COM component for Direct2D (handling reference counting and interfaces querying):
class PolaroidEffect: public IUnknown
{
public:
IFACEMETHODIMP_(ULONG) AddRef();
IFACEMETHODIMP_(ULONG) Release();
IFACEMETHODIMP QueryInterface(_In_ REFIID riid, _Outptr_ void** ppOutput);
private:
LONG m_refCount;
};
This class implements the IUnknown interface (which is the minimum you can do to be a COM component).
The associated code is obvious:
IFACEMETHODIMP_(ULONG) PolaroidEffect::AddRef()
{
m_refCount++;
return m_refCount;
}
IFACEMETHODIMP_(ULONG) PolaroidEffect::Release()
{
m_refCount--;
if (m_refCount == 0)
{
delete this;
return 0;
}
else
{
return m_refCount;
}
}
IFACEMETHODIMP PolaroidEffect::QueryInterface(_In_ REFIID riid, _Outptr_ void** ppOutput)
{
*ppOutput = nullptr;
HRESULT hr = S_OK;
if (riid == __uuidof(IUnknown))
{
*ppOutput = reinterpret_cast<IUnknown*>(this);
}
else
{
hr = E_NOINTERFACE;
}
if (*ppOutput != nullptr)
{
AddRef();
}
return hr;
}
Then you have to implement the ID2D1EffectImpl interface which is the root interface of every Direct2D effects:
class PolaroidEffect: public ID2D1EffectImpl
{
public:
IFACEMETHODIMP Initialize(
_In_ ID2D1EffectContext* pContextInternal,
_In_ ID2D1TransformGraph* pTransformGraph
);
IFACEMETHODIMP PrepareForRender(D2D1_CHANGE_TYPE changeType);
IFACEMETHODIMP SetGraph(_In_ ID2D1TransformGraph* pGraph);
static HRESULT Register(_In_ ID2D1Factory1* pFactory);
static HRESULT __stdcall CreateEffect(_Outptr_ IUnknown** ppEffectImpl);
IFACEMETHODIMP_(ULONG) AddRef();
IFACEMETHODIMP_(ULONG) Release();
IFACEMETHODIMP QueryInterface(_In_ REFIID riid, _Outptr_ void** ppOutput);
// Properties
float GetForce() const;
HRESULT SetForce(float force);
private:
PolaroidEffect();
LONG m_refCount;
float m_force;
ComPtr<ID2D1Effect> m_pColorMatrixBWEffect;
ComPtr<ID2D1Effect> m_pColorMatrixSepiaEffect;
ComPtr<ID2D1Effect> m_pBrightnessEffect;
ComPtr<ID2D1Effect> m_pHueEffect;
ComPtr<ID2D1Effect> m_pSaturationEffect;
ComPtr<ID2D1TransformNode> m_pSaturationTransform;
ComPtr<ID2D1TransformNode> m_pHueTransform;
ComPtr<ID2D1TransformNode> m_pBrightnessTransform;
ComPtr<ID2D1TransformNode> m_pColorMatrixBWTransform;
ComPtr<ID2D1TransformNode> m_pColorMatrixSepiaTransform;
};
The PolaroidEffect by itself has a Force property you can get/set with GetForce()/SetForce() methods.
The ID2D1EffectImpl interface adds the following methods:
- Initialize: This method is used to create the internal Direct2D objects
- PrepareForRender: This method is called just before rendering if the effect has been previously initialized but not yet drawn or a property has changed or something in the context has changed (for instance the DPI, etc.)
- SetGraph: This method is intended for composite effects which have variable number of inputs. We will get back to it in a future article
In our case, PrepareForRender and SetGraph are really simple:
IFACEMETHODIMP PolaroidEffect::PrepareForRender(D2D1_CHANGE_TYPE changeType)
{
return S_OK;
}
IFACEMETHODIMP PolaroidEffect::SetGraph(_In_ ID2D1TransformGraph* pGraph)
{
return E_NOTIMPL;
}
All the job will be done Inside the Initialize method:
IFACEMETHODIMP PolaroidEffect::Initialize(
_In_ ID2D1EffectContext* pEffectContext,
_In_ ID2D1TransformGraph* pTransformGraph
)
{
// Effects
// Create the b&w effect.
HRESULT hr = pEffectContext->CreateEffect(CLSID_D2D1ColorMatrix, &m_pColorMatrixBWEffect);
if (SUCCEEDED(hr))
{
D2D1_MATRIX_5X4_F matrix = D2D1::Matrix5x4F(
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);
m_pColorMatrixBWEffect->SetValue(D2D1_COLORMATRIX_PROP_COLOR_MATRIX, matrix);
}
// Create the sepia
hr = pEffectContext->CreateEffect(CLSID_D2D1ColorMatrix, &m_pColorMatrixSepiaEffect);
if (SUCCEEDED(hr))
{
D2D1_MATRIX_5X4_F matrix = D2D1::Matrix5x4F(
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);
m_pColorMatrixSepiaEffect->SetValue(D2D1_COLORMATRIX_PROP_COLOR_MATRIX, matrix);
}
// Create the saturation effect.
if (SUCCEEDED(hr))
{
hr = pEffectContext->CreateEffect(CLSID_D2D1Saturation, &m_pSaturationEffect);
}
if (SUCCEEDED(hr))
{
hr = m_pSaturationEffect->SetValue(D2D1_SATURATION_PROP_SATURATION, m_force);
}
// Create the brightness effect.
if (SUCCEEDED(hr))
{
hr = pEffectContext->CreateEffect(CLSID_D2D1Brightness, &m_pBrightnessEffect);
}
if (SUCCEEDED(hr))
{
hr = m_pBrightnessEffect->SetValue(D2D1_BRIGHTNESS_PROP_WHITE_POINT, D2D1::Vector2F(1.0f, 1.0f));
}
if (SUCCEEDED(hr))
{
hr = m_pBrightnessEffect->SetValue(D2D1_BRIGHTNESS_PROP_BLACK_POINT, D2D1::Vector2F(0.0f, 0.15f));
}
// Transforms
// Create the saturation transform from the saturation effect.
if (SUCCEEDED(hr))
{
hr = pEffectContext->CreateTransformNodeFromEffect(m_pSaturationEffect.Get(),
&m_pSaturationTransform);
}
// Create the brightness transform from the brightness effect.
if (SUCCEEDED(hr))
{
hr = pEffectContext->CreateTransformNodeFromEffect(m_pBrightnessEffect.Get(),
&m_pBrightnessTransform);
}
// Create the sepia transform from the sepia effect.
if (SUCCEEDED(hr))
{
hr = pEffectContext->CreateTransformNodeFromEffect(m_pColorMatrixBWEffect.Get(),
&m_pColorMatrixBWTransform);
}
// Create the sepia transform from the sepia effect.
if (SUCCEEDED(hr))
{
hr = pEffectContext->CreateTransformNodeFromEffect(m_pColorMatrixSepiaEffect.Get(),
&m_pColorMatrixSepiaTransform);
}
// Register transforms with the effect graph.
if (SUCCEEDED(hr))
{
hr = pTransformGraph->AddNode(m_pSaturationTransform.Get());
}
if (SUCCEEDED(hr))
{
hr = pTransformGraph->AddNode(m_pBrightnessTransform.Get());
}
if (SUCCEEDED(hr))
{
hr = pTransformGraph->AddNode(m_pColorMatrixBWTransform.Get());
}
if (SUCCEEDED(hr))
{
hr = pTransformGraph->AddNode(m_pColorMatrixSepiaTransform.Get());
}
// Connect the custom effect’s input to the shadow transform’s input.
if (SUCCEEDED(hr))
{
hr = pTransformGraph->ConnectToEffectInput(
0, // Input index of the effect.
m_pColorMatrixBWTransform.Get(), // The receiving transform.
0 // Input index of the receiving transform.
);
}
// Connect nodes
if (SUCCEEDED(hr))
{
hr = pTransformGraph->ConnectNode(
m_pColorMatrixBWTransform.Get(), // ‘From’ node.
m_pColorMatrixSepiaTransform.Get(), // ‘To’ node.
0 // Input index of the ‘to’ node.
);
}
if (SUCCEEDED(hr))
{
hr = pTransformGraph->ConnectNode(
m_pColorMatrixSepiaTransform.Get(), // ‘From’ node.
m_pBrightnessTransform.Get(), // ‘To’ node.
0 // Input index of the ‘to’ node.
);
}
if (SUCCEEDED(hr))
{
hr = pTransformGraph->ConnectNode(
m_pBrightnessTransform.Get(), // ‘From’ node.
m_pSaturationTransform.Get(), // ‘To’ node.
0 // Input index of the ‘to’ node.
);
}
// Connect the transform’s output to the custom effect’s output.
if (SUCCEEDED(hr))
{
hr = pTransformGraph->SetOutputNode(
m_pSaturationTransform.Get()
);
}
return hr;
}
The code creates every filter, gets the transform interface of each filter and connects the transforms like this:
- Input to black and white transform
- Black and white to sepia
- Sepia to brightness
- Brightness to saturation
- Saturation to output
Simple, isn’t it?
Then you have to create a method to register your effect with the factory:
DEFINE_GUID(CLSID_Polaroid, 0x59820389, 0xbbd5, 0x40e8, 0x9a, 0xf2, 0x30, 0x9b, 0xb2, 0x94, 0xb3, 0x95);
HRESULT PolaroidEffect::Register(_In_ ID2D1Factory1* pFactory)
{
PCWSTR propertyXml =
XML(
<?xml version='1.0'?>
<Effect>
<!-- System Properties -->
<Property name='DisplayName' type='string' value='Polaroïd Effect'/>
<Property name='Author' type='string' value='David Catuhe'/>
<Property name='Category' type='string' value='Bitmap Effect'/>
<Property name='Description' type='string' value='Apply a Polaroïd like effect.'/>
<Inputs>
<Input name='Source'/>
</Inputs>
<!-- Effect-specific Properties -->
<Property name='Force' type='float' value='0'>
<Property name='DisplayName' type='string' value='Force value'/>
<Property name='Default' type='float' value='1.0'/>
</Property>
</Effect>
);
D2D1_PROPERTY_BINDING bindings[] =
{
D2D1_VALUE_TYPE_BINDING(L"Force", &SetForce, &GetForce),
};
// Register the effect using the data defined above.
return pFactory->RegisterEffectFromString(
CLSID_Polaroid,
propertyXml,
bindings,
ARRAYSIZE(bindings),
CreateEffect
);
}
HRESULT __stdcall PolaroidEffect::CreateEffect(_Outptr_ IUnknown** ppEffectImpl)
{
*ppEffectImpl = static_cast<ID2D1EffectImpl*>(new PolaroidEffect());
if (*ppEffectImpl == nullptr)
{
return E_OUTOFMEMORY;
}
return S_OK;
}
The goal of the Register method is to create a XML description string. This XML describes the effect and each parameter handled by the effect.
In our case, we can expose the Force parameter through the GetForce()/SetForce() methods:
float PolaroidEffect::GetForce() const
{
return m_force;
}
HRESULT PolaroidEffect::SetForce(float force)
{
m_force = force;
return m_pSaturationEffect->SetValue(D2D1_SATURATION_PROP_SATURATION, m_force);
}
Please note the use of the CLSID_Polaroid in order to uniquely identify the effect when you call CreateEffect:
// Create the effect
ComPtr<ID2D1Effect> d2dEffect;
Tools::Check(
context->CreateEffect(CLSID_Polaroid, &d2dEffect)
);
So finally to allow DeForm to instantiate this new effect, you just have to call the following code:
// Register the custom Polaroïd effect.
Tools::Check(
PolaroidEffect::Register(m_d2dFactory.Get())
);
The next article will be about creating custom effects using HLSL shader code!