Paint.NET Pastel Filter
OK, so I had this idea to create a pastel filter for the free paint program Paint.NET. What I evenually came up with looked fair on some images and poor on others. Well, I finally updated this effect and the results look pretty good.
Here is a "before & after" picture:
And, here is another:
The Idea
Here is my idea:
First, I normalize the Hue to reduce the number of overall colors--similar to a "cutout" effect. Then, I compress all of the Saturation (S) information from 15-100 into 15-50. This should lighten up all the colors to a nice pastel tone. Then, it compresses the Value (V) from 5-100 into 85-100. This should really get rid of all the dark colors. The only thing left to deal with was the near black pixels. Finally, I converted all of the near black pixels to a shade of gray.
The Effect DLL
You can download the precompiled effect here: Pastel.dll
Just drop this file in your \program files\Paint.NET\effects directory and you should be all set.
The Source Code
Here is the source code to the older version that didn't work too well:
void Render(Surface dst, Surface src, Rectangle rect)
{ // How big is your box of pastels?
double BaseColors = 72; // 1-360, step 1, Default = 72
PdnRegion selectionRegion = EnvironmentParameters.GetSelection(src.Bounds);
Rectangle selection = this.EnvironmentParameters.GetSelection(src.Bounds).GetBoundsInt();
ColorBgra CurrentPixel;
double Q = 360 / BaseColors;
double H = 0,S = 0,V = 0;
for(int y = rect.Top; y < rect.Bottom; y++)
{
for (int x = rect.Left; x < rect.Right; x++)
{
if (selectionRegion.IsVisible(x, y))
{
CurrentPixel = src[x,y];
EvanRGBtoHSV(CurrentPixel.R,CurrentPixel.G,CurrentPixel.B,ref H,ref S,ref V);
H = Q*(double)Math.Round(H/Q); // Normalize Hue
if (S > .15) // Compress Saturation
{
S = ( (0.35*(S-0.15)) / (1.0-0.15) ) + 0.15;
}
if (V > 0.05) // Compress Value
{
V = ( (0.15*(V-0.05)) / (1.0-0.05) ) + 0.85;
}
else // Near Black Pixels
{
S = 0; // turn them gray
V += 0.75;
}
EvanHSVtoRGB(H,S,V,ref CurrentPixel.R,ref CurrentPixel.G,ref CurrentPixel.B);
dst[x,y] = CurrentPixel;
}
}
}
}
Here are the support functions written by someone named Evan:
public void EvanHSVtoRGB(double H, double S, double V, ref byte bR, ref byte bG, ref byte bB)
{
const double HSV_UNDEFINED = -999.0;
// Parameters must satisfy the following ranges:
// 0.0 <= H < 360.0
// 0.0 <= S <= 1.0
// 0.0 <= V <= 1.0
// Handle special case first
if (S == 0.0 || H == HSV_UNDEFINED)
{
byte x = (byte)(int)(V * 255.0);
bR = x;
bG = x;
bB = x;
return;
}
if (H >= 360.0)
{
H = AngleConstrain(H);
}
double R = V, G = V, B = V;
double Hi = Math.Floor(H / 60.0);
double f = H / 60.0 - Hi;
double p = V * (1.0 - S);
double q = V * (1.0 - f * S);
double t = V * (1.0 - (1.0 - f) * S);
if (Hi == 0.0)
{
R = V;
G = t;
B = p;
}
else if (Hi == 1.0)
{
R = q;
G = V;
B = p;
}
else if (Hi == 2.0)
{
R = p;
G = V;
B = t;
}
else if (Hi == 3.0)
{
R = p;
G = q;
B = V;
}
else if (Hi == 4.0)
{
R = t;
G = p;
B = V;
}
else if (Hi == 5.0)
{
R = V;
G = p;
B = q;
}
int iR = (int)(R * 255.0);
int iG = (int)(G * 255.0);
int iB = (int)(B * 255.0);
bR = (byte)iR;
bG = (byte)iG;
bB = (byte)iB;
}
public void EvanRGBtoHSV(int R, int G, int B, ref double outH, ref double outS, ref double outV)
{
const double HSV_UNDEFINED = -999.0;
// R, G, and B must range from 0 to 255
// Ouput value ranges:
// outH - 0.0 to 360.0
// outS - 0.0 to 1.0
// outV - 0.0 to 1.0
double dR = (double)R / 255.0;
double dG = (double)G / 255.0;
double dB = (double)B / 255.0;
double dmaxRGB = EvanMax3(dR, dG, dB);
double dminRGB = EvanMin3(dR, dG, dB);
double delta = dmaxRGB - dminRGB;
// Set value
outV = dmaxRGB;
// Handle special case
if (dmaxRGB == 0)
{
outH = HSV_UNDEFINED;
outS = 0.0;
return;
}
outS = delta / dmaxRGB;
if (dmaxRGB == dminRGB)
{
outH = HSV_UNDEFINED;
return;
}
// Finally, compute hue
if (dR == dmaxRGB)
{
outH = (dG - dB) / delta * 60.0;
}
else if (dG == dmaxRGB)
{
outH = (2.0 + (dB - dR) / delta) * 60.0;
}
else //if (dB == dmaxRGB)
{
outH = (4.0 + (dR - dG) / delta) * 60.0;
}
if (outH < 0)
{
outH += 360.0;
}
}
public double EvanMax3(double x, double y, double z)
{
return (x > y) ? ((x > z) ? x : z) : ((y > z) ? y : z);
}
public double EvanMin3(double x, double y, double z)
{
return (x < y) ? ((x < z) ? x : z) : ((y < z) ? y : z);
}
public double AngleConstrain(double MyAngle)
{
// Makes sure that 0.0 <= MyAngle < 360.0
// Wraps around the value if its outside this range
if (MyAngle >= 360.0)
{
MyAngle -= Math.Floor(MyAngle / 360.0) * 360.0;
}
if (MyAngle < 0.0)
{
MyAngle += 360.0;
}
return MyAngle;
}
I hope you enjoy this update.



