13 Paint.NET Effects and a Framework to create your own

I've created 13 "new" Paint.NET effects (9 adjustments and 4 effects) and a framework to create your own effects more easily. Download Vandermotten.PaintDotNetEffects from my download page. Full source code is included in the download.

The nine adjustments are:

  • Cyanotype: the classical blueprint look, see http://en.wikipedia.org/wiki/Cyanotype. This actually is a demonstration of the Monochrome Ink on Paper adjustment, see below.
  • Darken: darkens an image, simple demo of the framework. See also below.
  • Lighten: lightens an image, simple demo of the framework.
  • Duotone Ink on Paper: see http://en.wikipedia.org/wiki/Duotone. Set the two ink colors using the Colors window.
  • Duotone Light: a variation on the traditional duotone, where two colored light sources are mixed additively. Set the two light colors using the colors window.
  • Grayscale on Colored Paper: well, it looks like a grayscale image on colored paper :-). Set the paper color using the colors window. Or maybe it's a black and white image projected with a colored light...
  • Monochrome Ink on Paper: looks like a grayscale image printed with one color ink. Set the ink color using the colors window.
  • Negative: duplicate of the built-in Invert Colors adjustment, provided as a very simple demo of the framework. See also below.
  • Sepia: simple demo of the Grayscale on Colored Paper adjustment, configurable though.

And four effects:

  • Drop Shadow: creates a drop shadow under an image with transparency. Offset, widening and shadow blur can be controlled. The widening option also allows creating outlines around images. Shadow color is set using the colors window before starting the effect.
  • Fade Edge: a simple demo of the framework. Fades the edge of an image. Width and power can be controlled.
  • Average Blur: averages all pixels within a controlled radius.
  • Smart Blur: averages pixels in a controlled radius if their color is close enough to the center color. This is very useful to reduce aliasing from scanned prints, especially of cartoon-like images.

So how do you create an effect with the framework? Let's starts with a very simple adjustment example: negative. An adjustment processes one pixel at a time, where one color is transformed into another color, no matter where the color appeared in the original image. In essence, an adjustment is a function that takes one color parameter, and returns another one.

public delegate ColorBgra Adjustment(ColorBgra source);

To create a Paint.NET adjustment effect, create a dll project and add references to PaintDotNet.Base, PaintDotNet.Core, PaintDotNet.Effects, and ... Vandermotten.PaintDotNetEffects. Create a class inheriting from Vandermotten.PaintDotNetEffects.SmartAdjustment:

using PaintDotNet;
using PaintDotNet.Effects;
using Vandermotten.PaintDotNetEffects;

[EffectCategory(EffectCategory.Adjustment)]
public class NegativeAdjustment : SmartAdjustment
{
public NegativeAdjustment()
: base("Negative")
{
}

protected override Adjustment AdjustmentToRender
{
get
{
return c => new ColorBgra()
{
B = (byte)(255 - c.B),
G = (byte)(255 - c.G),
R = (byte)(255 - c.R),
A = c.A
};
}
}
}

Attribute the class with the EffectCategory attribute, to make it appear under the adjustments menu. In the constructor, call the base constructor to set a name, and optionally an icon, submenu and effect flags. All you need to do now is override the AdjustmentToRender property. Return a function that transforms one color into another and you're done.

Notice also how easy it becomes to unit test your adjustments. Just call the function returned by AdjustmentToRender from your unit tests and validate the results.

And what about configurable adjustments, such as Darken? Rick has made developing configurable effects much easier than it used to be, but SmartProperties go one step further:

[EffectCategory(EffectCategory.Adjustment)] 
public class DarkenAdjustment : SmartAdjustment
{
public DarkenAdjustment()
: base("Darken", null, EffectFlags.Configurable)
{
}

SmartDoubleProperty factor = new SmartDoubleProperty("Factor")
{
DefaultValue = 1.5,
MinValue = 1,
MaxValue = 16
};

protected override IEnumerable<ISmartProperty> GetProperties()
{
return new ISmartProperty[] { factor };
}

protected override Adjustment AdjustmentToRender
{
get
{
if (factor == 1)
{
return c => c;
}
return c => new ColorBgra()
{
B = (byte)(c.B / factor),
G = (byte)(c.G / factor),
R = (byte)(c.R / factor),
A = c.A
};
}
}
}

Tell the base class the effect is configurable, declare and initialize the properties (of type double, int or bool for now), and override GetProperties(). You can then use those properties in your AdjustmentToRender implementation.

Notice how AdjustmentToRender varies the function it returns by the values of the configuration properties. This is a key aspect of the functional programming style: this basic form of partial evaluation becomes almost trivially easy to implement, and can yield important performance benefits. In fact, the Drop Shadow effect in the download can be terribly slow for certain image and parameter combinations, but this partial evaluation, combined with the lazy evaluation of some parts of the function, make it perform very fast in most cases.

Non-adjustment effects work similarly to adjustments, but there are some very important differences as well. More about those in a later post. Or take a look at the source code that's included in the download. You'll need Visual Studio 2008 to compile it, but you can use the framework from Visual Studio 2005 as well (though you'll miss the convenient C# 3.0 syntax).

Enjoy.