You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

112 lines
4.1 KiB

using System;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Numerics;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
namespace gaemstone.Client;
[StructLayout(LayoutKind.Explicit)]
public readonly struct Color
: IEquatable<Color>
{
public static readonly Color Transparent = default;
public static readonly Color Black = FromRGB(0.0f, 0.0f, 0.0f);
public static readonly Color White = FromRGB(1.0f, 1.0f, 1.0f);
[FieldOffset(0)]
public readonly uint RGBA;
[FieldOffset(0)]
public readonly byte R;
[FieldOffset(1)]
public readonly byte G;
[FieldOffset(2)]
public readonly byte B;
[FieldOffset(3)]
public readonly byte A;
private Color(uint rgba) => RGBA = rgba;
private Color(byte r, byte g, byte b, byte a)
{ R = r; G = g; B = b; A = a; }
public static Color FromRGBA(uint value) => new((byte)(value >> 24), (byte)(value >> 16), (byte)(value >> 8), (byte)value);
public static Color FromRGBA(float r, float g, float b, float a = 1.0f) => new(F2B(r), F2B(g), F2B(b), F2B(a));
public static Color FromRGBA(Vector4 vec) => FromRGBA(vec.X, vec.Y, vec.Z, vec.W);
public static Color FromRGB(uint value) => new((byte)(value >> 16), (byte)(value >> 8), (byte)value, 0xFF);
public static Color FromRGB(float r, float g, float b) => new(F2B(r), F2B(g), F2B(b), 0xFF);
public static Color FromRGB(Vector3 vec) => FromRGB(vec.X, vec.Y, vec.Z);
public static Color FromGrayscale(float value, float alpha = 1.0f) => FromRGBA(value, value, value, alpha);
public static Color FromHSV(float hue, float saturation, float value, float alpha = 1.0f)
{
if (saturation <= 0.0f) return FromGrayscale(value, alpha);
var h = (((hue % 360f) + 360f) % 360f) / 60f;
var s = Math.Clamp(saturation, 0.0f, 1.0f);
var v = Math.Clamp(value , 0.0f, 1.0f);
var f = h % 1f;
var p = v * (1.0f - s);
var q = v * (1.0f - s * f);
var t = v * (1.0f - s * (1.0f - f));
return (int)h switch {
0 => FromRGBA(v, t, p, alpha),
1 => FromRGBA(q, v, p, alpha),
2 => FromRGBA(p, v, t, alpha),
3 => FromRGBA(p, q, v, alpha),
4 => FromRGBA(t, p, v, alpha),
5 => FromRGBA(v, p, q, alpha),
_ => throw new InvalidOperationException()
};
}
private static readonly Regex _hexRegex = new("^#([0-9a-fA-F]{6}|[0-9a-fA-F]{8})$");
public static Color? TryParseHex(string? str)
{
if (str == null) return null;
var match = _hexRegex.Match(str);
if (!match.Success) return null;
var hex = match.Value.AsSpan()[1..];
var value = uint.Parse(hex, NumberStyles.HexNumber);
return (hex.Length == 8) ? FromRGBA(value) : FromRGB(value);
}
public Color WithAlpha(float alpha)
=> new(R, G, B, F2B(alpha));
public static Color Mix(Color a, Color b, float ratio)
=> FromRGBA((B2F(a.R) * (1 - ratio) + B2F(b.R) * ratio) / 2,
(B2F(a.G) * (1 - ratio) + B2F(b.G) * ratio) / 2,
(B2F(a.B) * (1 - ratio) + B2F(b.B) * ratio) / 2,
(B2F(a.A) * (1 - ratio) + B2F(b.A) * ratio) / 2);
public bool Equals(Color other)
=> RGBA == other.RGBA;
public override bool Equals([NotNullWhen(true)] object? obj)
=> (obj is Color color) && Equals(color);
public override int GetHashCode()
=> RGBA.GetHashCode();
public override string ToString()
=> $"Color(0x{RGBA:X8})";
public Vector4 ToVector4() => new(B2F(R), B2F(G), B2F(B), B2F(A));
public Vector3 ToVector3() => new(B2F(R), B2F(G), B2F(B));
public string ToHexString() => (A < byte.MaxValue) ? $"#{RGBA:X8}" : $"#{R:X2}{G:X2}{B:X2}";
public static bool operator ==(Color left, Color right) => left.Equals(right);
public static bool operator !=(Color left, Color right) => !left.Equals(right);
public static implicit operator System.Drawing.Color(Color color)
=> System.Drawing.Color.FromArgb(color.A, color.R, color.G, color.B);
/// <summary> Converts a float clamped to range [0.0, 1.0] into a byte in range [0, 255]. </summary>
private static byte F2B(float f) => (byte)(Math.Clamp(f, 0.0f, 1.0f) * 255);
/// <summary> Converts a byte in range [0, 255] into a float in range [0.0, 1.0]. </summary>
private static float B2F(byte b) => b / 255f;
}