< Summary

Information
Class: Allyaria.Theming.Values.AllyariaColorValue
Assembly: Allyaria.Theming
File(s): /home/runner/work/allyaria/allyaria/src/Allyaria.Theming/Values/AllyariaColorValue.cs
Line coverage
100%
Covered lines: 687
Uncovered lines: 0
Coverable lines: 687
Total lines: 1149
Line coverage: 100%
Branch coverage
100%
Covered branches: 82
Total branches: 82
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%11100%
.ctor(...)100%11100%
.ctor(...)100%66100%
.ctor(...)100%1010100%
get_A()100%11100%
get_AlphaByte()100%11100%
get_B()100%11100%
get_G()100%11100%
get_H()100%11100%
get_HexRgb()100%11100%
get_HexRgba()100%11100%
get_Hsv()100%11100%
get_Hsva()100%11100%
get_R()100%11100%
get_Rgb()100%11100%
get_Rgba()100%11100%
get_S()100%11100%
get_V()100%11100%
get_Value()100%11100%
FromHexInline(...)100%11100%
FromHexString(...)100%1414100%
FromHsva(...)100%22100%
FromHsvString(...)100%44100%
FromRgba(...)100%11100%
FromRgbString(...)100%44100%
HsvToRgb(...)100%88100%
NormalizeMaterialKey(...)100%11100%
Parse(...)100%11100%
ParseDouble(...)100%44100%
ParseInt(...)100%44100%
RgbToHsv(...)100%1010100%
ToHexNibble(...)100%1212100%
TryFromMaterialName(...)100%22100%
TryFromWebName(...)100%22100%
TryParse(...)100%11100%
op_Implicit(...)100%11100%
op_Implicit(...)100%11100%

File(s)

/home/runner/work/allyaria/allyaria/src/Allyaria.Theming/Values/AllyariaColorValue.cs

#LineLine coverage
 1using Allyaria.Theming.Constants;
 2using Allyaria.Theming.Contracts;
 3using System.Globalization;
 4using System.Text.RegularExpressions;
 5
 6namespace Allyaria.Theming.Values;
 7
 8/// <summary>
 9/// Represents a framework-agnostic color value with CSS-oriented parsing and formatting, immutable value semantics, and
 10/// total ordering by the uppercase <c>#RRGGBBAA</c> form.
 11/// </summary>
 12/// <remarks>
 13/// This type is an immutable reference type. It supports:
 14/// <list type="bullet">
 15///     <item>
 16///         <description>
 17///         Parsing from <c>#RGB</c>, <c>#RGBA</c>, <c>#RRGGBB</c>, <c>#RRGGBBAA</c>, <c>rgb()</c>, <c>rgba()</c>,
 18///         <c>hsv()</c>, <c>hsva()</c>, CSS Web color names, and Material color names.
 19///         </description>
 20///     </item>
 21///     <item>
 22///         <description>Conversions between RGBA and HSVA (H: 0–360°, S/V: 0–100%, A: 0–1).</description>
 23///     </item>
 24///     <item>
 25///         <description>Formatting to multiple string forms and CSS declarations.</description>
 26///     </item>
 27///     <item>
 28///         <description>Value equality and ordering by the canonical <c>#RRGGBBAA</c> string.</description>
 29///     </item>
 30/// </list>
 31/// All numeric parsing/formatting uses <see cref="CultureInfo.InvariantCulture" />.
 32/// </remarks>
 33public sealed class AllyariaColorValue : ValueBase
 34{
 35    /// <summary>Material Design color lookup table.</summary>
 236    private static readonly Dictionary<string, AllyariaColorValue> MaterialMap = new(StringComparer.OrdinalIgnoreCase)
 237    {
 238        // Red
 239        ["red50"] = FromHexInline("#FFEBEEFF"),
 240        ["red100"] = FromHexInline("#FFCDD2FF"),
 241        ["red200"] = FromHexInline("#EF9A9AFF"),
 242        ["red300"] = FromHexInline("#E57373FF"),
 243        ["red400"] = FromHexInline("#EF5350FF"),
 244        ["red500"] = FromHexInline("#F44336FF"),
 245        ["red600"] = FromHexInline("#E53935FF"),
 246        ["red700"] = FromHexInline("#D32F2FFF"),
 247        ["red800"] = FromHexInline("#C62828FF"),
 248        ["red900"] = FromHexInline("#B71C1CFF"),
 249        ["reda100"] = FromHexInline("#FF8A80FF"),
 250        ["reda200"] = FromHexInline("#FF5252FF"),
 251        ["reda400"] = FromHexInline("#FF1744FF"),
 252        ["reda700"] = FromHexInline("#D50000FF"),
 253
 254        // Pink
 255        ["pink50"] = FromHexInline("#FCE4ECFF"),
 256        ["pink100"] = FromHexInline("#F8BBD0FF"),
 257        ["pink200"] = FromHexInline("#F48FB1FF"),
 258        ["pink300"] = FromHexInline("#F06292FF"),
 259        ["pink400"] = FromHexInline("#EC407AFF"),
 260        ["pink500"] = FromHexInline("#E91E63FF"),
 261        ["pink600"] = FromHexInline("#D81B60FF"),
 262        ["pink700"] = FromHexInline("#C2185BFF"),
 263        ["pink800"] = FromHexInline("#AD1457FF"),
 264        ["pink900"] = FromHexInline("#880E4FFF"),
 265        ["pinka100"] = FromHexInline("#FF80ABFF"),
 266        ["pinka200"] = FromHexInline("#FF4081FF"),
 267        ["pinka400"] = FromHexInline("#F50057FF"),
 268        ["pinka700"] = FromHexInline("#C51162FF"),
 269
 270        // Purple
 271        ["purple50"] = FromHexInline("#F3E5F5FF"),
 272        ["purple100"] = FromHexInline("#E1BEE7FF"),
 273        ["purple200"] = FromHexInline("#CE93D8FF"),
 274        ["purple300"] = FromHexInline("#BA68C8FF"),
 275        ["purple400"] = FromHexInline("#AB47BCFF"),
 276        ["purple500"] = FromHexInline("#9C27B0FF"),
 277        ["purple600"] = FromHexInline("#8E24AAFF"),
 278        ["purple700"] = FromHexInline("#7B1FA2FF"),
 279        ["purple800"] = FromHexInline("#6A1B9AFF"),
 280        ["purple900"] = FromHexInline("#4A148CFF"),
 281        ["purplea100"] = FromHexInline("#EA80FCFF"),
 282        ["purplea200"] = FromHexInline("#E040FBFF"),
 283        ["purplea400"] = FromHexInline("#D500F9FF"),
 284        ["purplea700"] = FromHexInline("#AA00FFFF"),
 285
 286        // Deep Purple
 287        ["deeppurple50"] = FromHexInline("#EDE7F6FF"),
 288        ["deeppurple100"] = FromHexInline("#D1C4E9FF"),
 289        ["deeppurple200"] = FromHexInline("#B39DDBFF"),
 290        ["deeppurple300"] = FromHexInline("#9575CDFF"),
 291        ["deeppurple400"] = FromHexInline("#7E57C2FF"),
 292        ["deeppurple500"] = FromHexInline("#673AB7FF"),
 293        ["deeppurple600"] = FromHexInline("#5E35B1FF"),
 294        ["deeppurple700"] = FromHexInline("#512DA8FF"),
 295        ["deeppurple800"] = FromHexInline("#4527A0FF"),
 296        ["deeppurple900"] = FromHexInline("#311B92FF"),
 297        ["deeppurplea100"] = FromHexInline("#B388FFFF"),
 298        ["deeppurplea200"] = FromHexInline("#7C4DFFFF"),
 299        ["deeppurplea400"] = FromHexInline("#651FFFFF"),
 2100        ["deeppurplea700"] = FromHexInline("#6200EAFF"),
 2101
 2102        // Indigo
 2103        ["indigo50"] = FromHexInline("#E8EAF6FF"),
 2104        ["indigo100"] = FromHexInline("#C5CAE9FF"),
 2105        ["indigo200"] = FromHexInline("#9FA8DAFF"),
 2106        ["indigo300"] = FromHexInline("#7986CBFF"),
 2107        ["indigo400"] = FromHexInline("#5C6BC0FF"),
 2108        ["indigo500"] = FromHexInline("#3F51B5FF"),
 2109        ["indigo600"] = FromHexInline("#3949ABFF"),
 2110        ["indigo700"] = FromHexInline("#303F9FFF"),
 2111        ["indigo800"] = FromHexInline("#283593FF"),
 2112        ["indigo900"] = FromHexInline("#1A237EFF"),
 2113        ["indigoa100"] = FromHexInline("#8C9EFFFF"),
 2114        ["indigoa200"] = FromHexInline("#536DFEFF"),
 2115        ["indigoa400"] = FromHexInline("#3D5AFEFF"),
 2116        ["indigoa700"] = FromHexInline("#304FFEFF"),
 2117
 2118        // Blue
 2119        ["blue50"] = FromHexInline("#E3F2FDFF"),
 2120        ["blue100"] = FromHexInline("#BBDEFBFF"),
 2121        ["blue200"] = FromHexInline("#90CAF9FF"),
 2122        ["blue300"] = FromHexInline("#64B5F6FF"),
 2123        ["blue400"] = FromHexInline("#42A5F5FF"),
 2124        ["blue500"] = FromHexInline("#2196F3FF"),
 2125        ["blue600"] = FromHexInline("#1E88E5FF"),
 2126        ["blue700"] = FromHexInline("#1976D2FF"),
 2127        ["blue800"] = FromHexInline("#1565C0FF"),
 2128        ["blue900"] = FromHexInline("#0D47A1FF"),
 2129        ["bluea100"] = FromHexInline("#82B1FFFF"),
 2130        ["bluea200"] = FromHexInline("#448AFFFF"),
 2131        ["bluea400"] = FromHexInline("#2979FFFF"),
 2132        ["bluea700"] = FromHexInline("#2962FFFF"),
 2133
 2134        // Light Blue
 2135        ["lightblue50"] = FromHexInline("#E1F5FEFF"),
 2136        ["lightblue100"] = FromHexInline("#B3E5FCFF"),
 2137        ["lightblue200"] = FromHexInline("#81D4FAFF"),
 2138        ["lightblue300"] = FromHexInline("#4FC3F7FF"),
 2139        ["lightblue400"] = FromHexInline("#29B6F6FF"),
 2140        ["lightblue500"] = FromHexInline("#03A9F4FF"),
 2141        ["lightblue600"] = FromHexInline("#039BE5FF"),
 2142        ["lightblue700"] = FromHexInline("#0288D1FF"),
 2143        ["lightblue800"] = FromHexInline("#0277BDFF"),
 2144        ["lightblue900"] = FromHexInline("#01579BFF"),
 2145        ["lightbluea100"] = FromHexInline("#80D8FFFF"),
 2146        ["lightbluea200"] = FromHexInline("#40C4FFFF"),
 2147        ["lightbluea400"] = FromHexInline("#00B0FFFF"),
 2148        ["lightbluea700"] = FromHexInline("#0091EAFF"),
 2149
 2150        // Cyan
 2151        ["cyan50"] = FromHexInline("#E0F7FAFF"),
 2152        ["cyan100"] = FromHexInline("#B2EBF2FF"),
 2153        ["cyan200"] = FromHexInline("#80DEEAFF"),
 2154        ["cyan300"] = FromHexInline("#4DD0E1FF"),
 2155        ["cyan400"] = FromHexInline("#26C6DAFF"),
 2156        ["cyan500"] = FromHexInline("#00BCD4FF"),
 2157        ["cyan600"] = FromHexInline("#00ACC1FF"),
 2158        ["cyan700"] = FromHexInline("#0097A7FF"),
 2159        ["cyan800"] = FromHexInline("#00838FFF"),
 2160        ["cyan900"] = FromHexInline("#006064FF"),
 2161        ["cyana100"] = FromHexInline("#84FFFFFF"),
 2162        ["cyana200"] = FromHexInline("#18FFFFFF"),
 2163        ["cyana400"] = FromHexInline("#00E5FFFF"),
 2164        ["cyana700"] = FromHexInline("#00B8D4FF"),
 2165
 2166        // Teal
 2167        ["teal50"] = FromHexInline("#E0F2F1FF"),
 2168        ["teal100"] = FromHexInline("#B2DFDBFF"),
 2169        ["teal200"] = FromHexInline("#80CBC4FF"),
 2170        ["teal300"] = FromHexInline("#4DB6ACFF"),
 2171        ["teal400"] = FromHexInline("#26A69AFF"),
 2172        ["teal500"] = FromHexInline("#009688FF"),
 2173        ["teal600"] = FromHexInline("#00897BFF"),
 2174        ["teal700"] = FromHexInline("#00796BFF"),
 2175        ["teal800"] = FromHexInline("#00695CFF"),
 2176        ["teal900"] = FromHexInline("#004D40FF"),
 2177        ["teala100"] = FromHexInline("#A7FFEBFF"),
 2178        ["teala200"] = FromHexInline("#64FFDAFF"),
 2179        ["teala400"] = FromHexInline("#1DE9B6FF"),
 2180        ["teala700"] = FromHexInline("#00BFA5FF"),
 2181
 2182        // Green
 2183        ["green50"] = FromHexInline("#E8F5E9FF"),
 2184        ["green100"] = FromHexInline("#C8E6C9FF"),
 2185        ["green200"] = FromHexInline("#A5D6A7FF"),
 2186        ["green300"] = FromHexInline("#81C784FF"),
 2187        ["green400"] = FromHexInline("#66BB6AFF"),
 2188        ["green500"] = FromHexInline("#4CAF50FF"),
 2189        ["green600"] = FromHexInline("#43A047FF"),
 2190        ["green700"] = FromHexInline("#388E3CFF"),
 2191        ["green800"] = FromHexInline("#2E7D32FF"),
 2192        ["green900"] = FromHexInline("#1B5E20FF"),
 2193        ["greena100"] = FromHexInline("#B9F6CAFF"),
 2194        ["greena200"] = FromHexInline("#69F0AEFF"),
 2195        ["greena400"] = FromHexInline("#00E676FF"),
 2196        ["greena700"] = FromHexInline("#00C853FF"),
 2197
 2198        // Light Green
 2199        ["lightgreen50"] = FromHexInline("#F1F8E9FF"),
 2200        ["lightgreen100"] = FromHexInline("#DCEDC8FF"),
 2201        ["lightgreen200"] = FromHexInline("#C5E1A5FF"),
 2202        ["lightgreen300"] = FromHexInline("#AED581FF"),
 2203        ["lightgreen400"] = FromHexInline("#9CCC65FF"),
 2204        ["lightgreen500"] = FromHexInline("#8BC34AFF"),
 2205        ["lightgreen600"] = FromHexInline("#7CB342FF"),
 2206        ["lightgreen700"] = FromHexInline("#689F38FF"),
 2207        ["lightgreen800"] = FromHexInline("#558B2FFF"),
 2208        ["lightgreen900"] = FromHexInline("#33691EFF"),
 2209        ["lightgreena100"] = FromHexInline("#CCFF90FF"),
 2210        ["lightgreena200"] = FromHexInline("#B2FF59FF"),
 2211        ["lightgreena400"] = FromHexInline("#76FF03FF"),
 2212        ["lightgreena700"] = FromHexInline("#64DD17FF"),
 2213
 2214        // Lime
 2215        ["lime50"] = FromHexInline("#F9FBE7FF"),
 2216        ["lime100"] = FromHexInline("#F0F4C3FF"),
 2217        ["lime200"] = FromHexInline("#E6EE9CFF"),
 2218        ["lime300"] = FromHexInline("#DCE775FF"),
 2219        ["lime400"] = FromHexInline("#D4E157FF"),
 2220        ["lime500"] = FromHexInline("#CDDC39FF"),
 2221        ["lime600"] = FromHexInline("#C0CA33FF"),
 2222        ["lime700"] = FromHexInline("#AFB42BFF"),
 2223        ["lime800"] = FromHexInline("#9E9D24FF"),
 2224        ["lime900"] = FromHexInline("#827717FF"),
 2225        ["limea100"] = FromHexInline("#F4FF81FF"),
 2226        ["limea200"] = FromHexInline("#EEFF41FF"),
 2227        ["limea400"] = FromHexInline("#C6FF00FF"),
 2228        ["limea700"] = FromHexInline("#AEEA00FF"),
 2229
 2230        // Yellow
 2231        ["yellow50"] = FromHexInline("#FFFDE7FF"),
 2232        ["yellow100"] = FromHexInline("#FFF9C4FF"),
 2233        ["yellow200"] = FromHexInline("#FFF59DFF"),
 2234        ["yellow300"] = FromHexInline("#FFF176FF"),
 2235        ["yellow400"] = FromHexInline("#FFEE58FF"),
 2236        ["yellow500"] = FromHexInline("#FFEB3BFF"),
 2237        ["yellow600"] = FromHexInline("#FDD835FF"),
 2238        ["yellow700"] = FromHexInline("#FBC02DFF"),
 2239        ["yellow800"] = FromHexInline("#F9A825FF"),
 2240        ["yellow900"] = FromHexInline("#F57F17FF"),
 2241        ["yellowa100"] = FromHexInline("#FFFF8DFF"),
 2242        ["yellowa200"] = FromHexInline("#FFFF00FF"),
 2243        ["yellowa400"] = FromHexInline("#FFEA00FF"),
 2244        ["yellowa700"] = FromHexInline("#FFD600FF"),
 2245
 2246        // Amber
 2247        ["amber50"] = FromHexInline("#FFF8E1FF"),
 2248        ["amber100"] = FromHexInline("#FFECB3FF"),
 2249        ["amber200"] = FromHexInline("#FFE082FF"),
 2250        ["amber300"] = FromHexInline("#FFD54FFF"),
 2251        ["amber400"] = FromHexInline("#FFCA28FF"),
 2252        ["amber500"] = FromHexInline("#FFC107FF"),
 2253        ["amber600"] = FromHexInline("#FFB300FF"),
 2254        ["amber700"] = FromHexInline("#FFA000FF"),
 2255        ["amber800"] = FromHexInline("#FF8F00FF"),
 2256        ["amber900"] = FromHexInline("#FF6F00FF"),
 2257        ["ambera100"] = FromHexInline("#FFE57FFF"),
 2258        ["ambera200"] = FromHexInline("#FFD740FF"),
 2259        ["ambera400"] = FromHexInline("#FFC400FF"),
 2260        ["ambera700"] = FromHexInline("#FFAB00FF"),
 2261
 2262        // Orange
 2263        ["orange50"] = FromHexInline("#FFF3E0FF"),
 2264        ["orange100"] = FromHexInline("#FFE0B2FF"),
 2265        ["orange200"] = FromHexInline("#FFCC80FF"),
 2266        ["orange300"] = FromHexInline("#FFB74DFF"),
 2267        ["orange400"] = FromHexInline("#FFA726FF"),
 2268        ["orange500"] = FromHexInline("#FF9800FF"),
 2269        ["orange600"] = FromHexInline("#FB8C00FF"),
 2270        ["orange700"] = FromHexInline("#F57C00FF"),
 2271        ["orange800"] = FromHexInline("#EF6C00FF"),
 2272        ["orange900"] = FromHexInline("#E65100FF"),
 2273        ["orangea100"] = FromHexInline("#FFD180FF"),
 2274        ["orangea200"] = FromHexInline("#FFAB40FF"),
 2275        ["orangea400"] = FromHexInline("#FF9100FF"),
 2276        ["orangea700"] = FromHexInline("#FF6D00FF"),
 2277
 2278        // Deep Orange
 2279        ["deeporange50"] = FromHexInline("#FBE9E7FF"),
 2280        ["deeporange100"] = FromHexInline("#FFCCBCFF"),
 2281        ["deeporange200"] = FromHexInline("#FFAB91FF"),
 2282        ["deeporange300"] = FromHexInline("#FF8A65FF"),
 2283        ["deeporange400"] = FromHexInline("#FF7043FF"),
 2284        ["deeporange500"] = FromHexInline("#FF5722FF"),
 2285        ["deeporange600"] = FromHexInline("#F4511EFF"),
 2286        ["deeporange700"] = FromHexInline("#E64A19FF"),
 2287        ["deeporange800"] = FromHexInline("#D84315FF"),
 2288        ["deeporange900"] = FromHexInline("#BF360CFF"),
 2289        ["deeporangea100"] = FromHexInline("#FF9E80FF"),
 2290        ["deeporangea200"] = FromHexInline("#FF6E40FF"),
 2291        ["deeporangea400"] = FromHexInline("#FF3D00FF"),
 2292        ["deeporangea700"] = FromHexInline("#DD2C00FF"),
 2293
 2294        // Brown (no accents in Material 2 palette)
 2295        ["brown50"] = FromHexInline("#EFEBE9FF"),
 2296        ["brown100"] = FromHexInline("#D7CCC8FF"),
 2297        ["brown200"] = FromHexInline("#BCAAA4FF"),
 2298        ["brown300"] = FromHexInline("#A1887FFF"),
 2299        ["brown400"] = FromHexInline("#8D6E63FF"),
 2300        ["brown500"] = FromHexInline("#795548FF"),
 2301        ["brown600"] = FromHexInline("#6D4C41FF"),
 2302        ["brown700"] = FromHexInline("#5D4037FF"),
 2303        ["brown800"] = FromHexInline("#4E342EFF"),
 2304        ["brown900"] = FromHexInline("#3E2723FF"),
 2305
 2306        // Blue Grey (no accents)
 2307        ["bluegrey50"] = FromHexInline("#ECEFF1FF"),
 2308        ["bluegrey100"] = FromHexInline("#CFD8DCFF"),
 2309        ["bluegrey200"] = FromHexInline("#B0BEC5FF"),
 2310        ["bluegrey300"] = FromHexInline("#90A4AEFF"),
 2311        ["bluegrey400"] = FromHexInline("#78909CFF"),
 2312        ["bluegrey500"] = FromHexInline("#607D8BFF"),
 2313        ["bluegrey600"] = FromHexInline("#546E7AFF"),
 2314        ["bluegrey700"] = FromHexInline("#455A64FF"),
 2315        ["bluegrey800"] = FromHexInline("#37474FFF"),
 2316        ["bluegrey900"] = FromHexInline("#263238FF"),
 2317
 2318        // Grey (no accents)
 2319        ["grey50"] = FromHexInline("#FAFAFAFF"),
 2320        ["grey100"] = FromHexInline("#F5F5F5FF"),
 2321        ["grey200"] = FromHexInline("#EEEEEEFF"),
 2322        ["grey300"] = FromHexInline("#E0E0E0FF"),
 2323        ["grey400"] = FromHexInline("#BDBDBDFF"),
 2324        ["grey500"] = FromHexInline("#9E9E9EFF"),
 2325        ["grey600"] = FromHexInline("#757575FF"),
 2326        ["grey700"] = FromHexInline("#616161FF"),
 2327        ["grey800"] = FromHexInline("#424242FF"),
 2328        ["grey900"] = FromHexInline("#212121FF"),
 2329
 2330        // Monochrome convenience entries
 2331        ["black"] = FromHexInline("#000000FF"),
 2332        ["white"] = FromHexInline("#FFFFFFFF")
 2333    };
 334
 335    /// <summary>
 336    /// Compiled regular expression for parsing CSS <c>hsv()</c> and <c>hsva()</c> functions with a safe timeout.
 337    /// </summary>
 2338    private static readonly Regex RxHsv = new(
 2339        @"^hsva?\s*\(\s*(?<h>(\d*\.)?\d+)\s*,\s*(?<s>(\d*\.)?\d+)\s*%\s*,\s*(?<v>(\d*\.)?\d+)\s*%(?:\s*,\s*(?<a>((\d*\.)
 2340        RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase,
 2341        TimeSpan.FromMilliseconds(250)
 2342    );
 343
 344    /// <summary>
 345    /// Compiled regular expression for parsing CSS <c>rgb()</c> and <c>rgba()</c> functions with a safe timeout.
 346    /// </summary>
 2347    private static readonly Regex RxRgb = new(
 2348        @"^rgba?\s*\(\s*(?<r>\d{1,3})\s*,\s*(?<g>\d{1,3})\s*,\s*(?<b>\d{1,3})(?:\s*,\s*(?<a>((\d*\.)?\d+)))?\s*\)\s*$",
 2349        RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase,
 2350        TimeSpan.FromMilliseconds(250)
 2351    );
 352
 353    /// <summary>CSS Web color lookup table.</summary>
 2354    private static readonly Dictionary<string, AllyariaColorValue> WebNameMap = new(StringComparer.OrdinalIgnoreCase)
 2355    {
 2356        ["aliceblue"] = FromHexInline("#F0F8FFFF"),
 2357        ["antiquewhite"] = FromHexInline("#FAEBD7FF"),
 2358        ["aqua"] = FromHexInline("#00FFFFFF"),
 2359        ["aquamarine"] = FromHexInline("#7FFFD4FF"),
 2360        ["azure"] = FromHexInline("#F0FFFFFF"),
 2361        ["beige"] = FromHexInline("#F5F5DCFF"),
 2362        ["bisque"] = FromHexInline("#FFE4C4FF"),
 2363        ["black"] = FromHexInline("#000000FF"),
 2364        ["blanchedalmond"] = FromHexInline("#FFEBCDFF"),
 2365        ["blue"] = FromHexInline("#0000FFFF"),
 2366        ["blueviolet"] = FromHexInline("#8A2BE2FF"),
 2367        ["brown"] = FromHexInline("#A52A2AFF"),
 2368        ["burlywood"] = FromHexInline("#DEB887FF"),
 2369        ["cadetblue"] = FromHexInline("#5F9EA0FF"),
 2370        ["chartreuse"] = FromHexInline("#7FFF00FF"),
 2371        ["chocolate"] = FromHexInline("#D2691EFF"),
 2372        ["coral"] = FromHexInline("#FF7F50FF"),
 2373        ["cornflowerblue"] = FromHexInline("#6495EDFF"),
 2374        ["cornsilk"] = FromHexInline("#FFF8DCFF"),
 2375        ["crimson"] = FromHexInline("#DC143CFF"),
 2376        ["cyan"] = FromHexInline("#00FFFFFF"),
 2377        ["darkblue"] = FromHexInline("#00008BFF"),
 2378        ["darkcyan"] = FromHexInline("#008B8BFF"),
 2379        ["darkgoldenrod"] = FromHexInline("#B8860BFF"),
 2380        ["darkgray"] = FromHexInline("#A9A9A9FF"),
 2381        ["darkgreen"] = FromHexInline("#006400FF"),
 2382        ["darkkhaki"] = FromHexInline("#BDB76BFF"),
 2383        ["darkmagenta"] = FromHexInline("#8B008BFF"),
 2384        ["darkolivegreen"] = FromHexInline("#556B2FFF"),
 2385        ["darkorange"] = FromHexInline("#FF8C00FF"),
 2386        ["darkorchid"] = FromHexInline("#9932CCFF"),
 2387        ["darkred"] = FromHexInline("#8B0000FF"),
 2388        ["darksalmon"] = FromHexInline("#E9967AFF"),
 2389        ["darkseagreen"] = FromHexInline("#8FBC8FFF"),
 2390        ["darkslateblue"] = FromHexInline("#483D8BFF"),
 2391        ["darkslategray"] = FromHexInline("#2F4F4FFF"),
 2392        ["darkturquoise"] = FromHexInline("#00CED1FF"),
 2393        ["darkviolet"] = FromHexInline("#9400D3FF"),
 2394        ["deeppink"] = FromHexInline("#FF1493FF"),
 2395        ["deepskyblue"] = FromHexInline("#00BFFFFF"),
 2396        ["dimgray"] = FromHexInline("#696969FF"),
 2397        ["dodgerblue"] = FromHexInline("#1E90FFFF"),
 2398        ["firebrick"] = FromHexInline("#B22222FF"),
 2399        ["floralwhite"] = FromHexInline("#FFFAF0FF"),
 2400        ["forestgreen"] = FromHexInline("#228B22FF"),
 2401        ["fuchsia"] = FromHexInline("#FF00FFFF"),
 2402        ["gainsboro"] = FromHexInline("#DCDCDCFF"),
 2403        ["ghostwhite"] = FromHexInline("#F8F8FFFF"),
 2404        ["gold"] = FromHexInline("#FFD700FF"),
 2405        ["goldenrod"] = FromHexInline("#DAA520FF"),
 2406        ["gray"] = FromHexInline("#808080FF"),
 2407        ["green"] = FromHexInline("#008000FF"),
 2408        ["greenyellow"] = FromHexInline("#ADFF2FFF"),
 2409        ["honeydew"] = FromHexInline("#F0FFF0FF"),
 2410        ["hotpink"] = FromHexInline("#FF69B4FF"),
 2411        ["indianred"] = FromHexInline("#CD5C5CFF"),
 2412        ["indigo"] = FromHexInline("#4B0082FF"),
 2413        ["ivory"] = FromHexInline("#FFFFF0FF"),
 2414        ["khaki"] = FromHexInline("#F0E68CFF"),
 2415        ["lavender"] = FromHexInline("#E6E6FAFF"),
 2416        ["lavenderblush"] = FromHexInline("#FFF0F5FF"),
 2417        ["lawngreen"] = FromHexInline("#7CFC00FF"),
 2418        ["lemonchiffon"] = FromHexInline("#FFFACDFF"),
 2419        ["lightblue"] = FromHexInline("#ADD8E6FF"),
 2420        ["lightcoral"] = FromHexInline("#F08080FF"),
 2421        ["lightcyan"] = FromHexInline("#E0FFFFFF"),
 2422        ["lightgoldenrodyellow"] = FromHexInline("#FAFAD2FF"),
 2423        ["lightgray"] = FromHexInline("#D3D3D3FF"),
 2424        ["lightgreen"] = FromHexInline("#90EE90FF"),
 2425        ["lightpink"] = FromHexInline("#FFB6C1FF"),
 2426        ["lightsalmon"] = FromHexInline("#FFA07AFF"),
 2427        ["lightseagreen"] = FromHexInline("#20B2AAFF"),
 2428        ["lightskyblue"] = FromHexInline("#87CEFAFF"),
 2429        ["lightslategray"] = FromHexInline("#778899FF"),
 2430        ["lightsteelblue"] = FromHexInline("#B0C4DEFF"),
 2431        ["lightyellow"] = FromHexInline("#FFFFE0FF"),
 2432        ["lime"] = FromHexInline("#00FF00FF"),
 2433        ["limegreen"] = FromHexInline("#32CD32FF"),
 2434        ["linen"] = FromHexInline("#FAF0E6FF"),
 2435        ["magenta"] = FromHexInline("#FF00FFFF"),
 2436        ["maroon"] = FromHexInline("#800000FF"),
 2437        ["mediumaquamarine"] = FromHexInline("#66CDAAFF"),
 2438        ["mediumblue"] = FromHexInline("#0000CDFF"),
 2439        ["mediumorchid"] = FromHexInline("#BA55D3FF"),
 2440        ["mediumpurple"] = FromHexInline("#9370DBFF"),
 2441        ["mediumseagreen"] = FromHexInline("#3CB371FF"),
 2442        ["mediumslateblue"] = FromHexInline("#7B68EEFF"),
 2443        ["mediumspringgreen"] = FromHexInline("#00FA9AFF"),
 2444        ["mediumturquoise"] = FromHexInline("#48D1CCFF"),
 2445        ["mediumvioletred"] = FromHexInline("#C71585FF"),
 2446        ["midnightblue"] = FromHexInline("#191970FF"),
 2447        ["mintcream"] = FromHexInline("#F5FFFAFF"),
 2448        ["mistyrose"] = FromHexInline("#FFE4E1FF"),
 2449        ["moccasin"] = FromHexInline("#FFE4B5FF"),
 2450        ["navajowhite"] = FromHexInline("#FFDEADFF"),
 2451        ["navy"] = FromHexInline("#000080FF"),
 2452        ["oldlace"] = FromHexInline("#FDF5E6FF"),
 2453        ["olive"] = FromHexInline("#808000FF"),
 2454        ["olivedrab"] = FromHexInline("#6B8E23FF"),
 2455        ["orange"] = FromHexInline("#FFA500FF"),
 2456        ["orangered"] = FromHexInline("#FF4500FF"),
 2457        ["orchid"] = FromHexInline("#DA70D6FF"),
 2458        ["palegoldenrod"] = FromHexInline("#EEE8AAFF"),
 2459        ["palegreen"] = FromHexInline("#98FB98FF"),
 2460        ["paleturquoise"] = FromHexInline("#AFEEEEFF"),
 2461        ["palevioletred"] = FromHexInline("#DB7093FF"),
 2462        ["papayawhip"] = FromHexInline("#FFEFD5FF"),
 2463        ["peachpuff"] = FromHexInline("#FFDAB9FF"),
 2464        ["peru"] = FromHexInline("#CD853FFF"),
 2465        ["pink"] = FromHexInline("#FFC0CBFF"),
 2466        ["plum"] = FromHexInline("#DDA0DDFF"),
 2467        ["powderblue"] = FromHexInline("#B0E0E6FF"),
 2468        ["purple"] = FromHexInline("#800080FF"),
 2469        ["red"] = FromHexInline("#FF0000FF"),
 2470        ["rosybrown"] = FromHexInline("#BC8F8FFF"),
 2471        ["royalblue"] = FromHexInline("#4169E1FF"),
 2472        ["saddlebrown"] = FromHexInline("#8B4513FF"),
 2473        ["salmon"] = FromHexInline("#FA8072FF"),
 2474        ["sandybrown"] = FromHexInline("#F4A460FF"),
 2475        ["seagreen"] = FromHexInline("#2E8B57FF"),
 2476        ["seashell"] = FromHexInline("#FFF5EEFF"),
 2477        ["sienna"] = FromHexInline("#A0522DFF"),
 2478        ["silver"] = FromHexInline("#C0C0C0FF"),
 2479        ["skyblue"] = FromHexInline("#87CEEBFF"),
 2480        ["slateblue"] = FromHexInline("#6A5ACDFF"),
 2481        ["slategray"] = FromHexInline("#708090FF"),
 2482        ["snow"] = FromHexInline("#FFFAFAFF"),
 2483        ["springgreen"] = FromHexInline("#00FF7FFF"),
 2484        ["steelblue"] = FromHexInline("#4682B4FF"),
 2485        ["tan"] = FromHexInline("#D2B48CFF"),
 2486        ["teal"] = FromHexInline("#008080FF"),
 2487        ["thistle"] = FromHexInline("#D8BFD8FF"),
 2488        ["tomato"] = FromHexInline("#FF6347FF"),
 2489        ["turquoise"] = FromHexInline("#40E0D0FF"),
 2490        ["violet"] = FromHexInline("#EE82EEFF"),
 2491        ["wheat"] = FromHexInline("#F5DEB3FF"),
 2492        ["white"] = FromHexInline("#FFFFFFFF"),
 2493        ["whitesmoke"] = FromHexInline("#F5F5F5FF"),
 2494        ["yellow"] = FromHexInline("#FFFF00FF"),
 2495        ["yellowgreen"] = FromHexInline("#9ACD32FF"),
 2496        ["transparent"] = FromHexInline("#00000000")
 2497    };
 498
 499    /// <summary>Initializes an instance from HSV(A) channels after validation and conversion to RGBA.</summary>
 500    /// <param name="h">Hue in degrees (0–360).</param>
 501    /// <param name="s">Saturation in percent (0–100).</param>
 502    /// <param name="v">Value (brightness) in percent (0–100).</param>
 503    /// <param name="a">Alpha in [0–1].</param>
 504    /// <exception cref="ArgumentOutOfRangeException">Thrown if any channel lies outside the valid range.</exception>
 505    private AllyariaColorValue(double h, double s, double v, double a = 1.0)
 1326506        : base(string.Empty)
 507    {
 1326508        HsvToRgb(h, s, v, out var r, out var g, out var b);
 1326509        R = r;
 1326510        G = g;
 1326511        B = b;
 1326512        A = a;
 1326513    }
 514
 515    /// <summary>Initializes an instance from RGB(A) channels after validation.</summary>
 516    /// <param name="r">Red channel in [0–255].</param>
 517    /// <param name="g">Green channel in [0–255].</param>
 518    /// <param name="b">Blue channel in [0–255].</param>
 519    /// <param name="a">Alpha in [0–1].</param>
 520    /// <exception cref="ArgumentOutOfRangeException">Thrown if <paramref name="a" /> lies outside [0,1].</exception>
 521    private AllyariaColorValue(byte r, byte g, byte b, double a = 1.0)
 1058522        : base(string.Empty)
 523    {
 1058524        if (a is < 0 or > 1.0)
 525        {
 4526            throw new ArgumentOutOfRangeException(nameof(a), a, "A must be between 0 and 1.0.");
 527        }
 528
 1054529        R = r;
 1054530        G = g;
 1054531        B = b;
 1054532        A = a;
 1054533    }
 534
 535    /// <summary>Initializes an instance by parsing the provided CSS/hex/name color string.</summary>
 536    /// <param name="value">
 537    /// A color value in <c>#RGB</c>, <c>#RGBA</c>, <c>#RRGGBB</c>, <c>#RRGGBBAA</c>, <c>rgb()</c>, <c>rgba()</c>, <c>hs
 538    /// , <c>hsva()</c>, a CSS Web color name, or a Material color name.
 539    /// </param>
 540    /// <exception cref="ArgumentException">Thrown when the value cannot be parsed.</exception>
 541    public AllyariaColorValue(string value)
 490542        : base(string.Empty)
 543    {
 544        try
 545        {
 490546            ArgumentException.ThrowIfNullOrWhiteSpace(value, nameof(value));
 547
 488548            var s = value.Trim();
 549
 550            // Hex forms
 488551            if (s.StartsWith("#", StringComparison.Ordinal))
 552            {
 444553                FromHexString(s, out var r, out var g, out var b, out var a);
 440554                R = r;
 440555                G = g;
 440556                B = b;
 440557                A = a;
 558
 440559                return;
 560            }
 561
 562            // rgb()/rgba()
 44563            if (s.StartsWith("rgb", StringComparison.OrdinalIgnoreCase))
 564            {
 12565                FromRgbString(s, out var r, out var g, out var b, out var a);
 6566                R = r;
 6567                G = g;
 6568                B = b;
 6569                A = a;
 570
 6571                return;
 572            }
 573
 574            // hsv()/hsva()
 32575            if (s.StartsWith("hsv", StringComparison.OrdinalIgnoreCase))
 576            {
 12577                FromHsvString(s, out var r, out var g, out var b, out var a);
 4578                R = r;
 4579                G = g;
 4580                B = b;
 4581                A = a;
 582
 4583                return;
 584            }
 585
 586            // Named palettes
 20587            if (TryFromWebName(s, out var web))
 588            {
 8589                R = web.R;
 8590                G = web.G;
 8591                B = web.B;
 8592                A = web.A;
 593
 8594                return;
 595            }
 596
 12597            if (TryFromMaterialName(s, out var mat))
 598            {
 6599                R = mat.R;
 6600                G = mat.G;
 6601                B = mat.B;
 6602                A = mat.A;
 603
 6604                return;
 605            }
 606
 6607            throw new ArgumentException("Unable to parse value.", nameof(value));
 608        }
 26609        catch (Exception exception)
 610        {
 26611            throw new ArgumentException(
 26612                $"Unrecognized color: '{value}'. Expected #RRGGBB, #RRGGBBAA, rgb(), rgba(), hsv(), hsva(), a CSS Web co
 26613                nameof(value), exception
 26614            );
 615        }
 464616    }
 617
 618    /// <summary>Gets the alpha channel as a unit value in the range [0..1].</summary>
 440619    public double A { get; }
 620
 621    /// <summary>Gets the alpha component rendered as a byte in the range <c>[0..255]</c>.</summary>
 622    /// <remarks>
 623    /// The underlying <see cref="A" /> value (unit interval) is multiplied by 255 and rounded using
 624    /// <see cref="MidpointRounding.AwayFromZero" />; the result is then clamped to <c>[0..255]</c>.
 625    /// </remarks>
 406626    private byte AlphaByte => (byte)Math.Clamp((int)Math.Round(A * 255.0, MidpointRounding.AwayFromZero), 0, 255);
 627
 628    /// <summary>Gets the blue channel in the range [0..255].</summary>
 4712629    public byte B { get; }
 630
 631    /// <summary>Gets the green channel in the range [0..255].</summary>
 4712632    public byte G { get; }
 633
 634    /// <summary>Gets the hue in degrees in the range [0..360].</summary>
 635    /// <remarks>The value is computed from the underlying RGB channels.</remarks>
 636    public double H
 637    {
 638        get
 639        {
 134640            RgbToHsv(R, G, B, out var h, out _, out _);
 641
 134642            return h;
 643        }
 644    }
 645
 646    /// <summary>Gets the uppercase <c>#RRGGBB</c> representation of the color (alpha omitted).</summary>
 18647    public string HexRgb => $"#{R:X2}{G:X2}{B:X2}";
 648
 649    /// <summary>Gets the uppercase <c>#RRGGBBAA</c> representation of the color (alpha included).</summary>
 406650    public string HexRgba => $"#{R:X2}{G:X2}{B:X2}{AlphaByte:X2}";
 651
 652    /// <summary>Gets the <c>hsv(H, S%, V%)</c> representation using invariant culture.</summary>
 653    public string Hsv
 654    {
 655        get
 656        {
 4657            RgbToHsv(R, G, B, out var h, out var s, out var v);
 658
 4659            return string.Create(CultureInfo.InvariantCulture, $"hsv({h:0.##}, {s:0.##}%, {v:0.##}%)");
 660        }
 661    }
 662
 663    /// <summary>Gets the <c>hsva(H, S%, V%, A)</c> representation using invariant culture.</summary>
 664    public string Hsva
 665    {
 666        get
 667        {
 2668            RgbToHsv(R, G, B, out var h, out var s, out var v);
 669
 2670            return string.Create(CultureInfo.InvariantCulture, $"hsva({h:0.##}, {s:0.##}%, {v:0.##}%, {A:0.###})");
 671        }
 672    }
 673
 674    /// <summary>Gets the red channel in the range [0..255].</summary>
 4712675    public byte R { get; }
 676
 677    /// <summary>Gets the <c>rgb(r, g, b)</c> representation.</summary>
 2678    public string Rgb => $"rgb({R}, {G}, {B})";
 679
 680    /// <summary>
 681    /// Gets the <c>rgba(r, g, b, a)</c> representation using invariant culture, where <c>a</c> is shown in [0..1].
 682    /// </summary>
 2683    public string Rgba => string.Create(CultureInfo.InvariantCulture, $"rgba({R}, {G}, {B}, {A:0.###})");
 684
 685    /// <summary>Gets the saturation in percent in the range [0..100].</summary>
 686    /// <remarks>The value is computed from the underlying RGB channels.</remarks>
 687    public double S
 688    {
 689        get
 690        {
 132691            RgbToHsv(R, G, B, out _, out var s, out _);
 692
 132693            return s;
 694        }
 695    }
 696
 697    /// <summary>Gets the value (brightness) in percent in the range [0..100].</summary>
 698    /// <remarks>The value is computed from the underlying RGB channels.</remarks>
 699    public double V
 700    {
 701        get
 702        {
 158703            RgbToHsv(R, G, B, out _, out _, out var v);
 704
 158705            return v;
 706        }
 707    }
 708
 709    /// <summary>Gets the style value represented as a string.</summary>
 344710    public override string Value => HexRgba;
 711
 712    /// <summary>Creates an <see cref="AllyariaColorValue" /> from a hex literal (helper used by color tables).</summary
 713    /// <param name="hex">A hex color string of the form <c>#RRGGBB</c> or <c>#RRGGBBAA</c>.</param>
 714    /// <returns>A new <see cref="AllyariaColorValue" /> parsed from <paramref name="hex" />.</returns>
 715    private static AllyariaColorValue FromHexInline(string hex)
 716    {
 794717        FromHexString(hex, out var r, out var g, out var b, out var a);
 718
 794719        return new AllyariaColorValue(r, g, b, a);
 720    }
 721
 722    /// <summary>Parses a hexadecimal CSS color literal.</summary>
 723    /// <param name="s">A hex color string of the form <c>#RGB</c>, <c>#RGBA</c>, <c>#RRGGBB</c>, or <c>#RRGGBBAA</c>.</
 724    /// <param name="r">Outputs the red channel (0–255).</param>
 725    /// <param name="g">Outputs the green channel (0–255).</param>
 726    /// <param name="b">Outputs the blue channel (0–255).</param>
 727    /// <param name="a">Outputs the alpha channel (0–1).</param>
 728    /// <exception cref="ArgumentException">
 729    /// Thrown when <paramref name="s" /> is not a supported hex format or does not begin
 730    /// with <c>#</c>.
 731    /// </exception>
 732    private static void FromHexString(string s, out byte r, out byte g, out byte b, out double a)
 733    {
 1238734        var hex = s.Trim();
 1238735        hex = hex[1..];
 736
 1238737        if (hex.Length is 3 or 4)
 738        {
 739            // #RGB or #RGBA -> expand nibbles
 14740            var r1 = ToHexNibble(hex[0]);
 14741            var g1 = ToHexNibble(hex[1]);
 14742            var b1 = ToHexNibble(hex[2]);
 12743            r = (byte)(r1 * 17);
 12744            g = (byte)(g1 * 17);
 12745            b = (byte)(b1 * 17);
 746
 12747            a = hex.Length == 4
 12748                ? Math.Clamp(ToHexNibble(hex[3]) * 17 / 255.0, 0.0, 1.0)
 12749                : 1.0;
 750
 12751            return;
 752        }
 753
 1224754        if (hex.Length is 6 or 8)
 755        {
 1222756            r = Convert.ToByte(hex.Substring(0, 2), 16);
 1222757            g = Convert.ToByte(hex.Substring(2, 2), 16);
 1222758            b = Convert.ToByte(hex.Substring(4, 2), 16);
 759
 1222760            a = hex.Length == 8
 1222761                ? Convert.ToByte(hex.Substring(6, 2), 16) / 255.0
 1222762                : 1.0;
 763
 1222764            return;
 765        }
 766
 2767        throw new ArgumentException($"Hex color must be #RGB, #RGBA, #RRGGBB, or #RRGGBBAA: '{s}'.", nameof(s));
 768    }
 769
 770    /// <summary>Returns a color from HSVA channels.</summary>
 771    /// <param name="h">Hue in degrees, clamped to [0..360].</param>
 772    /// <param name="s">Saturation in percent, clamped to [0..100].</param>
 773    /// <param name="v">Value (brightness) in percent, clamped to [0..100].</param>
 774    /// <param name="a">Alpha in [0..1], clamped.</param>
 775    /// <returns>The <see cref="AllyariaColorValue" /> from the HSVA channels.</returns>
 776    public static AllyariaColorValue FromHsva(double h, double s, double v, double a = 1.0)
 777    {
 1326778        var hh = h % 360.0;
 779
 1326780        if (hh < 0.0)
 781        {
 2782            hh += 360.0;
 783        }
 784
 1326785        var ss = Math.Clamp(s, 0.0, 100.0);
 1326786        var vv = Math.Clamp(v, 0.0, 100.0);
 1326787        var aa = Math.Clamp(a, 0.0, 1.0);
 788
 1326789        return new AllyariaColorValue(hh, ss, vv, aa);
 790    }
 791
 792    /// <summary>Parses an <c>hsv(H,S%,V%)</c> or <c>hsva(H,S%,V%,A)</c> CSS color function.</summary>
 793    /// <param name="s">The input string to parse.</param>
 794    /// <param name="r">Outputs the red channel (0–255).</param>
 795    /// <param name="g">Outputs the green channel (0–255).</param>
 796    /// <param name="b">Outputs the blue channel (0–255).</param>
 797    /// <param name="a">Outputs the alpha channel (0–1). Defaults to 1.0 when omitted.</param>
 798    /// <exception cref="ArgumentException">
 799    /// Thrown when the string is not in <c>hsv()</c>/<c>hsva()</c> form or contains
 800    /// out-of-range values.
 801    /// </exception>
 802    private static void FromHsvString(string s, out byte r, out byte g, out byte b, out double a)
 803    {
 12804        var m = RxHsv.Match(s);
 805
 12806        if (!m.Success)
 807        {
 4808            throw new ArgumentException(
 4809                $"Invalid hsv/hsva() format: '{s}'. Expected hsv(H,S%,V%) or hsva(H,S%,V%,A).", nameof(s)
 4810            );
 811        }
 812
 8813        var h = Math.Clamp(ParseDouble(m.Groups["h"].Value, "H", 0, 360), 0, 360);
 8814        var sp = Math.Clamp(ParseDouble(m.Groups["s"].Value, "S%", 0, 100), 0, 100);
 6815        var vp = Math.Clamp(ParseDouble(m.Groups["v"].Value, "V%", 0, 100), 0, 100);
 816
 6817        a = m.Groups["a"].Success
 6818            ? Math.Clamp(ParseDouble(m.Groups["a"].Value, "A", 0, 1), 0.0, 1.0)
 6819            : 1.0;
 820
 4821        HsvToRgb(h, sp, vp, out r, out g, out b);
 4822    }
 823
 824    /// <summary>Returns a color from RGBA channels.</summary>
 825    /// <param name="r">Red in [0..255].</param>
 826    /// <param name="g">Green in [0..255].</param>
 827    /// <param name="b">Blue in [0..255].</param>
 828    /// <param name="a">Alpha in [0..1], clamped.</param>
 829    /// <returns>The <see cref="AllyariaColorValue" /> from the RGBA channels.</returns>
 264830    public static AllyariaColorValue FromRgba(byte r, byte g, byte b, double a = 1.0) => new(r, g, b, a);
 831
 832    /// <summary>Parses an <c>rgb(r,g,b)</c> or <c>rgba(r,g,b,a)</c> CSS color function.</summary>
 833    /// <param name="s">The input string to parse.</param>
 834    /// <param name="r">Outputs the red channel (0–255).</param>
 835    /// <param name="g">Outputs the green channel (0–255).</param>
 836    /// <param name="b">Outputs the blue channel (0–255).</param>
 837    /// <param name="a">Outputs the alpha channel (0–1). Defaults to 1.0 when omitted.</param>
 838    /// <exception cref="ArgumentException">
 839    /// Thrown when the string is not in <c>rgb()</c>/<c>rgba()</c> form or contains
 840    /// out-of-range values.
 841    /// </exception>
 842    private static void FromRgbString(string s, out byte r, out byte g, out byte b, out double a)
 843    {
 12844        var m = RxRgb.Match(s);
 845
 12846        if (!m.Success)
 847        {
 4848            throw new ArgumentException(
 4849                $"Invalid rgb/rgba() format: '{s}'. Expected rgb(r,g,b) or rgba(r,g,b,a).", nameof(s)
 4850            );
 851        }
 852
 8853        r = (byte)Math.Clamp(ParseInt(m.Groups["r"].Value, "r", 0, 255), 0, 255);
 6854        g = (byte)Math.Clamp(ParseInt(m.Groups["g"].Value, "g", 0, 255), 0, 255);
 6855        b = (byte)Math.Clamp(ParseInt(m.Groups["b"].Value, "b", 0, 255), 0, 255);
 856
 6857        a = m.Groups["a"].Success
 6858            ? Math.Clamp(ParseDouble(m.Groups["a"].Value, "a", 0, 1), 0.0, 1.0)
 6859            : 1.0;
 6860    }
 861
 862    /// <summary>Converts HSV to RGB bytes.</summary>
 863    /// <param name="h">Hue in degrees (<c>0..360</c>).</param>
 864    /// <param name="s">Saturation in percent (<c>0..100</c>).</param>
 865    /// <param name="v">Value (brightness) in percent (<c>0..100</c>).</param>
 866    /// <param name="r">Outputs the red channel (0–255).</param>
 867    /// <param name="g">Outputs the green channel (0–255).</param>
 868    /// <param name="b">Outputs the blue channel (0–255).</param>
 869    /// <remarks>
 870    /// This method normalizes <paramref name="h" /> to <c>[0,360)</c>, converts <paramref name="s" /> and
 871    /// <paramref name="v" /> from percent to unit, performs the sector-based conversion, and rounds the results to byte
 872    /// </remarks>
 873    private static void HsvToRgb(double h,
 874        double s,
 875        double v,
 876        out byte r,
 877        out byte g,
 878        out byte b)
 879    {
 1330880        s = Math.Clamp(s / 100.0, 0.0, 1.0);
 1330881        v = Math.Clamp(v / 100.0, 0.0, 1.0);
 882
 1330883        if (s <= 0.0)
 884        {
 426885            var c = (byte)Math.Round(v * 255.0);
 426886            r = g = b = c;
 887
 426888            return;
 889        }
 890
 904891        h = (h % 360 + 360) % 360;
 904892        var hh = h / 60.0;
 904893        var i = (int)Math.Floor(hh);
 904894        var ff = hh - i;
 895
 904896        var p = v * (1.0 - s);
 904897        var q = v * (1.0 - s * ff);
 904898        var t = v * (1.0 - s * (1.0 - ff));
 899
 900        double r1, g1, b1;
 901
 902        switch (i)
 903        {
 904            case 0:
 424905                r1 = v;
 424906                g1 = t;
 424907                b1 = p;
 908
 424909                break;
 910            case 1:
 84911                r1 = q;
 84912                g1 = v;
 84913                b1 = p;
 914
 84915                break;
 916            case 2:
 62917                r1 = p;
 62918                g1 = v;
 62919                b1 = t;
 920
 62921                break;
 922            case 3:
 14923                r1 = p;
 14924                g1 = q;
 14925                b1 = v;
 926
 14927                break;
 928            case 4:
 318929                r1 = t;
 318930                g1 = p;
 318931                b1 = v;
 932
 318933                break;
 934            default:
 2935                r1 = v;
 2936                g1 = p;
 2937                b1 = q;
 938
 939                break;
 940        }
 941
 904942        r = (byte)Math.Clamp((int)Math.Round(r1 * 255.0), 0, 255);
 904943        g = (byte)Math.Clamp((int)Math.Round(g1 * 255.0), 0, 255);
 904944        b = (byte)Math.Clamp((int)Math.Round(b1 * 255.0), 0, 255);
 904945    }
 946
 947    /// <summary>Normalizes a Material color key for lookup.</summary>
 948    /// <param name="input">An input such as <c>"Deep Purple 200"</c>, <c>"deep-purple-200"</c>, or <c>"red500"</c>.</pa
 949    /// <returns>A normalized, lower-case key without spaces, dashes, or underscores (e.g., <c>"deeppurple200"</c>).</re
 950    private static string NormalizeMaterialKey(string input)
 12951        => input.Trim().ToLowerInvariant()
 12952            .Replace(" ", string.Empty)
 12953            .Replace("-", string.Empty)
 12954            .Replace("_", string.Empty);
 955
 956    /// <summary>Parses a color string into an <see cref="AllyariaColorValue" />.</summary>
 957    /// <param name="value">The color value to parse.</param>
 958    /// <returns>The parsed <see cref="AllyariaColorValue" />.</returns>
 959    /// <exception cref="ArgumentException">Thrown when parsing fails.</exception>
 2960    public static AllyariaColorValue Parse(string value) => new(value);
 961
 962    /// <summary>Parses a floating-point number using invariant culture and validates the range.</summary>
 963    /// <param name="value">The source text.</param>
 964    /// <param name="param">A parameter name used in exception messages.</param>
 965    /// <param name="min">The minimum allowed value (inclusive).</param>
 966    /// <param name="max">The maximum allowed value (inclusive).</param>
 967    /// <returns>The parsed number.</returns>
 968    /// <exception cref="ArgumentException">Thrown when parsing fails.</exception>
 969    /// <exception cref="ArgumentOutOfRangeException">Thrown when the parsed value is out of range.</exception>
 970    private static double ParseDouble(string value, string param, int min, int max)
 971    {
 30972        double.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out var v);
 973
 30974        if (v < min || v > max)
 975        {
 4976            throw new ArgumentOutOfRangeException(param, v, $"Expected {param} in [{min}..{max}].");
 977        }
 978
 26979        return v;
 980    }
 981
 982    /// <summary>Parses an integer and validates it against the provided inclusive range.</summary>
 983    /// <param name="value">The source text.</param>
 984    /// <param name="param">A parameter name used in exception messages.</param>
 985    /// <param name="min">The minimum allowed value (inclusive).</param>
 986    /// <param name="max">The maximum allowed value (inclusive).</param>
 987    /// <returns>The parsed integer.</returns>
 988    /// <exception cref="ArgumentException">Thrown when parsing fails.</exception>
 989    /// <exception cref="ArgumentOutOfRangeException">Thrown when the parsed value is out of range.</exception>
 990    private static int ParseInt(string value, string param, int min, int max)
 991    {
 20992        int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var v);
 993
 20994        if (v < min || v > max)
 995        {
 2996            throw new ArgumentOutOfRangeException(param, v, $"Expected {param} in [{min}..{max}].");
 997        }
 998
 18999        return v;
 1000    }
 1001
 1002    /// <summary>Converts RGB bytes to HSV.</summary>
 1003    /// <param name="r">Red (0–255).</param>
 1004    /// <param name="g">Green (0–255).</param>
 1005    /// <param name="b">Blue (0–255).</param>
 1006    /// <param name="h">Outputs hue in degrees (<c>0..360</c>).</param>
 1007    /// <param name="s">Outputs saturation in percent (<c>0..100</c>).</param>
 1008    /// <param name="v">Outputs value (brightness) in percent (<c>0..100</c>).</param>
 1009    /// <remarks>
 1010    /// When <paramref name="r" />, <paramref name="g" />, and <paramref name="b" /> are equal, hue is defined as <c>0</
 1011    /// saturation as <c>0</c>.
 1012    /// </remarks>
 1013    private static void RgbToHsv(byte r,
 1014        byte g,
 1015        byte b,
 1016        out double h,
 1017        out double s,
 1018        out double v)
 1019    {
 4301020        var rf = r / 255.0;
 4301021        var gf = g / 255.0;
 4301022        var bf = b / 255.0;
 1023
 4301024        var max = Math.Max(rf, Math.Max(gf, bf));
 4301025        var min = Math.Min(rf, Math.Min(gf, bf));
 4301026        var delta = max - min;
 1027
 1028        // Hue
 4301029        if (delta < 1e-9)
 1030        {
 1941031            h = 0.0;
 1032        }
 2361033        else if (Math.Abs(max - rf) < 1e-9)
 1034        {
 921035            h = 60.0 * ((gf - bf) / delta % 6.0);
 1036        }
 1441037        else if (Math.Abs(max - gf) < 1e-9)
 1038        {
 341039            h = 60.0 * ((bf - rf) / delta + 2.0);
 1040        }
 1041        else
 1042        {
 1101043            h = 60.0 * ((rf - gf) / delta + 4.0);
 1044        }
 1045
 4301046        if (h < 0)
 1047        {
 21048            h += 360.0;
 1049        }
 1050
 1051        // Saturation
 4301052        s = max <= 0
 4301053            ? 0
 4301054            : delta / max * 100.0;
 1055
 1056        // Value
 4301057        v = max * 100.0;
 4301058    }
 1059
 1060    /// <summary>Converts a single hexadecimal digit to its numeric nibble value.</summary>
 1061    /// <param name="c">A character in the set <c>0–9</c>, <c>a–f</c>, or <c>A–F</c>.</param>
 1062    /// <returns>The integer value <c>0..15</c> represented by <paramref name="c" />.</returns>
 1063    /// <exception cref="ArgumentException">Thrown when <paramref name="c" /> is not a valid hex digit.</exception>
 1064    private static int ToHexNibble(char c)
 481065        => c switch
 481066        {
 121067            >= '0' and <= '9' => c - '0',
 241068            >= 'a' and <= 'f' => c - 'a' + 10,
 101069            >= 'A' and <= 'F' => c - 'A' + 10,
 21070            _ => throw new ArgumentException($"Invalid hex digit '{c}'.")
 481071        };
 1072
 1073    /// <summary>Attempts to parse a Material color name of the form <c>{Hue}{Tone}</c>.</summary>
 1074    /// <param name="name">
 1075    /// Examples include <c>"DeepPurple200"</c>, <c>"red500"</c>, or <c>"deep-purple a700"</c> (whitespace, dashes, and
 1076    /// underscores are ignored).
 1077    /// </param>
 1078    /// <param name="color">
 1079    /// When this method returns, contains the parsed colorValue if successful; otherwise the default value.
 1080    /// </param>
 1081    /// <returns><c>true</c> if parsing succeeded; otherwise <c>false</c>.</returns>
 1082    private static bool TryFromMaterialName(string name, out AllyariaColorValue color)
 1083    {
 121084        var norm = NormalizeMaterialKey(name);
 1085
 121086        if (MaterialMap.TryGetValue(norm, out var rgba))
 1087        {
 61088            color = rgba;
 1089
 61090            return true;
 1091        }
 1092
 61093        color = Colors.Transparent;
 1094
 61095        return false;
 1096    }
 1097
 1098    /// <summary>Attempts to parse a CSS Web color name (case-insensitive).</summary>
 1099    /// <param name="name">The color name (e.g., <c>"dodgerblue"</c>, <c>"white"</c>).</param>
 1100    /// <param name="color">When this method returns, contains the parsed color if successful; otherwise the default val
 1101    /// <returns><c>true</c> if parsing succeeded; otherwise <c>false</c>.</returns>
 1102    private static bool TryFromWebName(string name, out AllyariaColorValue color)
 1103    {
 201104        var key = name.Trim().ToLowerInvariant();
 1105
 201106        if (WebNameMap.TryGetValue(key, out var rgba))
 1107        {
 81108            color = rgba;
 1109
 81110            return true;
 1111        }
 1112
 121113        color = Colors.Transparent;
 1114
 121115        return false;
 1116    }
 1117
 1118    /// <summary>Attempts to parse a color string into an <see cref="AllyariaColorValue" />.</summary>
 1119    /// <param name="value">The color value to parse.</param>
 1120    /// <param name="result">When this method returns, contains the parsed color if successful; otherwise <c>null</c>.</
 1121    /// <returns><c>true</c> if parsing succeeded; otherwise <c>false</c>.</returns>
 1122    public static bool TryParse(string value, out AllyariaColorValue? result)
 1123    {
 1124        try
 1125        {
 141126            result = new AllyariaColorValue(value);
 1127
 121128            return true;
 1129        }
 21130        catch
 1131        {
 21132            result = null;
 1133
 21134            return false;
 1135        }
 141136    }
 1137
 1138    /// <summary>Explicit cast from <see cref="string" /> to <see cref="AllyariaColorValue" />.</summary>
 1139    /// <param name="value">The color value to parse.</param>
 1140    /// <returns>A new <see cref="AllyariaColorValue" />.</returns>
 41141    public static implicit operator AllyariaColorValue(string value) => new(value);
 1142
 1143    /// <summary>
 1144    /// Implicit cast from <see cref="AllyariaColorValue" /> to its canonical string representation (<c>#RRGGBBAA</c>).
 1145    /// </summary>
 1146    /// <param name="value">The color value.</param>
 1147    /// <returns>The uppercase <c>#RRGGBBAA</c> string.</returns>
 21148    public static implicit operator string(AllyariaColorValue value) => value.Value;
 1149}