Created
September 5, 2018 15:39
-
-
Save profexorgeek/a407c0c96f69a37a2f2554b43491e247 to your computer and use it in GitHub Desktop.
This struct represents a Color using the HSL color space. It provides convenient conversions into the XNA/MonoGame RGB Color struct. HSL offers an easier way to work with color artistically by separating color into Hue, Saturation and Luminance.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public struct HSLColor | |
{ | |
// HSL stands for Hue, Saturation and Luminance. HSL | |
// color space makes it easier to do calculations | |
// that operate on these channels | |
// Helpful color math can be found here: | |
// https://www.easyrgb.com/en/math.php | |
/// <summary> | |
/// Hue: the 'color' of the color! | |
/// </summary> | |
public float H; | |
/// <summary> | |
/// Saturation: How grey or vivid/colorful a color is | |
/// </summary> | |
public float S; | |
/// <summary> | |
/// Luminance: The brightness or lightness of the color | |
/// </summary> | |
public float L; | |
public HSLColor(float h, float s, float l) | |
{ | |
H = h; | |
S = s; | |
L = l; | |
} | |
public static HSLColor FromColor(Color color) | |
{ | |
return FromRgb(color.R, color.G, color.B); | |
} | |
public static HSLColor FromRgb(byte R, byte G, byte B) | |
{ | |
var hsl = new HSLColor(); | |
hsl.H = 0; | |
hsl.S = 0; | |
hsl.L = 0; | |
float r = R / 255f; | |
float g = G / 255f; | |
float b = B / 255f; | |
float min = Math.Min(Math.Min(r, g), b); | |
float max = Math.Max(Math.Max(r, g), b); | |
float delta = max - min; | |
// luminance is the ave of max and min | |
hsl.L = (max + min) / 2f; | |
if (delta > 0) | |
{ | |
if (hsl.L < 0.5f) | |
{ | |
hsl.S = delta / (max + min); | |
} | |
else | |
{ | |
hsl.S = delta / (2 - max - min); | |
} | |
float deltaR = (((max - r) / 6f) + (delta / 2f)) / delta; | |
float deltaG = (((max - g) / 6f) + (delta / 2f)) / delta; | |
float deltaB = (((max - b) / 6f) + (delta / 2f)) / delta; | |
if (r == max) | |
{ | |
hsl.H = deltaB - deltaG; | |
} | |
else if (g == max) | |
{ | |
hsl.H = (1f / 3f) + deltaR - deltaB; | |
} | |
else if (b == max) | |
{ | |
hsl.H = (2f / 3f) + deltaG - deltaR; | |
} | |
if (hsl.H < 0) | |
{ | |
hsl.H += 1; | |
} | |
if (hsl.H > 1) | |
{ | |
hsl.H -= 1; | |
} | |
} | |
return hsl; | |
} | |
public HSLColor GetComplement() | |
{ | |
// complementary colors are across the color wheel | |
// which is 180 degrees or 50% of the way around the | |
// wheel. Add 50% to our hue and wrap large/small values | |
var h = H + 0.5f; | |
if (h > 1) | |
{ | |
h -= 1; | |
} | |
return new HSLColor(h, S, L); | |
} | |
public Color ToRgbColor() | |
{ | |
var c = new Color(); | |
if (S == 0) | |
{ | |
c.R = (byte)(L * 255f); | |
c.G = (byte)(L * 255f); | |
c.B = (byte)(L * 255f); | |
} | |
else | |
{ | |
float v2 = (L + S) - (S * L); | |
if (L < 0.5f) | |
{ | |
v2 = L * (1 + S); | |
} | |
float v1 = 2f * L - v2; | |
c.R = (byte)(255f * HueToRgb(v1, v2, H + (1f / 3f))); | |
c.G = (byte)(255f * HueToRgb(v1, v2, H)); | |
c.B = (byte)(255f * HueToRgb(v1, v2, H - (1f / 3f))); | |
} | |
return c; | |
} | |
private static float HueToRgb(float v1, float v2, float vH) | |
{ | |
vH += (vH < 0) ? 1 : 0; | |
vH -= (vH > 1) ? 1 : 0; | |
float ret = v1; | |
if ((6 * vH) < 1) | |
{ | |
ret = (v1 + (v2 - v1) * 6 * vH); | |
} | |
else if ((2 * vH) < 1) | |
{ | |
ret = (v2); | |
} | |
else if ((3 * vH) < 2) | |
{ | |
ret = (v1 + (v2 - v1) * ((2f / 3f) - vH) * 6f); | |
} | |
return ret.Clamp(0, 1); | |
} | |
} |
Can you give me some sample values you used to initialize the HSLColor
object? There are two things that could be happening here:
- You are initializing the HSLColor with a Luminosity value of 1: this will always result in white.
- You are using/casting to a different type of color object or other settings in your project are changing how premultiplied alpha works. Premultiplied alpha can cause a variety of unexpected conversion problems.
I have double checked the initialization and conversion and they seem to match what I've been using in my project for a few years now. It's working fine in my project so I suspect it's one of these two things. I'd love to figure this out if you have time to test or provide any more specific information. If I get a chance and remember, I'll try creating a small sample project and pushing it to github for demonstration.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Can you show me how you are using it, and are you using this with MonoGame? This should work (may be minor syntax errors since I just wrote this in the browser):
I use this class extensively in my Steam game Masteroid so I know it works well with MonoGame. The
Color
object this is designed to work with is specifically theMicrosoft.Framework.Xna.Color
, not a standard .NET framework color!