Skip to content

Instantly share code, notes, and snippets.

@a3geek
Created October 23, 2020 11:57
Show Gist options
  • Save a3geek/bdf7cafb868f3cff810a28d0e969fd8e to your computer and use it in GitHub Desktop.
Save a3geek/bdf7cafb868f3cff810a28d0e969fd8e to your computer and use it in GitHub Desktop.
HSL struct for Unity3d.
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