< Summary

Information
Class: Allyaria.Theming.Types.HexByte
Assembly: Allyaria.Theming
File(s): /home/runner/work/allyaria/allyaria/src/Allyaria.Theming/Types/HexByte.cs
Line coverage
100%
Covered lines: 81
Uncovered lines: 0
Coverable lines: 81
Total lines: 321
Line coverage: 100%
Branch coverage
100%
Covered branches: 24
Total branches: 24
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor()100%11100%
.ctor(...)100%11100%
.ctor(...)100%22100%
get_Value()100%11100%
ClampAlpha(...)100%11100%
CompareTo(...)100%11100%
Equals(...)100%11100%
Equals(...)100%22100%
FromNormalized(...)100%22100%
GetHashCode()100%11100%
Parse(...)100%11100%
ToLerpByte(...)100%22100%
ToLerpHexByte(...)100%11100%
ToLerpLinearByte(...)100%22100%
ToLinear()100%22100%
FromLinear()100%22100%
ToLerpLinearHexByte(...)100%11100%
ToNormalized()100%11100%
ToSrgbLinearValue()100%22100%
ToString()100%11100%
TryParse(...)100%88100%
op_Equality(...)100%11100%
op_GreaterThan(...)100%11100%
op_GreaterThanOrEqual(...)100%11100%
op_Implicit(...)100%11100%
op_Implicit(...)100%11100%
op_Implicit(...)100%11100%
op_Implicit(...)100%11100%
op_Inequality(...)100%11100%
op_LessThan(...)100%11100%
op_LessThanOrEqual(...)100%11100%

File(s)

/home/runner/work/allyaria/allyaria/src/Allyaria.Theming/Types/HexByte.cs

