using System.Buffers.Binary; using System.Numerics; namespace HellionChat.Util; internal static class ColourUtil { private static (byte r, byte g, byte b) RgbaToRgbComponents(uint rgba) { var r = (byte)((rgba & 0xFF000000) >> 24); var g = (byte)((rgba & 0xFF0000) >> 16); var b = (byte)((rgba & 0xFF00) >> 8); return (r, g, b); } internal static uint RgbaToAbgr(uint rgba) => BinaryPrimitives.ReverseEndianness(rgba); internal static Vector3 RgbaToVector3(uint rgba) { var (r, g, b) = RgbaToRgbComponents(rgba); return new Vector3((float)r / 255, (float)g / 255, (float)b / 255); } internal static uint Vector3ToRgba(Vector3 col) { return ComponentsToRgba( (byte)Math.Round(col.X * 255), (byte)Math.Round(col.Y * 255), (byte)Math.Round(col.Z * 255) ); } internal static Vector4 RgbaToVector4(uint rgba) { var (r, g, b) = RgbaToRgbComponents(rgba); var a = (byte)(rgba & 0xFFu); return new Vector4(r / 255f, g / 255f, b / 255f, a / 255f); } internal static uint Vector4ToRgba(Vector4 col) { // Clamp guards against future ImGuiColorEditFlags.HDR feeding out-of-range // components: a raw byte-cast would wrap (e.g. (byte)Math.Round(2.0f*255)=254). // Mirrors the ApplyAlpha clamping pattern in this file. var r = Math.Clamp(col.X, 0f, 1f); var g = Math.Clamp(col.Y, 0f, 1f); var b = Math.Clamp(col.Z, 0f, 1f); var a = Math.Clamp(col.W, 0f, 1f); return ComponentsToRgba( (byte)Math.Round(r * 255), (byte)Math.Round(g * 255), (byte)Math.Round(b * 255), (byte)Math.Round(a * 255) ); } internal static uint Vector4ToAbgr(Vector4 col) { return RgbaToAbgr( ComponentsToRgba( (byte)Math.Round(col.X * 255), (byte)Math.Round(col.Y * 255), (byte)Math.Round(col.Z * 255), (byte)Math.Round(col.W * 255) ) ); } public static unsafe uint ArgbToRgba(uint x) { var buf = (byte*)&x; (buf[1], buf[2], buf[3], buf[0]) = (buf[0], buf[1], buf[2], buf[3]); return x; } internal static uint ComponentsToRgba(byte red, byte green, byte blue, byte alpha = 0xFF) => alpha | (uint)(red << 24) | (uint)(green << 16) | (uint)(blue << 8); internal static uint AdjustBrightness(uint abgr, float factor) { var a = (byte)((abgr & 0xFF000000) >> 24); var b = (byte)((abgr & 0x00FF0000) >> 16); var g = (byte)((abgr & 0x0000FF00) >> 8); var r = (byte)(abgr & 0x000000FF); var nr = (byte)Math.Clamp(r * factor, 0f, 255f); var ng = (byte)Math.Clamp(g * factor, 0f, 255f); var nb = (byte)Math.Clamp(b * factor, 0f, 255f); return ((uint)a << 24) | ((uint)nb << 16) | ((uint)ng << 8) | nr; } // Modulates the alpha byte of an ABGR color by a factor in [0, 1]. // RGB stays intact. Used by the PM-3 hover-lerp path where each // frame produces a fractional alpha value but the colour itself // must not shift. internal static uint ApplyAlpha(uint abgr, float alphaFactor) { alphaFactor = Math.Clamp(alphaFactor, 0f, 1f); var origAlpha = (byte)((abgr >> 24) & 0xFFu); var newAlpha = (byte)Math.Round(origAlpha * alphaFactor); return (abgr & 0x00FFFFFFu) | ((uint)newAlpha << 24); } // Mixes an ABGR colour's RGB channels toward white (0xFF) by factor t in // [0, 1]; the alpha byte is left untouched. A1 hover-sheen accent-tint: // a low factor nudges the sweep toward the element's accent hue without // going fully saturated (effect level stays "subtle"). RGB-only on // purpose -- DrawHoverSheen owns the alpha falloff. internal static uint LerpTowardWhite(uint abgr, float t) { t = Math.Clamp(t, 0f, 1f); var a = (byte)((abgr >> 24) & 0xFFu); var b = (byte)((abgr >> 16) & 0xFFu); var g = (byte)((abgr >> 8) & 0xFFu); var r = (byte)(abgr & 0xFFu); var nr = (byte)Math.Round(r + (0xFF - r) * t); var ng = (byte)Math.Round(g + (0xFF - g) * t); var nb = (byte)Math.Round(b + (0xFF - b) * t); return ((uint)a << 24) | ((uint)nb << 16) | ((uint)ng << 8) | nr; } public static uint HexToRgba(string hex) { ArgumentNullException.ThrowIfNull(hex); var s = hex.StartsWith('#') ? hex[1..] : hex; if (s.Length != 6 && s.Length != 8) throw new FormatException( $"Hex colour must be 6 or 8 hex digits, got {s.Length}: '{hex}'" ); if ( !uint.TryParse( s, System.Globalization.NumberStyles.HexNumber, System.Globalization.CultureInfo.InvariantCulture, out var value ) ) throw new FormatException($"Hex colour '{hex}' is not a valid hexadecimal value"); if (s.Length == 6) value = (value << 8) | 0xFFu; // RRGGBB → RRGGBBFF return value; } }