I'm using some of the PdN code within my own application to do a few graphical adjustments on-the-fly, and ran into this issue. The adjusted gradients did not look very good after applying the hue/saturation/lightness effect.
This thread helped a lot. Using Tanel's suggestions and ennixo's code, I made the necessary modifications to the PdN 3.36 code to apply an accurate HSL adjustment.
Thanks ennixo and Tanel!
In UnaryPixelOps.cs, change the code for class HueSaturationLightness to:
[serializable]
public class HueSaturationLightness
: UnaryPixelOp
{
private int hueDelta;
private int satFactor;
private UnaryPixelOp blendOp;
public HueSaturationLightness(int hueDelta, int satDelta, int lightness)
{
//this.hueDelta = hueDelta;
this.hueDelta = hueDelta * (HslHelper.MAX6 / 360);
this.satFactor = (int)Math.Round((satDelta * 1024) / (double)100);
if (lightness == 0)
{
//blendOp = new UnaryPixelOps.Identity();
blendOp = null;
}
else if (lightness > 0)
{
blendOp = new UnaryPixelOps.BlendConstant(ColorBgra.FromBgra(255, 255, 255, (byte)Math.Round((lightness * 255) / (double)100)));
}
else // if (lightness < 0)
{
blendOp = new UnaryPixelOps.BlendConstant(ColorBgra.FromBgra(0, 0, 0, (byte)Math.Round((-lightness * 255) / (double)100)));
}
}
public override ColorBgra Apply(ColorBgra color)
{
ColorBgra origColor = color;
// adjust hue
if (hueDelta != 0)
{
//HsvColor hsvColor = HsvColor.FromColor(color.ToColor());
//int hue = hsvColor.Hue;
//
//hue += hueDelta;
//
//while (hue < 0)
//{
// hue += 360;
//}
//
//while (hue > 360)
//{
// hue -= 360;
//}
//
//hsvColor.Hue = hue;
//ColorBgra color = ColorBgra.FromColor(hsvColor.ToColor());
int h, s, l;
HslHelper.GetHSL(color.R, color.G, color.B, out h, out s, out l);
h += hueDelta;
while (h < 0)
h += HslHelper.MAX6;
while (h >= HslHelper.MAX6)
h -= HslHelper.MAX6;
HslHelper.GetRGB(h, s, l, out color.R, out color.G, out color.;
}
//adjust saturation
if (satFactor != 1024)
{
//byte intensity = color.GetIntensityByte();
double intensity = color.GetIntensity() * (double)255;
color.R = Utility.ClampToByte((int)Math.Round(intensity * (double)1024 + (color.R - intensity) * (double)satFactor) >> 10);
color.G = Utility.ClampToByte((int)Math.Round(intensity * (double)1024 + (color.G - intensity) * (double)satFactor) >> 10);
color.B = Utility.ClampToByte((int)Math.Round(intensity * (double)1024 + (color.B - intensity) * (double)satFactor) >> 10);
}
// adjust lightness
if (blendOp != null)
color = blendOp.Apply(color);
// set alpha
color.A = origColor.A;
return color;
}
}
Create a new file HslHelper.cs with this, slightly modified from ennixo's code:
using System;
namespace PaintDotNet
{
///
/// Lookup tables as a singleton
///
public class HslLookup
{
#region Tableaux
///
/// Scales a byte to a float [0..1]
/// = (double)i / 255d;
///
public readonly double[] Scale255To1Float;
///
/// Scales a number [0..4080] to [0..255]
/// = (byte)Math.Round((double)i / 16d);
///
public readonly byte[] Scale4080To255Byte;
#endregion
#region Initialisation
///
/// Initializes tables
///
private HslLookup()
{
Scale255To1Float = new double[256];
Scale4080To255Byte = new byte[4081];
for (int i = 0; i < 4081; ++i)
{
Scale4080To255Byte[i] = (byte)Math.Round((double)i / 16d);
}
for (int i = 0; i < 256; ++i)
{
Scale255To1Float[i] = (double)i / 255d;
}
}
#endregion
#region Static
///
/// Singleton of Lookup
///
public static readonly HslLookup Current;
///
/// Initializes the singleton
///
static HslLookup()
{
Current = new HslLookup();
}
#endregion
}
///
/// HSL/RGB converter
///
public static class HslHelper
{
#region Constants
public const int SCALESHIFT = 4;
public const int MAX = 255 << SCALESHIFT;
public const int HALF = MAX >> 1;
private const int MAX2 = MAX << 1;
private const int MAX4 = MAX << 2;
public const int MAX6 = MAX2 + MAX4;
private const int ONESIXTH = MAX / 6;
private const int ONETHIRD = MAX / 3;
private const int TWOTHIRD = MAX2 / 3;
#endregion
///
/// Converts an RGB color to an HSL color with scaled numbers
///
/// Red 0..255
/// Green 0..255
/// Blue 0..255
/// Hue 0..24480
/// Saturation 0..4080
/// Lightness 0..4080
public static void GetHSL(byte rB, byte gB, byte bB, out int h, out int s, out int l)
{
int r = rB << SCALESHIFT;
int g = gB << SCALESHIFT;
int b = bB << SCALESHIFT;
int min = r;
if (min > g) min = g;
if (min > min = b;
int max = r;
if (max < g) max = g;
if (max < max = b;
int delta = max - min;
l = (max + min) >> 1;
if (max == 0 || delta == 0)
{
s = 0;
h = 0;
}
else
{
if (l == 0)
{
s = MAX;
}
else if (l < HALF)
{
s = ((delta * MAX) / l) >> 1;
}
else
{
s = (((delta * MAX) / (MAX - l))) >> 1;
}
if (r == max)
{
h = ((g - * MAX) / delta;
}
else if (g == max)
{
h = MAX2 + (((b - r) * MAX) / delta);
}
else
{
h = MAX4 + (((r - g) * MAX) / delta);
}
if (h < 0)
{
h += MAX6;
}
}
}
///
/// Converts an HSL color with scaled numbers to an RGB color
///
/// Hue 0..24480
/// Saturation 0..4080
/// Lightness 0..4080
/// Red 0..255
/// Green 0..255
/// Blue 0..255
public static void GetRGB(int h, int s, int l, out byte rB, out byte gB, out byte bB)
{
HslLookup lut = HslLookup.Current;
int q, p, hk, r, g, b;
if (l < HALF)
{
q = (int)(((l * (MAX + s)) * 0x8081L) >> (23 + SCALESHIFT));
}
else
{
q = (l + s) - (int)((((l * s)) * 0x8081L) >> (23 + SCALESHIFT));
}
p = (l << 1) - q;
hk = (h * 10923) >> 16;
r = hk + ONETHIRD;
g = hk;
b = hk - ONETHIRD;
if (r > MAX) r -= MAX;
if (b < 0) b += MAX;
if (r < ONESIXTH)
{
r = p + (int)((((q - p) * 6 * r) * 0x8081L) >> (23 + SCALESHIFT));
}
else if (r < HALF)
{
r = q;
}
else if (r < TWOTHIRD)
{
r = p + (int)((((q - p) * 6 * (TWOTHIRD - r)) * 0x8081L) >> (23 + SCALESHIFT));
}
else
{
r = p;
}
if (g < ONESIXTH)
{
g = p + (int)((((q - p) * 6 * g) * 0x8081L) >> (23 + SCALESHIFT));
}
else if (g < HALF)
{
g = q;
}
else if (g < TWOTHIRD)
{
g = p + (int)((((q - p) * 6 * (TWOTHIRD - g)) * 0x8081L) >> (23 + SCALESHIFT));
}
else
{
g = p;
}
if (b < ONESIXTH)
{
b = p + (int)((((q - p) * 6 * * 0x8081L) >> (23 + SCALESHIFT));
}
else if (b < HALF)
{
b = q;
}
else if (b < TWOTHIRD)
{
b = p + (int)((((q - p) * 6 * (TWOTHIRD - ) * 0x8081L) >> (23 + SCALESHIFT));
}
else
{
b = p;
}
rB = lut.Scale4080To255Byte[r];
gB = lut.Scale4080To255Byte[g];
bB = lut.Scale4080To255Byte[b];
}
}
}