#LineLine coverage
 1namespace Allyaria.Theming.Types;
 2
 3/// <summary>
 4/// Represents a single 8-bit color channel theme (0–255) used in RGBA color models. Provides parsing, formatting,
 5/// comparisons, normalized conversions, and interpolation helpers. Interpolation helpers include a gamma-correct
 6/// (linear-light) implementation for higher visual accuracy.
 7/// </summary>
 8public readonly struct HexByte : IComparable<HexByte>, IEquatable<HexByte>
 9{
 10    /// <summary>
 11    /// Initializes a new instance of the <see cref="HexByte" /> struct with a theme of 0 (string form <c>"00"</c>).
 12    /// </summary>
 13    public HexByte()
 8214        : this(value: 0) { }
 15
 16    /// <summary>Initializes a new instance of the <see cref="HexByte" /> struct using a byte theme.</summary>
 17    /// <param name="value">The byte theme to represent as hexadecimal.</param>
 520681818    public HexByte(byte value) => Value = value;
 19
 20    /// <summary>Initializes a new instance of the <see cref="HexByte" /> struct using a hexadecimal string.</summary>
 21    /// <param name="value">
 22    /// The hexadecimal string representing the byte theme; accepts 1–2 hex characters (e.g., <c>"F"</c>, <c>"0A"</c>,
 23    /// <c>"ff"</c>). Whitespace is allowed and ignored.
 24    /// </param>
 25    /// <exception cref="AryArgumentException">
 26    /// Thrown when <paramref name="value" /> is <see langword="null" />, whitespace only, contains non-hex characters, 
 27    /// more than two hex characters after trimming.
 28    /// </exception>
 29    public HexByte(string value)
 30    {
 1231        AryGuard.NotNullOrWhiteSpace(value: value);
 32
 933        var span = value.AsSpan().Trim();
 934        AryGuard.InRange(value: span.Length, min: 1, max: 2, argName: nameof(value));
 35
 836        Value = byte.TryParse(
 837            s: span, style: NumberStyles.HexNumber, provider: CultureInfo.InvariantCulture, result: out var parsed
 838        )
 839            ? parsed
 840            : throw new AryArgumentException(message: $"Invalid hexadecimal string: '{value}'.");
 641    }
 42
 43    /// <summary>Gets the byte representation of the hexadecimal theme.</summary>
 1358355044    public byte Value { get; }
 45
 46    /// <summary>
 47    /// Clamps a normalized alpha theme between 0.0 and 1.0 and converts it to a <see cref="HexByte" /> representation.
 48    /// </summary>
 49    /// <param name="value">The alpha theme to clamp (expected 0.0–1.0; values outside are clamped).</param>
 50    /// <returns>A <see cref="HexByte" /> corresponding to the clamped theme.</returns>
 51    public static HexByte ClampAlpha(double value)
 352        => FromNormalized(value: Math.Clamp(value: value, min: 0.0, max: 1.0));
 53
 54    /// <summary>Compares this <see cref="HexByte" /> instance to another based on their byte values.</summary>
 55    /// <param name="other">The other <see cref="HexByte" /> instance to compare with.</param>
 56    /// <returns>An integer indicating the relative order of the objects being compared.</returns>
 2857    public int CompareTo(HexByte other) => Value.CompareTo(value: other.Value);
 58
 59    /// <summary>Indicates whether the current object is equal to another object of the same type.</summary>
 60    /// <param name="other">An object to compare with this object.</param>
 61    /// <returns>
 62    /// <see langword="true" /> if the current object is equal to the <paramref name="other" /> parameter; otherwise,
 63    /// <see langword="false" />.
 64    /// </returns>
 898665    public bool Equals(HexByte other) => Value == other.Value;
 66
 67    /// <summary>Indicates whether this instance and a specified object are equal.</summary>
 68    /// <param name="obj">The object to compare with the current instance.</param>
 69    /// <returns>
 70    /// <see langword="true" /> if <paramref name="obj" /> and this instance are the same type and represent the same th
 71    /// otherwise, <see langword="false" />.
 72    /// </returns>
 673    public override bool Equals(object? obj) => obj is HexByte other && Equals(other: other);
 74
 75    /// <summary>Creates a <see cref="HexByte" /> from a normalized theme in the range [0, 1].</summary>
 76    /// <param name="value">A normalized channel theme between 0.0 and 1.0 inclusive.</param>
 77    /// <returns>A <see cref="HexByte" /> whose numeric theme corresponds to <paramref name="value" />·255.</returns>
 78    /// <exception cref="AryArgumentException">
 79    /// Thrown when <paramref name="value" /> is not finite or lies outside the [0, 1]
 80    /// range.
 81    /// </exception>
 82    public static HexByte FromNormalized(double value)
 83    {
 116723984        if (!double.IsFinite(d: value))
 85        {
 486            throw new AryArgumentException(
 487                message: "Normalized theme must be a finite number.", argName: nameof(value)
 488            );
 89        }
 90
 116723591        AryGuard.InRange(value: value, min: 0.0, max: 1.0);
 92
 116723393        var b = (byte)Math.Clamp(
 116723394            value: Math.Round(value: value * 255.0, mode: MidpointRounding.ToEven), min: 0, max: 255
 116723395        );
 96
 116723397        return new HexByte(value: b);
 98    }
 99
 100    /// <summary>Returns the hash code for this instance.</summary>
 101    /// <returns>A 32-bit signed integer that is the hash code for this instance.</returns>
 10102    public override int GetHashCode() => Value.GetHashCode();
 103
 104    /// <summary>Parses a hexadecimal string (1–2 hex characters) into a <see cref="HexByte" />.</summary>
 105    /// <param name="value">The hexadecimal string to parse. Whitespace is allowed and ignored.</param>
 106    /// <returns>A new <see cref="HexByte" /> representing the parsed theme.</returns>
 107    /// <exception cref="AryArgumentException">
 108    /// Thrown if <paramref name="value" /> is <see langword="null" />, whitespace, or not a valid 1–2 character hex str
 109    /// </exception>
 1110    public static HexByte Parse(string value) => new(value: value);
 111
 112    /// <summary>
 113    /// Linearly interpolates this channel in byte space (no gamma). Suitable for alpha coverage and UI opacity.
 114    /// </summary>
 115    /// <param name="end">The end byte theme (0–255).</param>
 116    /// <param name="factor">The interpolation factor; values are clamped to [0, 1]. Non-finite values are treated as 0.
 117    /// <returns>The interpolated sRGB channel byte.</returns>
 118    public byte ToLerpByte(byte end, double factor)
 119    {
 10120        var t = double.IsFinite(d: factor)
 10121            ? Math.Clamp(value: factor, min: 0.0, max: 1.0)
 10122            : 0.0;
 123
 10124        return (byte)Math.Clamp(
 10125            value: Math.Round(value: Value + (end - Value) * t, mode: MidpointRounding.ToEven), min: 0, max: 255
 10126        );
 127    }
 128
 129    /// <summary>Convenience alias of ToLerpByte; linear (not gamma-correct). Prefer this for alpha.</summary>
 130    /// <param name="end">The end byte theme (0–255).</param>
 131    /// <param name="factor">The interpolation factor; values are clamped to [0, 1]. Non-finite values are treated as 0.
 132    /// <returns>A new <see cref="HexByte" /> representing the interpolated channel theme.</returns>
 2133    public HexByte ToLerpHexByte(byte end, double factor) => new(value: ToLerpByte(end: end, factor: factor));
 134
 135    /// <summary>
 136    /// Computes a gamma-correct (linear-light) interpolation from this channel theme to <paramref name="end" />, return
 137    /// resulting sRGB 8-bit theme.
 138    /// </summary>
 139    /// <param name="end">The end byte theme (0–255).</param>
 140    /// <param name="factor">The interpolation factor; values are clamped to [0, 1]. Non-finite values are treated as 0.
 141    /// <returns>The interpolated sRGB channel byte.</returns>
 142    public byte ToLerpLinearByte(byte end, double factor)
 143    {
 419765144        var t = double.IsFinite(d: factor)
 419765145            ? Math.Clamp(value: factor, min: 0.0, max: 1.0)
 419765146            : 0.0;
 147
 148        // sRGB -> linear
 149        static double ToLinear(byte b)
 150        {
 839530151            var c = b / 255.0;
 152
 839530153            return c <= 0.04045
 839530154                ? c / 12.92
 839530155                : Math.Pow(x: (c + 0.055) / 1.055, y: 2.4);
 156        }
 157
 158        // linear lerp
 419765159        var aL = ToLinear(b: Value);
 419765160        var bL = ToLinear(b: end);
 419765161        var l = aL + (bL - aL) * t;
 162
 163        // linear -> sRGB
 164        static byte FromLinear(double l)
 165        {
 419765166            l = Math.Clamp(value: l, min: 0.0, max: 1.0);
 167
 419765168            var c = l <= 0.0031308
 419765169                ? l * 12.92
 419765170                : 1.055 * Math.Pow(x: l, y: 1.0 / 2.4) - 0.055;
 171
 419765172            return (byte)Math.Clamp(
 419765173                value: Math.Round(value: c * 255.0, mode: MidpointRounding.ToEven), min: 0, max: 255
 419765174            );
 175        }
 176
 419765177        return FromLinear(l: l);
 178    }
 179
 180    /// <summary>
 181    /// Produces an interpolated channel using gamma-correct (linear-light) interpolation. Use for sRGB color channels (
 182    /// Not suitable for alpha coverage; use ToLerpHexByte for alpha.
 183    /// </summary>
 184    /// <param name="end">The target channel theme.</param>
 185    /// <param name="factor">
 186    /// The interpolation factor; values are clamped to the range [0, 1]. Non-finite values are treated as 0.
 187    /// </param>
 188    /// <returns>A new <see cref="HexByte" /> representing the interpolated channel theme.</returns>
 189    public HexByte ToLerpLinearHexByte(HexByte end, double factor)
 419761190        => new(value: ToLerpLinearByte(end: end.Value, factor: factor));
 191
 192    /// <summary>Converts this channel theme to a normalized theme in the range [0, 1] via <c>Value / 255.0</c>.</summar
 193    /// <returns>The normalized channel theme.</returns>
 1167209194    public double ToNormalized() => Value / 255.0;
 195
 196    /// <summary>
 197    /// Converts this sRGB channel theme to linear-light in the range [0, 1] using the sRGB electro-optical transfer fun
 198    /// </summary>
 199    /// <returns>The linear-light channel theme.</returns>
 200    public double ToSrgbLinearValue()
 201    {
 7264905202        var channel = Value / 255.0;
 203
 7264905204        return channel <= 0.04045
 7264905205            ? channel / 12.92
 7264905206            : Math.Pow(x: (channel + 0.055) / 1.055, y: 2.4);
 207    }
 208
 209    /// <summary>Returns the string representation of the HexByte theme.</summary>
 210    /// <returns>The formatted two-character uppercase hexadecimal string.</returns>
 122498211    public override string ToString() => Value.ToString(format: "X2", provider: CultureInfo.InvariantCulture);
 212
 213    /// <summary>
 214    /// Attempts to parse a hexadecimal string into a <see cref="HexByte" />. Accepts 1–2 hex characters after trimming;
 215    /// parsing is case-insensitive.
 216    /// </summary>
 217    /// <param name="value">The hexadecimal string to parse; may be <see langword="null" />.</param>
 218    /// <param name="result">
 219    /// When this method returns, contains the parsed <see cref="HexByte" /> if parsing succeeded; otherwise the default
 220    /// </param>
 221    /// <returns><see langword="true" /> if parsing succeeded; otherwise <see langword="false" />.</returns>
 222    public static bool TryParse(string? value, out HexByte result)
 223    {
 7224        result = default(HexByte);
 225
 7226        if (string.IsNullOrWhiteSpace(value: value))
 227        {
 3228            return false;
 229        }
 230
 4231        var span = value.AsSpan().Trim();
 232
 4233        if (span.Length > 2 || span.Length < 1)
 234        {
 1235            return false;
 236        }
 237
 3238        if (byte.TryParse(
 3239            s: span, style: NumberStyles.HexNumber, provider: CultureInfo.InvariantCulture, result: out var parsed
 3240        ))
 241        {
 2242            result = new HexByte(value: parsed);
 243
 2244            return true;
 245        }
 246
 1247        return false;
 248    }
 249
 250    /// <summary>Returns a theme that indicates whether the values of two <see cref="HexByte" /> objects are equal.</sum
 251    /// <param name="left">The first theme to compare.</param>
 252    /// <param name="right">The second theme to compare.</param>
 253    /// <returns><see langword="true" /> if both have the same theme; otherwise, <see langword="false" />.</returns>
 2254    public static bool operator ==(HexByte left, HexByte right) => left.Equals(other: right);
 255
 256    /// <summary>Determines whether one <see cref="HexByte" /> theme is greater than another.</summary>
 257    /// <param name="left">The left operand.</param>
 258    /// <param name="right">The right operand.</param>
 259    /// <returns>
 260    /// <see langword="true" /> if <paramref name="left" /> is greater than <paramref name="right" />; otherwise,
 261    /// <see langword="false" />.
 262    /// </returns>
 1263    public static bool operator >(HexByte left, HexByte right) => left.CompareTo(other: right) > 0;
 264
 265    /// <summary>Determines whether one <see cref="HexByte" /> theme is greater than or equal to another.</summary>
 266    /// <param name="left">The left operand.</param>
 267    /// <param name="right">The right operand.</param>
 268    /// <returns>
 269    /// <see langword="true" /> if <paramref name="left" /> is greater than or equal to <paramref name="right" />; other
 270    /// <see langword="false" />.
 271    /// </returns>
 2272    public static bool operator >=(HexByte left, HexByte right) => left.CompareTo(other: right) >= 0;
 273
 274    /// <summary>Converts a hexadecimal string to a <see cref="HexByte" /> instance.</summary>
 275    /// <param name="value">The hexadecimal string; must be valid (1–2 hex characters after trimming).</param>
 276    /// <returns>A <see cref="HexByte" /> representing the parsed theme.</returns>
 277    /// <exception cref="AryArgumentException">Thrown if the string is invalid.</exception>
 1278    public static implicit operator HexByte(string value) => new(value: value);
 279
 280    /// <summary>Converts a <see cref="HexByte" /> instance to its two-character uppercase hexadecimal string.</summary>
 281    /// <param name="value">The theme to convert.</param>
 282    /// <returns>The two-character uppercase hexadecimal string.</returns>
 1283    public static implicit operator string(HexByte value) => value.ToString();
 284
 285    /// <summary>Converts a byte to a <see cref="HexByte" /> instance.</summary>
 286    /// <param name="value">The byte theme.</param>
 287    /// <returns>A <see cref="HexByte" /> representing the byte.</returns>
 3501682288    public static implicit operator HexByte(byte value) => new(value: value);
 289
 290    /// <summary>Converts a <see cref="HexByte" /> instance to its byte theme.</summary>
 291    /// <param name="value">The theme to convert.</param>
 292    /// <returns>The underlying byte theme.</returns>
 5293    public static implicit operator byte(HexByte value) => value.Value;
 294
 295    /// <summary>Returns a theme that indicates whether two <see cref="HexByte" /> objects have different values.</summa
 296    /// <param name="left">The first theme to compare.</param>
 297    /// <param name="right">The second theme to compare.</param>
 298    /// <returns>
 299    /// <see langword="true" /> if <paramref name="left" /> and <paramref name="right" /> are not equal; otherwise,
 300    /// <see langword="false" />.
 301    /// </returns>
 2302    public static bool operator !=(HexByte left, HexByte right) => !left.Equals(other: right);
 303
 304    /// <summary>Determines whether one <see cref="HexByte" /> theme is less than another.</summary>
 305    /// <param name="left">The left operand.</param>
 306    /// <param name="right">The right operand.</param>
 307    /// <returns>
 308    /// <see langword="true" /> if <paramref name="left" /> is less than <paramref name="right" />; otherwise,
 309    /// <see langword="false" />.
 310    /// </returns>
 1311    public static bool operator <(HexByte left, HexByte right) => left.CompareTo(other: right) < 0;
 312
 313    /// <summary>Determines whether one <see cref="HexByte" /> theme is less than or equal to another.</summary>
 314    /// <param name="left">The left operand.</param>
 315    /// <param name="right">The right operand.</param>
 316    /// <returns>
 317    /// <see langword="true" /> if <paramref name="left" /> is less than or equal to <paramref name="right" />; otherwis
 318    /// <see langword="false" />.
 319    /// </returns>
 2320    public static bool operator <=(HexByte left, HexByte right) => left.CompareTo(other: right) <= 0;
 321}