using System.Drawing;
namespace OpenNest
{
///
/// The darker shade WinForms' ControlPaint.Dark gives a colour, computed without
/// WinForms so both desktop front ends outline parts alike. A port of the HLS arithmetic in
/// dotnet/winforms (ControlPaint.HLSColor, MIT licence), including its SystemColors.Control
/// special case.
///
public static class ColorShade
{
private const int ShadowAdjustment = -333;
private const int HLSMax = 240;
private const int RGBMax = 255;
private const int Undefined = HLSMax * 2 / 3;
/// As ControlPaint.Dark(color): halfway to the darkest shade.
public static Color Dark(Color color) => Dark(color, 0.5f);
/// As ControlPaint.Dark(color, percOfDarkDark).
public static Color Dark(Color color, float percOfDarkDark)
{
// With the usual colour scheme, ControlDark/DarkDark is not exactly what the HLS
// arithmetic would give, so WinForms uses the system colours for Control.
if (color.ToKnownColor() == KnownColor.Control)
{
if (percOfDarkDark == 0.0f)
return SystemColors.ControlDark;
if (percOfDarkDark == 1.0f)
return SystemColors.ControlDarkDark;
var dark = SystemColors.ControlDark;
var darkDark = SystemColors.ControlDarkDark;
return Color.FromArgb(
(byte)(dark.R - (byte)((dark.R - darkDark.R) * percOfDarkDark)),
(byte)(dark.G - (byte)((dark.G - darkDark.G) * percOfDarkDark)),
(byte)(dark.B - (byte)((dark.B - darkDark.B) * percOfDarkDark)));
}
var (hue, luminosity, saturation) = ToHls(color);
var zeroLum = luminosity * (ShadowAdjustment + 1000) / 1000;
return FromHls(hue, zeroLum - (int)(zeroLum * percOfDarkDark), saturation);
}
private static (int Hue, int Luminosity, int Saturation) ToHls(Color color)
{
int r = color.R;
int g = color.G;
int b = color.B;
var max = System.Math.Max(System.Math.Max(r, g), b);
var min = System.Math.Min(System.Math.Min(r, g), b);
var sum = max + min;
var luminosity = ((sum * HLSMax) + RGBMax) / (2 * RGBMax);
var dif = max - min;
if (dif == 0)
return (Undefined, luminosity, 0);
var saturation = luminosity <= (HLSMax / 2)
? ((dif * HLSMax) + (sum / 2)) / sum
: ((dif * HLSMax) + (2 * RGBMax - sum) / 2) / (2 * RGBMax - sum);
var rDelta = (((max - r) * (HLSMax / 6)) + (dif / 2)) / dif;
var gDelta = (((max - g) * (HLSMax / 6)) + (dif / 2)) / dif;
var bDelta = (((max - b) * (HLSMax / 6)) + (dif / 2)) / dif;
int hue;
if (r == max)
hue = bDelta - gDelta;
else if (g == max)
hue = (HLSMax / 3) + rDelta - bDelta;
else
hue = (2 * HLSMax / 3) + gDelta - rDelta;
if (hue < 0)
hue += HLSMax;
if (hue > HLSMax)
hue -= HLSMax;
return (hue, luminosity, saturation);
}
private static Color FromHls(int hue, int luminosity, int saturation)
{
byte r, g, b;
if (saturation == 0)
{
r = g = b = (byte)(luminosity * RGBMax / HLSMax);
}
else
{
var magic2 = luminosity <= (HLSMax / 2)
? ((luminosity * (HLSMax + saturation)) + (HLSMax / 2)) / HLSMax
: luminosity + saturation - (((luminosity * saturation) + (HLSMax / 2)) / HLSMax);
var magic1 = 2 * luminosity - magic2;
r = (byte)((HueToRgb(magic1, magic2, hue + HLSMax / 3) * RGBMax + (HLSMax / 2)) / HLSMax);
g = (byte)((HueToRgb(magic1, magic2, hue) * RGBMax + (HLSMax / 2)) / HLSMax);
b = (byte)((HueToRgb(magic1, magic2, hue - HLSMax / 3) * RGBMax + (HLSMax / 2)) / HLSMax);
}
return Color.FromArgb(r, g, b);
}
private static int HueToRgb(int n1, int n2, int hue)
{
if (hue < 0)
hue += HLSMax;
if (hue > HLSMax)
hue -= HLSMax;
if (hue < (HLSMax / 6))
return n1 + (((n2 - n1) * hue + (HLSMax / 12)) / (HLSMax / 6));
if (hue < (HLSMax / 2))
return n2;
if (hue < (HLSMax * 2 / 3))
return n1 + (((n2 - n1) * ((HLSMax * 2 / 3) - hue) + (HLSMax / 12)) / (HLSMax / 6));
return n1;
}
}
}