Created
October 23, 2020 11:57
-
-
Save a3geek/bdf7cafb868f3cff810a28d0e969fd8e to your computer and use it in GitHub Desktop.
HSL struct for Unity3d.
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
using System; | |
using System.Globalization; | |
using UnityEngine; | |
namespace Gists | |
{ | |
/// <summary> | |
/// HSL struct. | |
/// </summary> | |
/// <remarks>ref: https://github.com/Unity-Technologies/UnityCsReference/blob/master/Runtime/Export/Math/Color.cs </remarks> | |
[Serializable] | |
public struct HSL : IFormattable, IEquatable<HSL> | |
{ | |
public static readonly HSL White = new HSL(0f, 1f, 1f); | |
public static readonly HSL Black = new HSL(0f, 0f, 0f); | |
public float this[int index] | |
{ | |
get | |
{ | |
switch (index) | |
{ | |
case 0: | |
return this.h; | |
case 1: | |
return this.s; | |
case 2: | |
return this.l; | |
default: | |
throw new IndexOutOfRangeException("Invalid " + nameof(HSL) + " index(" + index + ")!"); | |
} | |
} | |
set | |
{ | |
switch (index) | |
{ | |
case 0: | |
this.h = value; | |
break; | |
case 1: | |
this.s = value; | |
break; | |
case 2: | |
this.l = value; | |
break; | |
default: | |
throw new IndexOutOfRangeException("Invalid " + nameof(HSL) + " index(" + index + ")!"); | |
} | |
} | |
} | |
public (float h, float s, float l) hsl => (this.h, this.s, this.l); | |
public float h; | |
public float s; | |
public float l; | |
public HSL(float h, float s, float l) | |
{ | |
this.h = h; | |
this.s = s; | |
this.l = l; | |
} | |
public HSL(Color color) | |
{ | |
(this.h, this.s, this.l) = RGBToHSL(color); | |
} | |
public float Magnitude(HSL other) | |
=> Magnitude(this, other); | |
public float SqrMagnitude(HSL other) | |
=> SqrMagnitude(this, other); | |
public float MagnitudeHue(HSL other) | |
=> MagnitudeHue(this, other); | |
public float SqrMagnitudeHue(HSL other) | |
=> SqrMagnitudeHue(this, other); | |
public float MagnitudeHue(float hue) | |
=> MagnitudeHue(this.h, hue); | |
public float SqrMagnitudeHue(float hue) | |
=> SqrMagnitudeHue(this.h, hue); | |
#region "Csharp functions" | |
public override string ToString() | |
=> this.ToString(null, CultureInfo.InvariantCulture.NumberFormat); | |
public string ToString(string format) | |
=> this.ToString(format, CultureInfo.InvariantCulture.NumberFormat); | |
public string ToString(string format, IFormatProvider formatProvider) | |
{ | |
if (string.IsNullOrEmpty(format) == true) | |
{ | |
format = "F3"; | |
} | |
return string.Format(CultureInfo.InvariantCulture.NumberFormat, | |
"HSL({0}, {1}, {2})", | |
this.h.ToString(format, formatProvider), | |
this.s.ToString(format, formatProvider), | |
this.l.ToString(format, formatProvider) | |
); | |
} | |
public override int GetHashCode() | |
=> ((Vector3)this).GetHashCode(); | |
public override bool Equals(object other) | |
=> other is HSL hsl && this.Equals(hsl); | |
public bool Equals(HSL other) | |
=> this.h.Equals(other.h) && this.s.Equals(other.s) && this.l.Equals(other.l); | |
#endregion | |
#region "Static functions" | |
public static float Magnitude(HSL a, HSL b) | |
=> Mathf.Sqrt(SqrMagnitude(a, b)); | |
public static float SqrMagnitude(HSL a, HSL b) | |
{ | |
// ref: | |
// https://www.jstage.jst.go.jp/article/iieej/36/4/36_4_473/_pdf | |
// p.474, formula 4 ~ 7 | |
var c1 = a.s * Mathf.Cos(a.h) - b.s * Mathf.Cos(b.h); | |
var c2 = a.s * Mathf.Sin(a.h) - b.s * Mathf.Sin(b.h); | |
var l = a.l - b.l; | |
return l * l + c1 * c1 + c2 * c2; | |
} | |
public static float MagnitudeHue(HSL a, HSL b) | |
=> Mathf.Min(Mathf.Abs(a.h - b.h), 1f - Mathf.Abs(a.h - b.h)); | |
public static float MagnitudeHue(float hueA, float hueB) | |
=> Mathf.Min(Mathf.Abs(hueA - hueB), 1f - Mathf.Abs(hueA - hueB)); | |
public static float SqrMagnitudeHue(HSL a, HSL b) | |
=> MagnitudeHue(a, b) * MagnitudeHue(a, b); | |
public static float SqrMagnitudeHue(float hueA, float hueB) | |
=> MagnitudeHue(hueA, hueB) * MagnitudeHue(hueA, hueB); | |
public static HSL Lerp(HSL a, HSL b, float t) | |
{ | |
t = Mathf.Clamp01(t); | |
return new HSL( | |
a.h + (b.h - a.h) * t, | |
a.s + (b.s - a.s) * t, | |
a.l + (b.l - a.l) * t | |
); | |
} | |
public static HSL LerpUnclamped(HSL a, HSL b, float t) | |
=> new HSL( | |
a.h + (b.h - a.h) * t, | |
a.s + (b.s - a.s) * t, | |
a.l + (b.l - a.l) * t | |
); | |
public static (float h, float s, float l) RGBToHSL((float r, float g, float b) color) | |
=> RGBToHSL(new Color(color.r, color.g, color.b)); | |
public static (float h, float s, float l) RGBToHSL(Color color) | |
{ | |
float r = color.r, g = color.g, b = color.b; | |
var max = Mathf.Max(r, g, b); | |
var min = Mathf.Min(r, g, b); | |
float h = 0f, s = 0f; | |
var l = (max + min) * 0.5f; | |
if (max != min) | |
{ | |
var d = max - min; | |
s = l > 0.5f ? d / (2f - max - min) : d / (max + min); | |
if (max == r) | |
{ | |
h = (g - b) / d + (g < b ? 6f : 0f); | |
} | |
else if (max == g) | |
{ | |
h = (b - r) / d + 2f; | |
} | |
else | |
{ | |
h = (r - g) / d + 4f; | |
} | |
h /= 6f; | |
} | |
return (h, s, l); | |
} | |
public static (float r, float g, float b) HSLToRGB((float h, float s, float l) hsl) | |
=> HSLToRGB(new HSL(hsl.h, hsl.s, hsl.l)); | |
public static (float r, float g, float b) HSLToRGB(HSL hsl) | |
{ | |
var (h, s, l) = hsl.hsl; | |
if (s == 0f) | |
{ | |
return (l, l, l); | |
} | |
float HueToRGB(float p, float q, float t) | |
{ | |
t += t < 0f ? 1f : (t > 1f ? -1f : 0f); | |
if (t < 1f / 6f) | |
{ | |
return p + (q - p) * 6f * t; | |
} | |
else if (t < 1f / 2f) | |
{ | |
return q; | |
} | |
else if (t < 2f / 3f) | |
{ | |
return p + (q - p) * (2f / 3f - t) * 6f; | |
} | |
return p; | |
} | |
var q2 = l < 0.5f ? l * (1f + s) : l + s - l * s; | |
var p2 = 2 * l - q2; | |
return (HueToRGB(p2, q2, h + 1f / 3f), HueToRGB(p2, q2, h), HueToRGB(p2, q2, h - 1f / 3f)); | |
} | |
public static HSL operator +(HSL a, HSL b) | |
=> new HSL(a.h + b.h, a.s + b.s, a.l + b.l); | |
public static HSL operator -(HSL a, HSL b) | |
=> new HSL(a.h - b.h, a.s - b.s, a.l - b.l); | |
public static HSL operator *(HSL a, HSL b) | |
=> new HSL(a.h * b.h, a.s * b.s, a.l * b.l); | |
public static HSL operator *(HSL a, float b) | |
=> new HSL(a.h * b, a.s * b, a.l * b); | |
public static HSL operator *(float b, HSL a) | |
=> new HSL(a.h * b, a.s * b, a.l * b); | |
public static HSL operator /(HSL a, float b) | |
=> new HSL(a.h / b, a.s / b, a.l / b); | |
public static bool operator ==(HSL lhs, HSL rhs) | |
=> (Vector3)lhs == (Vector3)rhs; | |
public static bool operator !=(HSL lhs, HSL rhs) | |
=> !(lhs == rhs); | |
public static implicit operator Vector3(HSL hsl) | |
=> new Vector3(hsl.h, hsl.s, hsl.l); | |
public static implicit operator HSL(Vector3 v) | |
=> new HSL(v.x, v.y, v.z); | |
public static implicit operator HSL(Color v) | |
=> new HSL(v); | |
public static implicit operator Color(HSL hsl) | |
{ | |
var (r, g, b) = HSLToRGB(hsl); | |
return new Color(r, g, b); | |
} | |
#endregion | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment