| | 1 | | using Allyaria.Theming.Constants; |
| | 2 | | using Allyaria.Theming.Contracts; |
| | 3 | | using System.Globalization; |
| | 4 | | using System.Text.RegularExpressions; |
| | 5 | |
|
| | 6 | | namespace 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> |
| | 33 | | public sealed class AllyariaColorValue : ValueBase |
| | 34 | | { |
| | 35 | | /// <summary>Material Design color lookup table.</summary> |
| 2 | 36 | | private static readonly Dictionary<string, AllyariaColorValue> MaterialMap = new(StringComparer.OrdinalIgnoreCase) |
| 2 | 37 | | { |
| 2 | 38 | | // Red |
| 2 | 39 | | ["red50"] = FromHexInline("#FFEBEEFF"), |
| 2 | 40 | | ["red100"] = FromHexInline("#FFCDD2FF"), |
| 2 | 41 | | ["red200"] = FromHexInline("#EF9A9AFF"), |
| 2 | 42 | | ["red300"] = FromHexInline("#E57373FF"), |
| 2 | 43 | | ["red400"] = FromHexInline("#EF5350FF"), |
| 2 | 44 | | ["red500"] = FromHexInline("#F44336FF"), |
| 2 | 45 | | ["red600"] = FromHexInline("#E53935FF"), |
| 2 | 46 | | ["red700"] = FromHexInline("#D32F2FFF"), |
| 2 | 47 | | ["red800"] = FromHexInline("#C62828FF"), |
| 2 | 48 | | ["red900"] = FromHexInline("#B71C1CFF"), |
| 2 | 49 | | ["reda100"] = FromHexInline("#FF8A80FF"), |
| 2 | 50 | | ["reda200"] = FromHexInline("#FF5252FF"), |
| 2 | 51 | | ["reda400"] = FromHexInline("#FF1744FF"), |
| 2 | 52 | | ["reda700"] = FromHexInline("#D50000FF"), |
| 2 | 53 | |
|
| 2 | 54 | | // Pink |
| 2 | 55 | | ["pink50"] = FromHexInline("#FCE4ECFF"), |
| 2 | 56 | | ["pink100"] = FromHexInline("#F8BBD0FF"), |
| 2 | 57 | | ["pink200"] = FromHexInline("#F48FB1FF"), |
| 2 | 58 | | ["pink300"] = FromHexInline("#F06292FF"), |
| 2 | 59 | | ["pink400"] = FromHexInline("#EC407AFF"), |
| 2 | 60 | | ["pink500"] = FromHexInline("#E91E63FF"), |
| 2 | 61 | | ["pink600"] = FromHexInline("#D81B60FF"), |
| 2 | 62 | | ["pink700"] = FromHexInline("#C2185BFF"), |
| 2 | 63 | | ["pink800"] = FromHexInline("#AD1457FF"), |
| 2 | 64 | | ["pink900"] = FromHexInline("#880E4FFF"), |
| 2 | 65 | | ["pinka100"] = FromHexInline("#FF80ABFF"), |
| 2 | 66 | | ["pinka200"] = FromHexInline("#FF4081FF"), |
| 2 | 67 | | ["pinka400"] = FromHexInline("#F50057FF"), |
| 2 | 68 | | ["pinka700"] = FromHexInline("#C51162FF"), |
| 2 | 69 | |
|
| 2 | 70 | | // Purple |
| 2 | 71 | | ["purple50"] = FromHexInline("#F3E5F5FF"), |
| 2 | 72 | | ["purple100"] = FromHexInline("#E1BEE7FF"), |
| 2 | 73 | | ["purple200"] = FromHexInline("#CE93D8FF"), |
| 2 | 74 | | ["purple300"] = FromHexInline("#BA68C8FF"), |
| 2 | 75 | | ["purple400"] = FromHexInline("#AB47BCFF"), |
| 2 | 76 | | ["purple500"] = FromHexInline("#9C27B0FF"), |
| 2 | 77 | | ["purple600"] = FromHexInline("#8E24AAFF"), |
| 2 | 78 | | ["purple700"] = FromHexInline("#7B1FA2FF"), |
| 2 | 79 | | ["purple800"] = FromHexInline("#6A1B9AFF"), |
| 2 | 80 | | ["purple900"] = FromHexInline("#4A148CFF"), |
| 2 | 81 | | ["purplea100"] = FromHexInline("#EA80FCFF"), |
| 2 | 82 | | ["purplea200"] = FromHexInline("#E040FBFF"), |
| 2 | 83 | | ["purplea400"] = FromHexInline("#D500F9FF"), |
| 2 | 84 | | ["purplea700"] = FromHexInline("#AA00FFFF"), |
| 2 | 85 | |
|
| 2 | 86 | | // Deep Purple |
| 2 | 87 | | ["deeppurple50"] = FromHexInline("#EDE7F6FF"), |
| 2 | 88 | | ["deeppurple100"] = FromHexInline("#D1C4E9FF"), |
| 2 | 89 | | ["deeppurple200"] = FromHexInline("#B39DDBFF"), |
| 2 | 90 | | ["deeppurple300"] = FromHexInline("#9575CDFF"), |
| 2 | 91 | | ["deeppurple400"] = FromHexInline("#7E57C2FF"), |
| 2 | 92 | | ["deeppurple500"] = FromHexInline("#673AB7FF"), |
| 2 | 93 | | ["deeppurple600"] = FromHexInline("#5E35B1FF"), |
| 2 | 94 | | ["deeppurple700"] = FromHexInline("#512DA8FF"), |
| 2 | 95 | | ["deeppurple800"] = FromHexInline("#4527A0FF"), |
| 2 | 96 | | ["deeppurple900"] = FromHexInline("#311B92FF"), |
| 2 | 97 | | ["deeppurplea100"] = FromHexInline("#B388FFFF"), |
| 2 | 98 | | ["deeppurplea200"] = FromHexInline("#7C4DFFFF"), |
| 2 | 99 | | ["deeppurplea400"] = FromHexInline("#651FFFFF"), |
| 2 | 100 | | ["deeppurplea700"] = FromHexInline("#6200EAFF"), |
| 2 | 101 | |
|
| 2 | 102 | | // Indigo |
| 2 | 103 | | ["indigo50"] = FromHexInline("#E8EAF6FF"), |
| 2 | 104 | | ["indigo100"] = FromHexInline("#C5CAE9FF"), |
| 2 | 105 | | ["indigo200"] = FromHexInline("#9FA8DAFF"), |
| 2 | 106 | | ["indigo300"] = FromHexInline("#7986CBFF"), |
| 2 | 107 | | ["indigo400"] = FromHexInline("#5C6BC0FF"), |
| 2 | 108 | | ["indigo500"] = FromHexInline("#3F51B5FF"), |
| 2 | 109 | | ["indigo600"] = FromHexInline("#3949ABFF"), |
| 2 | 110 | | ["indigo700"] = FromHexInline("#303F9FFF"), |
| 2 | 111 | | ["indigo800"] = FromHexInline("#283593FF"), |
| 2 | 112 | | ["indigo900"] = FromHexInline("#1A237EFF"), |
| 2 | 113 | | ["indigoa100"] = FromHexInline("#8C9EFFFF"), |
| 2 | 114 | | ["indigoa200"] = FromHexInline("#536DFEFF"), |
| 2 | 115 | | ["indigoa400"] = FromHexInline("#3D5AFEFF"), |
| 2 | 116 | | ["indigoa700"] = FromHexInline("#304FFEFF"), |
| 2 | 117 | |
|
| 2 | 118 | | // Blue |
| 2 | 119 | | ["blue50"] = FromHexInline("#E3F2FDFF"), |
| 2 | 120 | | ["blue100"] = FromHexInline("#BBDEFBFF"), |
| 2 | 121 | | ["blue200"] = FromHexInline("#90CAF9FF"), |
| 2 | 122 | | ["blue300"] = FromHexInline("#64B5F6FF"), |
| 2 | 123 | | ["blue400"] = FromHexInline("#42A5F5FF"), |
| 2 | 124 | | ["blue500"] = FromHexInline("#2196F3FF"), |
| 2 | 125 | | ["blue600"] = FromHexInline("#1E88E5FF"), |
| 2 | 126 | | ["blue700"] = FromHexInline("#1976D2FF"), |
| 2 | 127 | | ["blue800"] = FromHexInline("#1565C0FF"), |
| 2 | 128 | | ["blue900"] = FromHexInline("#0D47A1FF"), |
| 2 | 129 | | ["bluea100"] = FromHexInline("#82B1FFFF"), |
| 2 | 130 | | ["bluea200"] = FromHexInline("#448AFFFF"), |
| 2 | 131 | | ["bluea400"] = FromHexInline("#2979FFFF"), |
| 2 | 132 | | ["bluea700"] = FromHexInline("#2962FFFF"), |
| 2 | 133 | |
|
| 2 | 134 | | // Light Blue |
| 2 | 135 | | ["lightblue50"] = FromHexInline("#E1F5FEFF"), |
| 2 | 136 | | ["lightblue100"] = FromHexInline("#B3E5FCFF"), |
| 2 | 137 | | ["lightblue200"] = FromHexInline("#81D4FAFF"), |
| 2 | 138 | | ["lightblue300"] = FromHexInline("#4FC3F7FF"), |
| 2 | 139 | | ["lightblue400"] = FromHexInline("#29B6F6FF"), |
| 2 | 140 | | ["lightblue500"] = FromHexInline("#03A9F4FF"), |
| 2 | 141 | | ["lightblue600"] = FromHexInline("#039BE5FF"), |
| 2 | 142 | | ["lightblue700"] = FromHexInline("#0288D1FF"), |
| 2 | 143 | | ["lightblue800"] = FromHexInline("#0277BDFF"), |
| 2 | 144 | | ["lightblue900"] = FromHexInline("#01579BFF"), |
| 2 | 145 | | ["lightbluea100"] = FromHexInline("#80D8FFFF"), |
| 2 | 146 | | ["lightbluea200"] = FromHexInline("#40C4FFFF"), |
| 2 | 147 | | ["lightbluea400"] = FromHexInline("#00B0FFFF"), |
| 2 | 148 | | ["lightbluea700"] = FromHexInline("#0091EAFF"), |
| 2 | 149 | |
|
| 2 | 150 | | // Cyan |
| 2 | 151 | | ["cyan50"] = FromHexInline("#E0F7FAFF"), |
| 2 | 152 | | ["cyan100"] = FromHexInline("#B2EBF2FF"), |
| 2 | 153 | | ["cyan200"] = FromHexInline("#80DEEAFF"), |
| 2 | 154 | | ["cyan300"] = FromHexInline("#4DD0E1FF"), |
| 2 | 155 | | ["cyan400"] = FromHexInline("#26C6DAFF"), |
| 2 | 156 | | ["cyan500"] = FromHexInline("#00BCD4FF"), |
| 2 | 157 | | ["cyan600"] = FromHexInline("#00ACC1FF"), |
| 2 | 158 | | ["cyan700"] = FromHexInline("#0097A7FF"), |
| 2 | 159 | | ["cyan800"] = FromHexInline("#00838FFF"), |
| 2 | 160 | | ["cyan900"] = FromHexInline("#006064FF"), |
| 2 | 161 | | ["cyana100"] = FromHexInline("#84FFFFFF"), |
| 2 | 162 | | ["cyana200"] = FromHexInline("#18FFFFFF"), |
| 2 | 163 | | ["cyana400"] = FromHexInline("#00E5FFFF"), |
| 2 | 164 | | ["cyana700"] = FromHexInline("#00B8D4FF"), |
| 2 | 165 | |
|
| 2 | 166 | | // Teal |
| 2 | 167 | | ["teal50"] = FromHexInline("#E0F2F1FF"), |
| 2 | 168 | | ["teal100"] = FromHexInline("#B2DFDBFF"), |
| 2 | 169 | | ["teal200"] = FromHexInline("#80CBC4FF"), |
| 2 | 170 | | ["teal300"] = FromHexInline("#4DB6ACFF"), |
| 2 | 171 | | ["teal400"] = FromHexInline("#26A69AFF"), |
| 2 | 172 | | ["teal500"] = FromHexInline("#009688FF"), |
| 2 | 173 | | ["teal600"] = FromHexInline("#00897BFF"), |
| 2 | 174 | | ["teal700"] = FromHexInline("#00796BFF"), |
| 2 | 175 | | ["teal800"] = FromHexInline("#00695CFF"), |
| 2 | 176 | | ["teal900"] = FromHexInline("#004D40FF"), |
| 2 | 177 | | ["teala100"] = FromHexInline("#A7FFEBFF"), |
| 2 | 178 | | ["teala200"] = FromHexInline("#64FFDAFF"), |
| 2 | 179 | | ["teala400"] = FromHexInline("#1DE9B6FF"), |
| 2 | 180 | | ["teala700"] = FromHexInline("#00BFA5FF"), |
| 2 | 181 | |
|
| 2 | 182 | | // Green |
| 2 | 183 | | ["green50"] = FromHexInline("#E8F5E9FF"), |
| 2 | 184 | | ["green100"] = FromHexInline("#C8E6C9FF"), |
| 2 | 185 | | ["green200"] = FromHexInline("#A5D6A7FF"), |
| 2 | 186 | | ["green300"] = FromHexInline("#81C784FF"), |
| 2 | 187 | | ["green400"] = FromHexInline("#66BB6AFF"), |
| 2 | 188 | | ["green500"] = FromHexInline("#4CAF50FF"), |
| 2 | 189 | | ["green600"] = FromHexInline("#43A047FF"), |
| 2 | 190 | | ["green700"] = FromHexInline("#388E3CFF"), |
| 2 | 191 | | ["green800"] = FromHexInline("#2E7D32FF"), |
| 2 | 192 | | ["green900"] = FromHexInline("#1B5E20FF"), |
| 2 | 193 | | ["greena100"] = FromHexInline("#B9F6CAFF"), |
| 2 | 194 | | ["greena200"] = FromHexInline("#69F0AEFF"), |
| 2 | 195 | | ["greena400"] = FromHexInline("#00E676FF"), |
| 2 | 196 | | ["greena700"] = FromHexInline("#00C853FF"), |
| 2 | 197 | |
|
| 2 | 198 | | // Light Green |
| 2 | 199 | | ["lightgreen50"] = FromHexInline("#F1F8E9FF"), |
| 2 | 200 | | ["lightgreen100"] = FromHexInline("#DCEDC8FF"), |
| 2 | 201 | | ["lightgreen200"] = FromHexInline("#C5E1A5FF"), |
| 2 | 202 | | ["lightgreen300"] = FromHexInline("#AED581FF"), |
| 2 | 203 | | ["lightgreen400"] = FromHexInline("#9CCC65FF"), |
| 2 | 204 | | ["lightgreen500"] = FromHexInline("#8BC34AFF"), |
| 2 | 205 | | ["lightgreen600"] = FromHexInline("#7CB342FF"), |
| 2 | 206 | | ["lightgreen700"] = FromHexInline("#689F38FF"), |
| 2 | 207 | | ["lightgreen800"] = FromHexInline("#558B2FFF"), |
| 2 | 208 | | ["lightgreen900"] = FromHexInline("#33691EFF"), |
| 2 | 209 | | ["lightgreena100"] = FromHexInline("#CCFF90FF"), |
| 2 | 210 | | ["lightgreena200"] = FromHexInline("#B2FF59FF"), |
| 2 | 211 | | ["lightgreena400"] = FromHexInline("#76FF03FF"), |
| 2 | 212 | | ["lightgreena700"] = FromHexInline("#64DD17FF"), |
| 2 | 213 | |
|
| 2 | 214 | | // Lime |
| 2 | 215 | | ["lime50"] = FromHexInline("#F9FBE7FF"), |
| 2 | 216 | | ["lime100"] = FromHexInline("#F0F4C3FF"), |
| 2 | 217 | | ["lime200"] = FromHexInline("#E6EE9CFF"), |
| 2 | 218 | | ["lime300"] = FromHexInline("#DCE775FF"), |
| 2 | 219 | | ["lime400"] = FromHexInline("#D4E157FF"), |
| 2 | 220 | | ["lime500"] = FromHexInline("#CDDC39FF"), |
| 2 | 221 | | ["lime600"] = FromHexInline("#C0CA33FF"), |
| 2 | 222 | | ["lime700"] = FromHexInline("#AFB42BFF"), |
| 2 | 223 | | ["lime800"] = FromHexInline("#9E9D24FF"), |
| 2 | 224 | | ["lime900"] = FromHexInline("#827717FF"), |
| 2 | 225 | | ["limea100"] = FromHexInline("#F4FF81FF"), |
| 2 | 226 | | ["limea200"] = FromHexInline("#EEFF41FF"), |
| 2 | 227 | | ["limea400"] = FromHexInline("#C6FF00FF"), |
| 2 | 228 | | ["limea700"] = FromHexInline("#AEEA00FF"), |
| 2 | 229 | |
|
| 2 | 230 | | // Yellow |
| 2 | 231 | | ["yellow50"] = FromHexInline("#FFFDE7FF"), |
| 2 | 232 | | ["yellow100"] = FromHexInline("#FFF9C4FF"), |
| 2 | 233 | | ["yellow200"] = FromHexInline("#FFF59DFF"), |
| 2 | 234 | | ["yellow300"] = FromHexInline("#FFF176FF"), |
| 2 | 235 | | ["yellow400"] = FromHexInline("#FFEE58FF"), |
| 2 | 236 | | ["yellow500"] = FromHexInline("#FFEB3BFF"), |
| 2 | 237 | | ["yellow600"] = FromHexInline("#FDD835FF"), |
| 2 | 238 | | ["yellow700"] = FromHexInline("#FBC02DFF"), |
| 2 | 239 | | ["yellow800"] = FromHexInline("#F9A825FF"), |
| 2 | 240 | | ["yellow900"] = FromHexInline("#F57F17FF"), |
| 2 | 241 | | ["yellowa100"] = FromHexInline("#FFFF8DFF"), |
| 2 | 242 | | ["yellowa200"] = FromHexInline("#FFFF00FF"), |
| 2 | 243 | | ["yellowa400"] = FromHexInline("#FFEA00FF"), |
| 2 | 244 | | ["yellowa700"] = FromHexInline("#FFD600FF"), |
| 2 | 245 | |
|
| 2 | 246 | | // Amber |
| 2 | 247 | | ["amber50"] = FromHexInline("#FFF8E1FF"), |
| 2 | 248 | | ["amber100"] = FromHexInline("#FFECB3FF"), |
| 2 | 249 | | ["amber200"] = FromHexInline("#FFE082FF"), |
| 2 | 250 | | ["amber300"] = FromHexInline("#FFD54FFF"), |
| 2 | 251 | | ["amber400"] = FromHexInline("#FFCA28FF"), |
| 2 | 252 | | ["amber500"] = FromHexInline("#FFC107FF"), |
| 2 | 253 | | ["amber600"] = FromHexInline("#FFB300FF"), |
| 2 | 254 | | ["amber700"] = FromHexInline("#FFA000FF"), |
| 2 | 255 | | ["amber800"] = FromHexInline("#FF8F00FF"), |
| 2 | 256 | | ["amber900"] = FromHexInline("#FF6F00FF"), |
| 2 | 257 | | ["ambera100"] = FromHexInline("#FFE57FFF"), |
| 2 | 258 | | ["ambera200"] = FromHexInline("#FFD740FF"), |
| 2 | 259 | | ["ambera400"] = FromHexInline("#FFC400FF"), |
| 2 | 260 | | ["ambera700"] = FromHexInline("#FFAB00FF"), |
| 2 | 261 | |
|
| 2 | 262 | | // Orange |
| 2 | 263 | | ["orange50"] = FromHexInline("#FFF3E0FF"), |
| 2 | 264 | | ["orange100"] = FromHexInline("#FFE0B2FF"), |
| 2 | 265 | | ["orange200"] = FromHexInline("#FFCC80FF"), |
| 2 | 266 | | ["orange300"] = FromHexInline("#FFB74DFF"), |
| 2 | 267 | | ["orange400"] = FromHexInline("#FFA726FF"), |
| 2 | 268 | | ["orange500"] = FromHexInline("#FF9800FF"), |
| 2 | 269 | | ["orange600"] = FromHexInline("#FB8C00FF"), |
| 2 | 270 | | ["orange700"] = FromHexInline("#F57C00FF"), |
| 2 | 271 | | ["orange800"] = FromHexInline("#EF6C00FF"), |
| 2 | 272 | | ["orange900"] = FromHexInline("#E65100FF"), |
| 2 | 273 | | ["orangea100"] = FromHexInline("#FFD180FF"), |
| 2 | 274 | | ["orangea200"] = FromHexInline("#FFAB40FF"), |
| 2 | 275 | | ["orangea400"] = FromHexInline("#FF9100FF"), |
| 2 | 276 | | ["orangea700"] = FromHexInline("#FF6D00FF"), |
| 2 | 277 | |
|
| 2 | 278 | | // Deep Orange |
| 2 | 279 | | ["deeporange50"] = FromHexInline("#FBE9E7FF"), |
| 2 | 280 | | ["deeporange100"] = FromHexInline("#FFCCBCFF"), |
| 2 | 281 | | ["deeporange200"] = FromHexInline("#FFAB91FF"), |
| 2 | 282 | | ["deeporange300"] = FromHexInline("#FF8A65FF"), |
| 2 | 283 | | ["deeporange400"] = FromHexInline("#FF7043FF"), |
| 2 | 284 | | ["deeporange500"] = FromHexInline("#FF5722FF"), |
| 2 | 285 | | ["deeporange600"] = FromHexInline("#F4511EFF"), |
| 2 | 286 | | ["deeporange700"] = FromHexInline("#E64A19FF"), |
| 2 | 287 | | ["deeporange800"] = FromHexInline("#D84315FF"), |
| 2 | 288 | | ["deeporange900"] = FromHexInline("#BF360CFF"), |
| 2 | 289 | | ["deeporangea100"] = FromHexInline("#FF9E80FF"), |
| 2 | 290 | | ["deeporangea200"] = FromHexInline("#FF6E40FF"), |
| 2 | 291 | | ["deeporangea400"] = FromHexInline("#FF3D00FF"), |
| 2 | 292 | | ["deeporangea700"] = FromHexInline("#DD2C00FF"), |
| 2 | 293 | |
|
| 2 | 294 | | // Brown (no accents in Material 2 palette) |
| 2 | 295 | | ["brown50"] = FromHexInline("#EFEBE9FF"), |
| 2 | 296 | | ["brown100"] = FromHexInline("#D7CCC8FF"), |
| 2 | 297 | | ["brown200"] = FromHexInline("#BCAAA4FF"), |
| 2 | 298 | | ["brown300"] = FromHexInline("#A1887FFF"), |
| 2 | 299 | | ["brown400"] = FromHexInline("#8D6E63FF"), |
| 2 | 300 | | ["brown500"] = FromHexInline("#795548FF"), |
| 2 | 301 | | ["brown600"] = FromHexInline("#6D4C41FF"), |
| 2 | 302 | | ["brown700"] = FromHexInline("#5D4037FF"), |
| 2 | 303 | | ["brown800"] = FromHexInline("#4E342EFF"), |
| 2 | 304 | | ["brown900"] = FromHexInline("#3E2723FF"), |
| 2 | 305 | |
|
| 2 | 306 | | // Blue Grey (no accents) |
| 2 | 307 | | ["bluegrey50"] = FromHexInline("#ECEFF1FF"), |
| 2 | 308 | | ["bluegrey100"] = FromHexInline("#CFD8DCFF"), |
| 2 | 309 | | ["bluegrey200"] = FromHexInline("#B0BEC5FF"), |
| 2 | 310 | | ["bluegrey300"] = FromHexInline("#90A4AEFF"), |
| 2 | 311 | | ["bluegrey400"] = FromHexInline("#78909CFF"), |
| 2 | 312 | | ["bluegrey500"] = FromHexInline("#607D8BFF"), |
| 2 | 313 | | ["bluegrey600"] = FromHexInline("#546E7AFF"), |
| 2 | 314 | | ["bluegrey700"] = FromHexInline("#455A64FF"), |
| 2 | 315 | | ["bluegrey800"] = FromHexInline("#37474FFF"), |
| 2 | 316 | | ["bluegrey900"] = FromHexInline("#263238FF"), |
| 2 | 317 | |
|
| 2 | 318 | | // Grey (no accents) |
| 2 | 319 | | ["grey50"] = FromHexInline("#FAFAFAFF"), |
| 2 | 320 | | ["grey100"] = FromHexInline("#F5F5F5FF"), |
| 2 | 321 | | ["grey200"] = FromHexInline("#EEEEEEFF"), |
| 2 | 322 | | ["grey300"] = FromHexInline("#E0E0E0FF"), |
| 2 | 323 | | ["grey400"] = FromHexInline("#BDBDBDFF"), |
| 2 | 324 | | ["grey500"] = FromHexInline("#9E9E9EFF"), |
| 2 | 325 | | ["grey600"] = FromHexInline("#757575FF"), |
| 2 | 326 | | ["grey700"] = FromHexInline("#616161FF"), |
| 2 | 327 | | ["grey800"] = FromHexInline("#424242FF"), |
| 2 | 328 | | ["grey900"] = FromHexInline("#212121FF"), |
| 2 | 329 | |
|
| 2 | 330 | | // Monochrome convenience entries |
| 2 | 331 | | ["black"] = FromHexInline("#000000FF"), |
| 2 | 332 | | ["white"] = FromHexInline("#FFFFFFFF") |
| 2 | 333 | | }; |
| | 334 | |
|
| | 335 | | /// <summary> |
| | 336 | | /// Compiled regular expression for parsing CSS <c>hsv()</c> and <c>hsva()</c> functions with a safe timeout. |
| | 337 | | /// </summary> |
| 2 | 338 | | private static readonly Regex RxHsv = new( |
| 2 | 339 | | @"^hsva?\s*\(\s*(?<h>(\d*\.)?\d+)\s*,\s*(?<s>(\d*\.)?\d+)\s*%\s*,\s*(?<v>(\d*\.)?\d+)\s*%(?:\s*,\s*(?<a>((\d*\.) |
| 2 | 340 | | RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase, |
| 2 | 341 | | TimeSpan.FromMilliseconds(250) |
| 2 | 342 | | ); |
| | 343 | |
|
| | 344 | | /// <summary> |
| | 345 | | /// Compiled regular expression for parsing CSS <c>rgb()</c> and <c>rgba()</c> functions with a safe timeout. |
| | 346 | | /// </summary> |
| 2 | 347 | | private static readonly Regex RxRgb = new( |
| 2 | 348 | | @"^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*$", |
| 2 | 349 | | RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase, |
| 2 | 350 | | TimeSpan.FromMilliseconds(250) |
| 2 | 351 | | ); |
| | 352 | |
|
| | 353 | | /// <summary>CSS Web color lookup table.</summary> |
| 2 | 354 | | private static readonly Dictionary<string, AllyariaColorValue> WebNameMap = new(StringComparer.OrdinalIgnoreCase) |
| 2 | 355 | | { |
| 2 | 356 | | ["aliceblue"] = FromHexInline("#F0F8FFFF"), |
| 2 | 357 | | ["antiquewhite"] = FromHexInline("#FAEBD7FF"), |
| 2 | 358 | | ["aqua"] = FromHexInline("#00FFFFFF"), |
| 2 | 359 | | ["aquamarine"] = FromHexInline("#7FFFD4FF"), |
| 2 | 360 | | ["azure"] = FromHexInline("#F0FFFFFF"), |
| 2 | 361 | | ["beige"] = FromHexInline("#F5F5DCFF"), |
| 2 | 362 | | ["bisque"] = FromHexInline("#FFE4C4FF"), |
| 2 | 363 | | ["black"] = FromHexInline("#000000FF"), |
| 2 | 364 | | ["blanchedalmond"] = FromHexInline("#FFEBCDFF"), |
| 2 | 365 | | ["blue"] = FromHexInline("#0000FFFF"), |
| 2 | 366 | | ["blueviolet"] = FromHexInline("#8A2BE2FF"), |
| 2 | 367 | | ["brown"] = FromHexInline("#A52A2AFF"), |
| 2 | 368 | | ["burlywood"] = FromHexInline("#DEB887FF"), |
| 2 | 369 | | ["cadetblue"] = FromHexInline("#5F9EA0FF"), |
| 2 | 370 | | ["chartreuse"] = FromHexInline("#7FFF00FF"), |
| 2 | 371 | | ["chocolate"] = FromHexInline("#D2691EFF"), |
| 2 | 372 | | ["coral"] = FromHexInline("#FF7F50FF"), |
| 2 | 373 | | ["cornflowerblue"] = FromHexInline("#6495EDFF"), |
| 2 | 374 | | ["cornsilk"] = FromHexInline("#FFF8DCFF"), |
| 2 | 375 | | ["crimson"] = FromHexInline("#DC143CFF"), |
| 2 | 376 | | ["cyan"] = FromHexInline("#00FFFFFF"), |
| 2 | 377 | | ["darkblue"] = FromHexInline("#00008BFF"), |
| 2 | 378 | | ["darkcyan"] = FromHexInline("#008B8BFF"), |
| 2 | 379 | | ["darkgoldenrod"] = FromHexInline("#B8860BFF"), |
| 2 | 380 | | ["darkgray"] = FromHexInline("#A9A9A9FF"), |
| 2 | 381 | | ["darkgreen"] = FromHexInline("#006400FF"), |
| 2 | 382 | | ["darkkhaki"] = FromHexInline("#BDB76BFF"), |
| 2 | 383 | | ["darkmagenta"] = FromHexInline("#8B008BFF"), |
| 2 | 384 | | ["darkolivegreen"] = FromHexInline("#556B2FFF"), |
| 2 | 385 | | ["darkorange"] = FromHexInline("#FF8C00FF"), |
| 2 | 386 | | ["darkorchid"] = FromHexInline("#9932CCFF"), |
| 2 | 387 | | ["darkred"] = FromHexInline("#8B0000FF"), |
| 2 | 388 | | ["darksalmon"] = FromHexInline("#E9967AFF"), |
| 2 | 389 | | ["darkseagreen"] = FromHexInline("#8FBC8FFF"), |
| 2 | 390 | | ["darkslateblue"] = FromHexInline("#483D8BFF"), |
| 2 | 391 | | ["darkslategray"] = FromHexInline("#2F4F4FFF"), |
| 2 | 392 | | ["darkturquoise"] = FromHexInline("#00CED1FF"), |
| 2 | 393 | | ["darkviolet"] = FromHexInline("#9400D3FF"), |
| 2 | 394 | | ["deeppink"] = FromHexInline("#FF1493FF"), |
| 2 | 395 | | ["deepskyblue"] = FromHexInline("#00BFFFFF"), |
| 2 | 396 | | ["dimgray"] = FromHexInline("#696969FF"), |
| 2 | 397 | | ["dodgerblue"] = FromHexInline("#1E90FFFF"), |
| 2 | 398 | | ["firebrick"] = FromHexInline("#B22222FF"), |
| 2 | 399 | | ["floralwhite"] = FromHexInline("#FFFAF0FF"), |
| 2 | 400 | | ["forestgreen"] = FromHexInline("#228B22FF"), |
| 2 | 401 | | ["fuchsia"] = FromHexInline("#FF00FFFF"), |
| 2 | 402 | | ["gainsboro"] = FromHexInline("#DCDCDCFF"), |
| 2 | 403 | | ["ghostwhite"] = FromHexInline("#F8F8FFFF"), |
| 2 | 404 | | ["gold"] = FromHexInline("#FFD700FF"), |
| 2 | 405 | | ["goldenrod"] = FromHexInline("#DAA520FF"), |
| 2 | 406 | | ["gray"] = FromHexInline("#808080FF"), |
| 2 | 407 | | ["green"] = FromHexInline("#008000FF"), |
| 2 | 408 | | ["greenyellow"] = FromHexInline("#ADFF2FFF"), |
| 2 | 409 | | ["honeydew"] = FromHexInline("#F0FFF0FF"), |
| 2 | 410 | | ["hotpink"] = FromHexInline("#FF69B4FF"), |
| 2 | 411 | | ["indianred"] = FromHexInline("#CD5C5CFF"), |
| 2 | 412 | | ["indigo"] = FromHexInline("#4B0082FF"), |
| 2 | 413 | | ["ivory"] = FromHexInline("#FFFFF0FF"), |
| 2 | 414 | | ["khaki"] = FromHexInline("#F0E68CFF"), |
| 2 | 415 | | ["lavender"] = FromHexInline("#E6E6FAFF"), |
| 2 | 416 | | ["lavenderblush"] = FromHexInline("#FFF0F5FF"), |
| 2 | 417 | | ["lawngreen"] = FromHexInline("#7CFC00FF"), |
| 2 | 418 | | ["lemonchiffon"] = FromHexInline("#FFFACDFF"), |
| 2 | 419 | | ["lightblue"] = FromHexInline("#ADD8E6FF"), |
| 2 | 420 | | ["lightcoral"] = FromHexInline("#F08080FF"), |
| 2 | 421 | | ["lightcyan"] = FromHexInline("#E0FFFFFF"), |
| 2 | 422 | | ["lightgoldenrodyellow"] = FromHexInline("#FAFAD2FF"), |
| 2 | 423 | | ["lightgray"] = FromHexInline("#D3D3D3FF"), |
| 2 | 424 | | ["lightgreen"] = FromHexInline("#90EE90FF"), |
| 2 | 425 | | ["lightpink"] = FromHexInline("#FFB6C1FF"), |
| 2 | 426 | | ["lightsalmon"] = FromHexInline("#FFA07AFF"), |
| 2 | 427 | | ["lightseagreen"] = FromHexInline("#20B2AAFF"), |
| 2 | 428 | | ["lightskyblue"] = FromHexInline("#87CEFAFF"), |
| 2 | 429 | | ["lightslategray"] = FromHexInline("#778899FF"), |
| 2 | 430 | | ["lightsteelblue"] = FromHexInline("#B0C4DEFF"), |
| 2 | 431 | | ["lightyellow"] = FromHexInline("#FFFFE0FF"), |
| 2 | 432 | | ["lime"] = FromHexInline("#00FF00FF"), |
| 2 | 433 | | ["limegreen"] = FromHexInline("#32CD32FF"), |
| 2 | 434 | | ["linen"] = FromHexInline("#FAF0E6FF"), |
| 2 | 435 | | ["magenta"] = FromHexInline("#FF00FFFF"), |
| 2 | 436 | | ["maroon"] = FromHexInline("#800000FF"), |
| 2 | 437 | | ["mediumaquamarine"] = FromHexInline("#66CDAAFF"), |
| 2 | 438 | | ["mediumblue"] = FromHexInline("#0000CDFF"), |
| 2 | 439 | | ["mediumorchid"] = FromHexInline("#BA55D3FF"), |
| 2 | 440 | | ["mediumpurple"] = FromHexInline("#9370DBFF"), |
| 2 | 441 | | ["mediumseagreen"] = FromHexInline("#3CB371FF"), |
| 2 | 442 | | ["mediumslateblue"] = FromHexInline("#7B68EEFF"), |
| 2 | 443 | | ["mediumspringgreen"] = FromHexInline("#00FA9AFF"), |
| 2 | 444 | | ["mediumturquoise"] = FromHexInline("#48D1CCFF"), |
| 2 | 445 | | ["mediumvioletred"] = FromHexInline("#C71585FF"), |
| 2 | 446 | | ["midnightblue"] = FromHexInline("#191970FF"), |
| 2 | 447 | | ["mintcream"] = FromHexInline("#F5FFFAFF"), |
| 2 | 448 | | ["mistyrose"] = FromHexInline("#FFE4E1FF"), |
| 2 | 449 | | ["moccasin"] = FromHexInline("#FFE4B5FF"), |
| 2 | 450 | | ["navajowhite"] = FromHexInline("#FFDEADFF"), |
| 2 | 451 | | ["navy"] = FromHexInline("#000080FF"), |
| 2 | 452 | | ["oldlace"] = FromHexInline("#FDF5E6FF"), |
| 2 | 453 | | ["olive"] = FromHexInline("#808000FF"), |
| 2 | 454 | | ["olivedrab"] = FromHexInline("#6B8E23FF"), |
| 2 | 455 | | ["orange"] = FromHexInline("#FFA500FF"), |
| 2 | 456 | | ["orangered"] = FromHexInline("#FF4500FF"), |
| 2 | 457 | | ["orchid"] = FromHexInline("#DA70D6FF"), |
| 2 | 458 | | ["palegoldenrod"] = FromHexInline("#EEE8AAFF"), |
| 2 | 459 | | ["palegreen"] = FromHexInline("#98FB98FF"), |
| 2 | 460 | | ["paleturquoise"] = FromHexInline("#AFEEEEFF"), |
| 2 | 461 | | ["palevioletred"] = FromHexInline("#DB7093FF"), |
| 2 | 462 | | ["papayawhip"] = FromHexInline("#FFEFD5FF"), |
| 2 | 463 | | ["peachpuff"] = FromHexInline("#FFDAB9FF"), |
| 2 | 464 | | ["peru"] = FromHexInline("#CD853FFF"), |
| 2 | 465 | | ["pink"] = FromHexInline("#FFC0CBFF"), |
| 2 | 466 | | ["plum"] = FromHexInline("#DDA0DDFF"), |
| 2 | 467 | | ["powderblue"] = FromHexInline("#B0E0E6FF"), |
| 2 | 468 | | ["purple"] = FromHexInline("#800080FF"), |
| 2 | 469 | | ["red"] = FromHexInline("#FF0000FF"), |
| 2 | 470 | | ["rosybrown"] = FromHexInline("#BC8F8FFF"), |
| 2 | 471 | | ["royalblue"] = FromHexInline("#4169E1FF"), |
| 2 | 472 | | ["saddlebrown"] = FromHexInline("#8B4513FF"), |
| 2 | 473 | | ["salmon"] = FromHexInline("#FA8072FF"), |
| 2 | 474 | | ["sandybrown"] = FromHexInline("#F4A460FF"), |
| 2 | 475 | | ["seagreen"] = FromHexInline("#2E8B57FF"), |
| 2 | 476 | | ["seashell"] = FromHexInline("#FFF5EEFF"), |
| 2 | 477 | | ["sienna"] = FromHexInline("#A0522DFF"), |
| 2 | 478 | | ["silver"] = FromHexInline("#C0C0C0FF"), |
| 2 | 479 | | ["skyblue"] = FromHexInline("#87CEEBFF"), |
| 2 | 480 | | ["slateblue"] = FromHexInline("#6A5ACDFF"), |
| 2 | 481 | | ["slategray"] = FromHexInline("#708090FF"), |
| 2 | 482 | | ["snow"] = FromHexInline("#FFFAFAFF"), |
| 2 | 483 | | ["springgreen"] = FromHexInline("#00FF7FFF"), |
| 2 | 484 | | ["steelblue"] = FromHexInline("#4682B4FF"), |
| 2 | 485 | | ["tan"] = FromHexInline("#D2B48CFF"), |
| 2 | 486 | | ["teal"] = FromHexInline("#008080FF"), |
| 2 | 487 | | ["thistle"] = FromHexInline("#D8BFD8FF"), |
| 2 | 488 | | ["tomato"] = FromHexInline("#FF6347FF"), |
| 2 | 489 | | ["turquoise"] = FromHexInline("#40E0D0FF"), |
| 2 | 490 | | ["violet"] = FromHexInline("#EE82EEFF"), |
| 2 | 491 | | ["wheat"] = FromHexInline("#F5DEB3FF"), |
| 2 | 492 | | ["white"] = FromHexInline("#FFFFFFFF"), |
| 2 | 493 | | ["whitesmoke"] = FromHexInline("#F5F5F5FF"), |
| 2 | 494 | | ["yellow"] = FromHexInline("#FFFF00FF"), |
| 2 | 495 | | ["yellowgreen"] = FromHexInline("#9ACD32FF"), |
| 2 | 496 | | ["transparent"] = FromHexInline("#00000000") |
| 2 | 497 | | }; |
| | 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) |
| 1326 | 506 | | : base(string.Empty) |
| | 507 | | { |
| 1326 | 508 | | HsvToRgb(h, s, v, out var r, out var g, out var b); |
| 1326 | 509 | | R = r; |
| 1326 | 510 | | G = g; |
| 1326 | 511 | | B = b; |
| 1326 | 512 | | A = a; |
| 1326 | 513 | | } |
| | 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) |
| 1058 | 522 | | : base(string.Empty) |
| | 523 | | { |
| 1058 | 524 | | if (a is < 0 or > 1.0) |
| | 525 | | { |
| 4 | 526 | | throw new ArgumentOutOfRangeException(nameof(a), a, "A must be between 0 and 1.0."); |
| | 527 | | } |
| | 528 | |
|
| 1054 | 529 | | R = r; |
| 1054 | 530 | | G = g; |
| 1054 | 531 | | B = b; |
| 1054 | 532 | | A = a; |
| 1054 | 533 | | } |
| | 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) |
| 490 | 542 | | : base(string.Empty) |
| | 543 | | { |
| | 544 | | try |
| | 545 | | { |
| 490 | 546 | | ArgumentException.ThrowIfNullOrWhiteSpace(value, nameof(value)); |
| | 547 | |
|
| 488 | 548 | | var s = value.Trim(); |
| | 549 | |
|
| | 550 | | // Hex forms |
| 488 | 551 | | if (s.StartsWith("#", StringComparison.Ordinal)) |
| | 552 | | { |
| 444 | 553 | | FromHexString(s, out var r, out var g, out var b, out var a); |
| 440 | 554 | | R = r; |
| 440 | 555 | | G = g; |
| 440 | 556 | | B = b; |
| 440 | 557 | | A = a; |
| | 558 | |
|
| 440 | 559 | | return; |
| | 560 | | } |
| | 561 | |
|
| | 562 | | // rgb()/rgba() |
| 44 | 563 | | if (s.StartsWith("rgb", StringComparison.OrdinalIgnoreCase)) |
| | 564 | | { |
| 12 | 565 | | FromRgbString(s, out var r, out var g, out var b, out var a); |
| 6 | 566 | | R = r; |
| 6 | 567 | | G = g; |
| 6 | 568 | | B = b; |
| 6 | 569 | | A = a; |
| | 570 | |
|
| 6 | 571 | | return; |
| | 572 | | } |
| | 573 | |
|
| | 574 | | // hsv()/hsva() |
| 32 | 575 | | if (s.StartsWith("hsv", StringComparison.OrdinalIgnoreCase)) |
| | 576 | | { |
| 12 | 577 | | FromHsvString(s, out var r, out var g, out var b, out var a); |
| 4 | 578 | | R = r; |
| 4 | 579 | | G = g; |
| 4 | 580 | | B = b; |
| 4 | 581 | | A = a; |
| | 582 | |
|
| 4 | 583 | | return; |
| | 584 | | } |
| | 585 | |
|
| | 586 | | // Named palettes |
| 20 | 587 | | if (TryFromWebName(s, out var web)) |
| | 588 | | { |
| 8 | 589 | | R = web.R; |
| 8 | 590 | | G = web.G; |
| 8 | 591 | | B = web.B; |
| 8 | 592 | | A = web.A; |
| | 593 | |
|
| 8 | 594 | | return; |
| | 595 | | } |
| | 596 | |
|
| 12 | 597 | | if (TryFromMaterialName(s, out var mat)) |
| | 598 | | { |
| 6 | 599 | | R = mat.R; |
| 6 | 600 | | G = mat.G; |
| 6 | 601 | | B = mat.B; |
| 6 | 602 | | A = mat.A; |
| | 603 | |
|
| 6 | 604 | | return; |
| | 605 | | } |
| | 606 | |
|
| 6 | 607 | | throw new ArgumentException("Unable to parse value.", nameof(value)); |
| | 608 | | } |
| 26 | 609 | | catch (Exception exception) |
| | 610 | | { |
| 26 | 611 | | throw new ArgumentException( |
| 26 | 612 | | $"Unrecognized color: '{value}'. Expected #RRGGBB, #RRGGBBAA, rgb(), rgba(), hsv(), hsva(), a CSS Web co |
| 26 | 613 | | nameof(value), exception |
| 26 | 614 | | ); |
| | 615 | | } |
| 464 | 616 | | } |
| | 617 | |
|
| | 618 | | /// <summary>Gets the alpha channel as a unit value in the range [0..1].</summary> |
| 440 | 619 | | 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> |
| 406 | 626 | | 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> |
| 4712 | 629 | | public byte B { get; } |
| | 630 | |
|
| | 631 | | /// <summary>Gets the green channel in the range [0..255].</summary> |
| 4712 | 632 | | 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 | | { |
| 134 | 640 | | RgbToHsv(R, G, B, out var h, out _, out _); |
| | 641 | |
|
| 134 | 642 | | return h; |
| | 643 | | } |
| | 644 | | } |
| | 645 | |
|
| | 646 | | /// <summary>Gets the uppercase <c>#RRGGBB</c> representation of the color (alpha omitted).</summary> |
| 18 | 647 | | 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> |
| 406 | 650 | | 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 | | { |
| 4 | 657 | | RgbToHsv(R, G, B, out var h, out var s, out var v); |
| | 658 | |
|
| 4 | 659 | | 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 | | { |
| 2 | 668 | | RgbToHsv(R, G, B, out var h, out var s, out var v); |
| | 669 | |
|
| 2 | 670 | | 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> |
| 4712 | 675 | | public byte R { get; } |
| | 676 | |
|
| | 677 | | /// <summary>Gets the <c>rgb(r, g, b)</c> representation.</summary> |
| 2 | 678 | | 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> |
| 2 | 683 | | 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 | | { |
| 132 | 691 | | RgbToHsv(R, G, B, out _, out var s, out _); |
| | 692 | |
|
| 132 | 693 | | 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 | | { |
| 158 | 703 | | RgbToHsv(R, G, B, out _, out _, out var v); |
| | 704 | |
|
| 158 | 705 | | return v; |
| | 706 | | } |
| | 707 | | } |
| | 708 | |
|
| | 709 | | /// <summary>Gets the style value represented as a string.</summary> |
| 344 | 710 | | 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 | | { |
| 794 | 717 | | FromHexString(hex, out var r, out var g, out var b, out var a); |
| | 718 | |
|
| 794 | 719 | | 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 | | { |
| 1238 | 734 | | var hex = s.Trim(); |
| 1238 | 735 | | hex = hex[1..]; |
| | 736 | |
|
| 1238 | 737 | | if (hex.Length is 3 or 4) |
| | 738 | | { |
| | 739 | | // #RGB or #RGBA -> expand nibbles |
| 14 | 740 | | var r1 = ToHexNibble(hex[0]); |
| 14 | 741 | | var g1 = ToHexNibble(hex[1]); |
| 14 | 742 | | var b1 = ToHexNibble(hex[2]); |
| 12 | 743 | | r = (byte)(r1 * 17); |
| 12 | 744 | | g = (byte)(g1 * 17); |
| 12 | 745 | | b = (byte)(b1 * 17); |
| | 746 | |
|
| 12 | 747 | | a = hex.Length == 4 |
| 12 | 748 | | ? Math.Clamp(ToHexNibble(hex[3]) * 17 / 255.0, 0.0, 1.0) |
| 12 | 749 | | : 1.0; |
| | 750 | |
|
| 12 | 751 | | return; |
| | 752 | | } |
| | 753 | |
|
| 1224 | 754 | | if (hex.Length is 6 or 8) |
| | 755 | | { |
| 1222 | 756 | | r = Convert.ToByte(hex.Substring(0, 2), 16); |
| 1222 | 757 | | g = Convert.ToByte(hex.Substring(2, 2), 16); |
| 1222 | 758 | | b = Convert.ToByte(hex.Substring(4, 2), 16); |
| | 759 | |
|
| 1222 | 760 | | a = hex.Length == 8 |
| 1222 | 761 | | ? Convert.ToByte(hex.Substring(6, 2), 16) / 255.0 |
| 1222 | 762 | | : 1.0; |
| | 763 | |
|
| 1222 | 764 | | return; |
| | 765 | | } |
| | 766 | |
|
| 2 | 767 | | 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 | | { |
| 1326 | 778 | | var hh = h % 360.0; |
| | 779 | |
|
| 1326 | 780 | | if (hh < 0.0) |
| | 781 | | { |
| 2 | 782 | | hh += 360.0; |
| | 783 | | } |
| | 784 | |
|
| 1326 | 785 | | var ss = Math.Clamp(s, 0.0, 100.0); |
| 1326 | 786 | | var vv = Math.Clamp(v, 0.0, 100.0); |
| 1326 | 787 | | var aa = Math.Clamp(a, 0.0, 1.0); |
| | 788 | |
|
| 1326 | 789 | | 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 | | { |
| 12 | 804 | | var m = RxHsv.Match(s); |
| | 805 | |
|
| 12 | 806 | | if (!m.Success) |
| | 807 | | { |
| 4 | 808 | | throw new ArgumentException( |
| 4 | 809 | | $"Invalid hsv/hsva() format: '{s}'. Expected hsv(H,S%,V%) or hsva(H,S%,V%,A).", nameof(s) |
| 4 | 810 | | ); |
| | 811 | | } |
| | 812 | |
|
| 8 | 813 | | var h = Math.Clamp(ParseDouble(m.Groups["h"].Value, "H", 0, 360), 0, 360); |
| 8 | 814 | | var sp = Math.Clamp(ParseDouble(m.Groups["s"].Value, "S%", 0, 100), 0, 100); |
| 6 | 815 | | var vp = Math.Clamp(ParseDouble(m.Groups["v"].Value, "V%", 0, 100), 0, 100); |
| | 816 | |
|
| 6 | 817 | | a = m.Groups["a"].Success |
| 6 | 818 | | ? Math.Clamp(ParseDouble(m.Groups["a"].Value, "A", 0, 1), 0.0, 1.0) |
| 6 | 819 | | : 1.0; |
| | 820 | |
|
| 4 | 821 | | HsvToRgb(h, sp, vp, out r, out g, out b); |
| 4 | 822 | | } |
| | 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> |
| 264 | 830 | | 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 | | { |
| 12 | 844 | | var m = RxRgb.Match(s); |
| | 845 | |
|
| 12 | 846 | | if (!m.Success) |
| | 847 | | { |
| 4 | 848 | | throw new ArgumentException( |
| 4 | 849 | | $"Invalid rgb/rgba() format: '{s}'. Expected rgb(r,g,b) or rgba(r,g,b,a).", nameof(s) |
| 4 | 850 | | ); |
| | 851 | | } |
| | 852 | |
|
| 8 | 853 | | r = (byte)Math.Clamp(ParseInt(m.Groups["r"].Value, "r", 0, 255), 0, 255); |
| 6 | 854 | | g = (byte)Math.Clamp(ParseInt(m.Groups["g"].Value, "g", 0, 255), 0, 255); |
| 6 | 855 | | b = (byte)Math.Clamp(ParseInt(m.Groups["b"].Value, "b", 0, 255), 0, 255); |
| | 856 | |
|
| 6 | 857 | | a = m.Groups["a"].Success |
| 6 | 858 | | ? Math.Clamp(ParseDouble(m.Groups["a"].Value, "a", 0, 1), 0.0, 1.0) |
| 6 | 859 | | : 1.0; |
| 6 | 860 | | } |
| | 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 | | { |
| 1330 | 880 | | s = Math.Clamp(s / 100.0, 0.0, 1.0); |
| 1330 | 881 | | v = Math.Clamp(v / 100.0, 0.0, 1.0); |
| | 882 | |
|
| 1330 | 883 | | if (s <= 0.0) |
| | 884 | | { |
| 426 | 885 | | var c = (byte)Math.Round(v * 255.0); |
| 426 | 886 | | r = g = b = c; |
| | 887 | |
|
| 426 | 888 | | return; |
| | 889 | | } |
| | 890 | |
|
| 904 | 891 | | h = (h % 360 + 360) % 360; |
| 904 | 892 | | var hh = h / 60.0; |
| 904 | 893 | | var i = (int)Math.Floor(hh); |
| 904 | 894 | | var ff = hh - i; |
| | 895 | |
|
| 904 | 896 | | var p = v * (1.0 - s); |
| 904 | 897 | | var q = v * (1.0 - s * ff); |
| 904 | 898 | | var t = v * (1.0 - s * (1.0 - ff)); |
| | 899 | |
|
| | 900 | | double r1, g1, b1; |
| | 901 | |
|
| | 902 | | switch (i) |
| | 903 | | { |
| | 904 | | case 0: |
| 424 | 905 | | r1 = v; |
| 424 | 906 | | g1 = t; |
| 424 | 907 | | b1 = p; |
| | 908 | |
|
| 424 | 909 | | break; |
| | 910 | | case 1: |
| 84 | 911 | | r1 = q; |
| 84 | 912 | | g1 = v; |
| 84 | 913 | | b1 = p; |
| | 914 | |
|
| 84 | 915 | | break; |
| | 916 | | case 2: |
| 62 | 917 | | r1 = p; |
| 62 | 918 | | g1 = v; |
| 62 | 919 | | b1 = t; |
| | 920 | |
|
| 62 | 921 | | break; |
| | 922 | | case 3: |
| 14 | 923 | | r1 = p; |
| 14 | 924 | | g1 = q; |
| 14 | 925 | | b1 = v; |
| | 926 | |
|
| 14 | 927 | | break; |
| | 928 | | case 4: |
| 318 | 929 | | r1 = t; |
| 318 | 930 | | g1 = p; |
| 318 | 931 | | b1 = v; |
| | 932 | |
|
| 318 | 933 | | break; |
| | 934 | | default: |
| 2 | 935 | | r1 = v; |
| 2 | 936 | | g1 = p; |
| 2 | 937 | | b1 = q; |
| | 938 | |
|
| | 939 | | break; |
| | 940 | | } |
| | 941 | |
|
| 904 | 942 | | r = (byte)Math.Clamp((int)Math.Round(r1 * 255.0), 0, 255); |
| 904 | 943 | | g = (byte)Math.Clamp((int)Math.Round(g1 * 255.0), 0, 255); |
| 904 | 944 | | b = (byte)Math.Clamp((int)Math.Round(b1 * 255.0), 0, 255); |
| 904 | 945 | | } |
| | 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) |
| 12 | 951 | | => input.Trim().ToLowerInvariant() |
| 12 | 952 | | .Replace(" ", string.Empty) |
| 12 | 953 | | .Replace("-", string.Empty) |
| 12 | 954 | | .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> |
| 2 | 960 | | 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 | | { |
| 30 | 972 | | double.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out var v); |
| | 973 | |
|
| 30 | 974 | | if (v < min || v > max) |
| | 975 | | { |
| 4 | 976 | | throw new ArgumentOutOfRangeException(param, v, $"Expected {param} in [{min}..{max}]."); |
| | 977 | | } |
| | 978 | |
|
| 26 | 979 | | 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 | | { |
| 20 | 992 | | int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var v); |
| | 993 | |
|
| 20 | 994 | | if (v < min || v > max) |
| | 995 | | { |
| 2 | 996 | | throw new ArgumentOutOfRangeException(param, v, $"Expected {param} in [{min}..{max}]."); |
| | 997 | | } |
| | 998 | |
|
| 18 | 999 | | 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 | | { |
| 430 | 1020 | | var rf = r / 255.0; |
| 430 | 1021 | | var gf = g / 255.0; |
| 430 | 1022 | | var bf = b / 255.0; |
| | 1023 | |
|
| 430 | 1024 | | var max = Math.Max(rf, Math.Max(gf, bf)); |
| 430 | 1025 | | var min = Math.Min(rf, Math.Min(gf, bf)); |
| 430 | 1026 | | var delta = max - min; |
| | 1027 | |
|
| | 1028 | | // Hue |
| 430 | 1029 | | if (delta < 1e-9) |
| | 1030 | | { |
| 194 | 1031 | | h = 0.0; |
| | 1032 | | } |
| 236 | 1033 | | else if (Math.Abs(max - rf) < 1e-9) |
| | 1034 | | { |
| 92 | 1035 | | h = 60.0 * ((gf - bf) / delta % 6.0); |
| | 1036 | | } |
| 144 | 1037 | | else if (Math.Abs(max - gf) < 1e-9) |
| | 1038 | | { |
| 34 | 1039 | | h = 60.0 * ((bf - rf) / delta + 2.0); |
| | 1040 | | } |
| | 1041 | | else |
| | 1042 | | { |
| 110 | 1043 | | h = 60.0 * ((rf - gf) / delta + 4.0); |
| | 1044 | | } |
| | 1045 | |
|
| 430 | 1046 | | if (h < 0) |
| | 1047 | | { |
| 2 | 1048 | | h += 360.0; |
| | 1049 | | } |
| | 1050 | |
|
| | 1051 | | // Saturation |
| 430 | 1052 | | s = max <= 0 |
| 430 | 1053 | | ? 0 |
| 430 | 1054 | | : delta / max * 100.0; |
| | 1055 | |
|
| | 1056 | | // Value |
| 430 | 1057 | | v = max * 100.0; |
| 430 | 1058 | | } |
| | 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) |
| 48 | 1065 | | => c switch |
| 48 | 1066 | | { |
| 12 | 1067 | | >= '0' and <= '9' => c - '0', |
| 24 | 1068 | | >= 'a' and <= 'f' => c - 'a' + 10, |
| 10 | 1069 | | >= 'A' and <= 'F' => c - 'A' + 10, |
| 2 | 1070 | | _ => throw new ArgumentException($"Invalid hex digit '{c}'.") |
| 48 | 1071 | | }; |
| | 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 | | { |
| 12 | 1084 | | var norm = NormalizeMaterialKey(name); |
| | 1085 | |
|
| 12 | 1086 | | if (MaterialMap.TryGetValue(norm, out var rgba)) |
| | 1087 | | { |
| 6 | 1088 | | color = rgba; |
| | 1089 | |
|
| 6 | 1090 | | return true; |
| | 1091 | | } |
| | 1092 | |
|
| 6 | 1093 | | color = Colors.Transparent; |
| | 1094 | |
|
| 6 | 1095 | | 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 | | { |
| 20 | 1104 | | var key = name.Trim().ToLowerInvariant(); |
| | 1105 | |
|
| 20 | 1106 | | if (WebNameMap.TryGetValue(key, out var rgba)) |
| | 1107 | | { |
| 8 | 1108 | | color = rgba; |
| | 1109 | |
|
| 8 | 1110 | | return true; |
| | 1111 | | } |
| | 1112 | |
|
| 12 | 1113 | | color = Colors.Transparent; |
| | 1114 | |
|
| 12 | 1115 | | 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 | | { |
| 14 | 1126 | | result = new AllyariaColorValue(value); |
| | 1127 | |
|
| 12 | 1128 | | return true; |
| | 1129 | | } |
| 2 | 1130 | | catch |
| | 1131 | | { |
| 2 | 1132 | | result = null; |
| | 1133 | |
|
| 2 | 1134 | | return false; |
| | 1135 | | } |
| 14 | 1136 | | } |
| | 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> |
| 4 | 1141 | | 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> |
| 2 | 1148 | | public static implicit operator string(AllyariaColorValue value) => value.Value; |
| | 1149 | | } |