ColorFormatHelper.cs
1 // Copyright (c) Microsoft Corporation 2 // The Microsoft Corporation licenses this file to you under the MIT license. 3 // See the LICENSE file in the project root for more information. 4 5 using System; 6 using System.Collections.Generic; 7 using System.Drawing; 8 using System.Globalization; 9 10 namespace ManagedCommon 11 { 12 public static class ColorFormatHelper 13 { 14 /// <summary> 15 /// Convert a given <see cref="Color"/> to a CMYK color (cyan, magenta, yellow, black key) 16 /// </summary> 17 /// <param name="color">The <see cref="Color"/> to convert</param> 18 /// <returns>The cyan[0..1], magenta[0..1], yellow[0..1] and black key[0..1] of the converted color</returns> 19 public static (double Cyan, double Magenta, double Yellow, double BlackKey) ConvertToCMYKColor(Color color) 20 { 21 // special case for black (avoid division by zero) 22 if (color.R == 0 && color.G == 0 && color.B == 0) 23 { 24 return (0d, 0d, 0d, 1d); 25 } 26 27 var red = color.R / 255d; 28 var green = color.G / 255d; 29 var blue = color.B / 255d; 30 31 var blackKey = 1d - Math.Max(Math.Max(red, green), blue); 32 33 // special case for black (avoid division by zero) 34 if (1d - blackKey == 0d) 35 { 36 return (0d, 0d, 0d, 1d); 37 } 38 39 var cyan = (1d - red - blackKey) / (1d - blackKey); 40 var magenta = (1d - green - blackKey) / (1d - blackKey); 41 var yellow = (1d - blue - blackKey) / (1d - blackKey); 42 43 return (cyan, magenta, yellow, blackKey); 44 } 45 46 /// <summary> 47 /// Convert a given <see cref="Color"/> to a HSB color (hue, saturation, brightness) 48 /// </summary> 49 /// <param name="color">The <see cref="Color"/> to convert</param> 50 /// <returns>The hue [0°..360°], saturation [0..1] and brightness [0..1] of the converted color</returns> 51 public static (double Hue, double Saturation, double Brightness) ConvertToHSBColor(Color color) 52 { 53 // HSB and HSV represents the same color space 54 return ConvertToHSVColor(color); 55 } 56 57 /// <summary> 58 /// Convert a given <see cref="Color"/> to a HSV color (hue, saturation, value) 59 /// </summary> 60 /// <param name="color">The <see cref="Color"/> to convert</param> 61 /// <returns>The hue [0°..360°], saturation [0..1] and value [0..1] of the converted color</returns> 62 public static (double Hue, double Saturation, double Value) ConvertToHSVColor(Color color) 63 { 64 var min = Math.Min(Math.Min(color.R, color.G), color.B) / 255d; 65 var max = Math.Max(Math.Max(color.R, color.G), color.B) / 255d; 66 67 return (color.GetHue(), max == 0d ? 0d : (max - min) / max, max); 68 } 69 70 /// <summary> 71 /// Convert a given <see cref="Color"/> to a HSI color (hue, saturation, intensity) 72 /// </summary> 73 /// <param name="color">The <see cref="Color"/> to convert</param> 74 /// <returns>The hue [0°..360°], saturation [0..1] and intensity [0..1] of the converted color</returns> 75 public static (double Hue, double Saturation, double Intensity) ConvertToHSIColor(Color color) 76 { 77 // special case for black 78 if (color.R == 0 && color.G == 0 && color.B == 0) 79 { 80 return (0d, 0d, 0d); 81 } 82 83 var red = color.R / 255d; 84 var green = color.G / 255d; 85 var blue = color.B / 255d; 86 87 var intensity = (red + green + blue) / 3d; 88 89 var min = Math.Min(Math.Min(color.R, color.G), color.B) / 255d; 90 91 return (color.GetHue(), 1d - (min / intensity), intensity); 92 } 93 94 /// <summary> 95 /// Convert a given <see cref="Color"/> to a HSL color (hue, saturation, lightness) 96 /// </summary> 97 /// <param name="color">The <see cref="Color"/> to convert</param> 98 /// <returns>The hue [0°..360°], saturation [0..1] and lightness [0..1] values of the converted color</returns> 99 public static (double Hue, double Saturation, double Lightness) ConvertToHSLColor(Color color) 100 { 101 var min = Math.Min(Math.Min(color.R, color.G), color.B) / 255d; 102 var max = Math.Max(Math.Max(color.R, color.G), color.B) / 255d; 103 104 var lightness = (max + min) / 2d; 105 106 if (lightness == 0d || min == max) 107 { 108 return (color.GetHue(), 0d, lightness); 109 } 110 else if (lightness > 0d && lightness <= 0.5d) 111 { 112 return (color.GetHue(), (max - min) / (max + min), lightness); 113 } 114 115 return (color.GetHue(), (max - min) / (2d - (max + min)), lightness); 116 } 117 118 /// <summary> 119 /// Convert a given <see cref="Color"/> to a HWB color (hue, whiteness, blackness) 120 /// </summary> 121 /// <param name="color">The <see cref="Color"/> to convert</param> 122 /// <returns>The hue [0°..360°], whiteness [0..1] and blackness [0..1] of the converted color</returns> 123 public static (double Hue, double Whiteness, double Blackness) ConvertToHWBColor(Color color) 124 { 125 var min = Math.Min(Math.Min(color.R, color.G), color.B) / 255d; 126 var max = Math.Max(Math.Max(color.R, color.G), color.B) / 255d; 127 128 return (color.GetHue(), min, 1 - max); 129 } 130 131 /// <summary> 132 /// Convert a given <see cref="Color"/> to a CIE LAB color (LAB) 133 /// </summary> 134 /// <param name="color">The <see cref="Color"/> to convert</param> 135 /// <returns>The lightness [0..100] and two chromaticities [-128..127]</returns> 136 public static (double Lightness, double ChromaticityA, double ChromaticityB) ConvertToCIELABColor(Color color) 137 { 138 var xyz = ConvertToCIEXYZColor(color); 139 var lab = GetCIELABColorFromCIEXYZ(xyz.X, xyz.Y, xyz.Z); 140 141 return lab; 142 } 143 144 /// <summary> 145 /// Convert a given <see cref="Color"/> to a Oklab color 146 /// </summary> 147 /// <param name="color">The <see cref="Color"/> to convert</param> 148 /// <returns>The perceptual lightness [0..1] and two chromaticities [-0.5..0.5]</returns> 149 public static (double Lightness, double ChromaticityA, double ChromaticityB) ConvertToOklabColor(Color color) 150 { 151 var linear = ConvertSRGBToLinearRGB(color.R / 255d, color.G / 255d, color.B / 255d); 152 var oklab = GetOklabColorFromLinearRGB(linear.R, linear.G, linear.B); 153 return oklab; 154 } 155 156 /// <summary> 157 /// Convert a given <see cref="Color"/> to a Oklch color 158 /// </summary> 159 /// <param name="color">The <see cref="Color"/> to convert</param> 160 /// <returns>The perceptual lightness [0..1], the chroma [0..0.5], and the hue angle [0°..360°]</returns> 161 public static (double Lightness, double Chroma, double Hue) ConvertToOklchColor(Color color) 162 { 163 var oklab = ConvertToOklabColor(color); 164 var oklch = GetOklchColorFromOklab(oklab.Lightness, oklab.ChromaticityA, oklab.ChromaticityB); 165 166 return oklch; 167 } 168 169 public static (double R, double G, double B) ConvertSRGBToLinearRGB(double r, double g, double b) 170 { 171 // inverse companding, gamma correction must be undone 172 double rLinear = (r > 0.04045) ? Math.Pow((r + 0.055) / 1.055, 2.4) : (r / 12.92); 173 double gLinear = (g > 0.04045) ? Math.Pow((g + 0.055) / 1.055, 2.4) : (g / 12.92); 174 double bLinear = (b > 0.04045) ? Math.Pow((b + 0.055) / 1.055, 2.4) : (b / 12.92); 175 return (rLinear, gLinear, bLinear); 176 } 177 178 /// <summary> 179 /// Convert a given <see cref="Color"/> to a CIE XYZ color (XYZ) 180 /// The constants of the formula matches this Wikipedia page, but at a higher precision: 181 /// https://en.wikipedia.org/wiki/SRGB#The_reverse_transformation_(sRGB_to_CIE_XYZ) 182 /// This page provides a method to calculate the constants: 183 /// http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html 184 /// </summary> 185 /// <param name="color">The <see cref="Color"/> to convert</param> 186 /// <returns>The X [0..1], Y [0..1] and Z [0..1]</returns> 187 public static (double X, double Y, double Z) ConvertToCIEXYZColor(Color color) 188 { 189 double r = color.R / 255d; 190 double g = color.G / 255d; 191 double b = color.B / 255d; 192 193 (double rLinear, double gLinear, double bLinear) = ConvertSRGBToLinearRGB(r, g, b); 194 195 return ( 196 (rLinear * 0.41239079926595948) + (gLinear * 0.35758433938387796) + (bLinear * 0.18048078840183429), 197 (rLinear * 0.21263900587151036) + (gLinear * 0.71516867876775593) + (bLinear * 0.07219231536073372), 198 (rLinear * 0.01933081871559185) + (gLinear * 0.11919477979462599) + (bLinear * 0.95053215224966058) 199 ); 200 } 201 202 /// <summary> 203 /// Convert a CIE XYZ color <see cref="double"/> to a CIE LAB color (LAB) adapted to sRGB D65 white point 204 /// The constants of the formula used come from this wikipedia page: 205 /// https://en.wikipedia.org/wiki/CIELAB_color_space#Converting_between_CIELAB_and_CIEXYZ_coordinates 206 /// </summary> 207 /// <param name="x">The <see cref="x"/> represents a mix of the three CIE RGB curves</param> 208 /// <param name="y">The <see cref="y"/> represents the luminance</param> 209 /// <param name="z">The <see cref="z"/> is quasi-equal to blue (of CIE RGB)</param> 210 /// <returns>The lightness [0..100] and two chromaticities [-128..127]</returns> 211 private static (double Lightness, double ChromaticityA, double ChromaticityB) 212 GetCIELABColorFromCIEXYZ(double x, double y, double z) 213 { 214 // sRGB reference white (x=0.3127, y=0.3290, Y=1.0), actually CIE Standard Illuminant D65 truncated to 4 decimal places, 215 // then converted to XYZ using the formula: 216 // X = x * (Y / y) 217 // Y = Y 218 // Z = (1 - x - y) * (Y / y) 219 double x_n = 0.9504559270516717; 220 double y_n = 1.0; 221 double z_n = 1.0890577507598784; 222 223 // Scale XYZ values relative to reference white 224 x /= x_n; 225 y /= y_n; 226 z /= z_n; 227 228 // XYZ to CIELab transformation 229 double delta = 6d / 29; 230 double m = (1d / 3) * Math.Pow(delta, -2); 231 double t = Math.Pow(delta, 3); 232 233 double fx = (x > t) ? Math.Pow(x, 1.0 / 3.0) : (x * m) + (16.0 / 116.0); 234 double fy = (y > t) ? Math.Pow(y, 1.0 / 3.0) : (y * m) + (16.0 / 116.0); 235 double fz = (z > t) ? Math.Pow(z, 1.0 / 3.0) : (z * m) + (16.0 / 116.0); 236 237 double l = (116 * fy) - 16; 238 double a = 500 * (fx - fy); 239 double b = 200 * (fy - fz); 240 241 return (l, a, b); 242 } 243 244 /// <summary> 245 /// Convert a linear RGB color <see cref="double"/> to an Oklab color. 246 /// The constants of this formula come from https://github.com/Evercoder/culori/blob/2bedb8f0507116e75f844a705d0b45cf279b15d0/src/oklab/convertLrgbToOklab.js 247 /// and the implementation is based on https://bottosson.github.io/posts/oklab/ 248 /// </summary> 249 /// <param name="r">Linear R value</param> 250 /// <param name="g">Linear G value</param> 251 /// <param name="b">Linear B value</param> 252 /// <returns>The perceptual lightness [0..1] and two chromaticities [-0.5..0.5]</returns> 253 private static (double Lightness, double ChromaticityA, double ChromaticityB) 254 GetOklabColorFromLinearRGB(double r, double g, double b) 255 { 256 double l = (0.41222147079999993 * r) + (0.5363325363 * g) + (0.0514459929 * b); 257 double m = (0.2119034981999999 * r) + (0.6806995450999999 * g) + (0.1073969566 * b); 258 double s = (0.08830246189999998 * r) + (0.2817188376 * g) + (0.6299787005000002 * b); 259 260 double l_ = Math.Cbrt(l); 261 double m_ = Math.Cbrt(m); 262 double s_ = Math.Cbrt(s); 263 264 return ( 265 (0.2104542553 * l_) + (0.793617785 * m_) - (0.0040720468 * s_), 266 (1.9779984951 * l_) - (2.428592205 * m_) + (0.4505937099 * s_), 267 (0.0259040371 * l_) + (0.7827717662 * m_) - (0.808675766 * s_) 268 ); 269 } 270 271 /// <summary> 272 /// Convert an Oklab color <see cref="double"/> from Cartesian form to its polar form Oklch 273 /// https://bottosson.github.io/posts/oklab/#the-oklab-color-space 274 /// </summary> 275 /// <param name="lightness">The <see cref="lightness"/></param> 276 /// <param name="chromaticity_a">The <see cref="chromaticity_a"/></param> 277 /// <param name="chromaticity_b">The <see cref="chromaticity_b"/></param> 278 /// <returns>The perceptual lightness [0..1], the chroma [0..0.5], and the hue angle [0°..360°]</returns> 279 private static (double Lightness, double Chroma, double Hue) 280 GetOklchColorFromOklab(double lightness, double chromaticity_a, double chromaticity_b) 281 { 282 return GetLCHColorFromLAB(lightness, chromaticity_a, chromaticity_b); 283 } 284 285 /// <summary> 286 /// Convert a color in Cartesian form (Lab) to its polar form (LCh) 287 /// </summary> 288 /// <param name="lightness">The <see cref="lightness"/></param> 289 /// <param name="chromaticity_a">The <see cref="chromaticity_a"/></param> 290 /// <param name="chromaticity_b">The <see cref="chromaticity_b"/></param> 291 /// <returns>The lightness, chroma, and hue angle</returns> 292 private static (double Lightness, double Chroma, double Hue) 293 GetLCHColorFromLAB(double lightness, double chromaticity_a, double chromaticity_b) 294 { 295 // Lab to LCh transformation 296 double chroma = Math.Sqrt(Math.Pow(chromaticity_a, 2) + Math.Pow(chromaticity_b, 2)); 297 double hue = Math.Round(chroma, 3) == 0 ? 0.0 : ((Math.Atan2(chromaticity_b, chromaticity_a) * 180d / Math.PI) + 360d) % 360d; 298 return (lightness, chroma, hue); 299 } 300 301 /// <summary> 302 /// Convert a given <see cref="Color"/> to a natural color (hue, whiteness, blackness) 303 /// </summary> 304 /// <param name="color">The <see cref="Color"/> to convert</param> 305 /// <returns>The hue, whiteness [0..1] and blackness [0..1] of the converted color</returns> 306 public static (string Hue, double Whiteness, double Blackness) ConvertToNaturalColor(Color color) 307 { 308 var min = Math.Min(Math.Min(color.R, color.G), color.B) / 255d; 309 var max = Math.Max(Math.Max(color.R, color.G), color.B) / 255d; 310 311 return (GetNaturalColorFromHue(color.GetHue()), min, 1 - max); 312 } 313 314 /// <summary> 315 /// Return the natural color for the given hue value 316 /// </summary> 317 /// <param name="hue">The hue value to convert</param> 318 /// <returns>A natural color</returns> 319 private static string GetNaturalColorFromHue(double hue) 320 { 321 if (hue < 60d) 322 { 323 return $"R{Math.Round(hue / 0.6d, 0)}"; 324 } 325 326 if (hue < 120d) 327 { 328 return $"Y{Math.Round((hue - 60d) / 0.6d, 0)}"; 329 } 330 331 if (hue < 180d) 332 { 333 return $"G{Math.Round((hue - 120d) / 0.6d, 0)}"; 334 } 335 336 if (hue < 240d) 337 { 338 return $"C{Math.Round((hue - 180d) / 0.6d, 0)}"; 339 } 340 341 if (hue < 300d) 342 { 343 return $"B{Math.Round((hue - 240d) / 0.6d, 0)}"; 344 } 345 346 return $"M{Math.Round((hue - 300d) / 0.6d, 0)}"; 347 } 348 349 private static readonly Dictionary<string, char> DefaultFormatTypes = new Dictionary<string, char>() 350 { 351 { "Re", 'b' }, // red byte 352 { "Gr", 'b' }, // green byte 353 { "Bl", 'b' }, // blue byte 354 { "Al", 'b' }, // alpha byte 355 { "Cy", 'p' }, // cyan percent 356 { "Ma", 'p' }, // magenta percent 357 { "Ye", 'p' }, // yellow percent 358 { "Bk", 'p' }, // black key percent 359 { "Hu", 'i' }, // hue int 360 { "Hn", 'i' }, // hue natural string 361 { "Si", 'p' }, // saturation (HSI) percent 362 { "Sl", 'p' }, // saturation (HSL) percent 363 { "Sb", 'p' }, // saturation (HSB) percent 364 { "Br", 'p' }, // brightness percent 365 { "In", 'p' }, // intensity percent 366 { "Ll", 'p' }, // lightness (HSL) percent 367 { "Va", 'p' }, // value percent 368 { "Wh", 'p' }, // whiteness percent 369 { "Bn", 'p' }, // blackness percent 370 { "Lc", 'p' }, // lightness (CIE) percent 371 { "Ca", 'p' }, // chromaticityA (CIELAB) percent 372 { "Cb", 'p' }, // chromaticityB (CIELAB) percent 373 { "Lo", 'p' }, // lightness (Oklab/Oklch) percent 374 { "Oa", 'p' }, // chromaticityA (Oklab) percent 375 { "Ob", 'p' }, // chromaticityB (Oklab) percent 376 { "Oc", 'p' }, // chroma (Oklch) percent 377 { "Oh", 'p' }, // hue angle (Oklch) percent 378 { "Xv", 'i' }, // X value int 379 { "Yv", 'i' }, // Y value int 380 { "Zv", 'i' }, // Z value int 381 { "Dr", 'i' }, // Decimal value (RGB) int 382 { "Dv", 'i' }, // Decimal value (BGR) int 383 384 // Removed Parameter Na, as the color name gets replaced separately, in localised way 385 // { "Na", 's' }, // Color name string 386 }; 387 388 public static string GetColorNameParameter() => "%Na"; 389 390 private static readonly Dictionary<char, string> FormatTypeToStringFormatters = new Dictionary<char, string>() 391 { 392 { 'b', "b" }, // 0..255 byte 393 { 'h', "x1" }, // hex lowercase one digit 394 { 'H', "X1" }, // hex uppercase one digit 395 { 'x', "x2" }, // hex lowercase two digits 396 { 'X', "X2" }, // hex uppercase two digits 397 { 'f', "0.##" }, // float with leading zero, 2 digits 398 { 'F', ".##" }, // float without leading zero, 2 digits 399 { 'p', "%" }, // percent value 400 { 'i', "i" }, // int value 401 { 's', "s" }, // string value 402 }; 403 404 public static string GetStringRepresentation(Color? color, string formatString) 405 { 406 if (color == null) 407 { 408 color = Color.Moccasin; // example color 409 } 410 411 // convert all %?? expressions to strings 412 int formatterPosition = formatString.IndexOf('%', 0); 413 while (formatterPosition != -1) 414 { 415 if (formatterPosition >= formatString.Length - 2) 416 { 417 // the formatter % was the last character, we are done 418 break; 419 } 420 421 char paramFormat; 422 string paramType = formatString.Substring(formatterPosition + 1, 2); 423 int paramCount = 3; 424 if (DefaultFormatTypes.TryGetValue(paramType, out char value)) 425 { 426 // check the next char, which could be a formatter 427 if (formatterPosition >= formatString.Length - 3) 428 { 429 // not enough characters, end of string, no formatter, use the default one 430 paramFormat = value; 431 paramCount = 2; 432 } 433 else 434 { 435 paramFormat = formatString[formatterPosition + 3]; 436 437 // check if it a valid formatter 438 if (!FormatTypeToStringFormatters.ContainsKey(paramFormat)) 439 { 440 paramFormat = value; 441 paramCount = 2; 442 } 443 } 444 445 formatString = string.Concat(formatString.AsSpan(0, formatterPosition), GetStringRepresentation(color.Value, paramFormat, paramType), formatString.AsSpan(formatterPosition + paramCount + 1)); 446 } 447 448 // search for the next occurrence of the formatter char 449 formatterPosition = formatString.IndexOf('%', formatterPosition + 1); 450 } 451 452 return formatString; 453 } 454 455 private static string GetStringRepresentation(Color color, char paramFormat, string paramType) 456 { 457 if (!DefaultFormatTypes.ContainsKey(paramType) || !FormatTypeToStringFormatters.ContainsKey(paramFormat)) 458 { 459 return string.Empty; 460 } 461 462 switch (paramType) 463 { 464 case "Re": return ColorByteFormatted(color.R, paramFormat); 465 case "Gr": return ColorByteFormatted(color.G, paramFormat); 466 case "Bl": return ColorByteFormatted(color.B, paramFormat); 467 case "Al": return ColorByteFormatted(color.A, paramFormat); 468 case "Cy": 469 var (cyan, _, _, _) = ConvertToCMYKColor(color); 470 cyan = Math.Round(cyan * 100); 471 return cyan.ToString(CultureInfo.InvariantCulture); 472 case "Ma": 473 var (_, magenta, _, _) = ConvertToCMYKColor(color); 474 magenta = Math.Round(magenta * 100); 475 return magenta.ToString(CultureInfo.InvariantCulture); 476 case "Ye": 477 var (_, _, yellow, _) = ConvertToCMYKColor(color); 478 yellow = Math.Round(yellow * 100); 479 return yellow.ToString(CultureInfo.InvariantCulture); 480 case "Bk": 481 var (_, _, _, blackKey) = ConvertToCMYKColor(color); 482 blackKey = Math.Round(blackKey * 100); 483 return blackKey.ToString(CultureInfo.InvariantCulture); 484 case "Hu": 485 var (hue, _, _) = ConvertToHSBColor(color); 486 hue = Math.Round(hue); 487 return hue.ToString(CultureInfo.InvariantCulture); 488 case "Hn": 489 var (hueNatural, _, _) = ConvertToNaturalColor(color); 490 return hueNatural; 491 case "Sb": 492 var (_, saturationB, _) = ConvertToHSBColor(color); 493 saturationB = Math.Round(saturationB * 100); 494 return saturationB.ToString(CultureInfo.InvariantCulture); 495 case "Si": 496 var (_, saturationI, _) = ConvertToHSIColor(color); 497 saturationI = Math.Round(saturationI * 100); 498 return saturationI.ToString(CultureInfo.InvariantCulture); 499 case "Sl": 500 var (_, saturationL, _) = ConvertToHSLColor(color); 501 saturationL = Math.Round(saturationL * 100); 502 return saturationL.ToString(CultureInfo.InvariantCulture); 503 case "Va": // value and brightness are the same values 504 case "Br": 505 var (_, _, brightness) = ConvertToHSBColor(color); 506 brightness = Math.Round(brightness * 100); 507 return brightness.ToString(CultureInfo.InvariantCulture); 508 case "In": 509 var (_, _, intensity) = ConvertToHSIColor(color); 510 intensity = Math.Round(intensity * 100); 511 return intensity.ToString(CultureInfo.InvariantCulture); 512 case "Ll": 513 var (_, _, lightnessL) = ConvertToHSLColor(color); 514 lightnessL = Math.Round(lightnessL * 100); 515 return lightnessL.ToString(CultureInfo.InvariantCulture); 516 case "Lc": 517 var (lightnessC, _, _) = ConvertToCIELABColor(color); 518 lightnessC = Math.Round(lightnessC, 2); 519 return lightnessC.ToString(CultureInfo.InvariantCulture); 520 case "Lo": 521 var (lightnessO, _, _) = ConvertToOklabColor(color); 522 lightnessO = Math.Round(lightnessO, 2); 523 return lightnessO.ToString(CultureInfo.InvariantCulture); 524 case "Wh": 525 var (_, whiteness, _) = ConvertToHWBColor(color); 526 whiteness = Math.Round(whiteness * 100); 527 return whiteness.ToString(CultureInfo.InvariantCulture); 528 case "Bn": 529 var (_, _, blackness) = ConvertToHWBColor(color); 530 blackness = Math.Round(blackness * 100); 531 return blackness.ToString(CultureInfo.InvariantCulture); 532 case "Ca": 533 var (_, chromaticityA, _) = ConvertToCIELABColor(color); 534 chromaticityA = Math.Round(chromaticityA, 2); 535 return chromaticityA.ToString(CultureInfo.InvariantCulture); 536 case "Cb": 537 var (_, _, chromaticityB) = ConvertToCIELABColor(color); 538 chromaticityB = Math.Round(chromaticityB, 2); 539 return chromaticityB.ToString(CultureInfo.InvariantCulture); 540 case "Oa": 541 var (_, chromaticityAOklab, _) = ConvertToOklabColor(color); 542 chromaticityAOklab = Math.Round(chromaticityAOklab, 2); 543 return chromaticityAOklab.ToString(CultureInfo.InvariantCulture); 544 case "Ob": 545 var (_, _, chromaticityBOklab) = ConvertToOklabColor(color); 546 chromaticityBOklab = Math.Round(chromaticityBOklab, 2); 547 return chromaticityBOklab.ToString(CultureInfo.InvariantCulture); 548 case "Oc": 549 var (_, chromaOklch, _) = ConvertToOklchColor(color); 550 chromaOklch = Math.Round(chromaOklch, 2); 551 return chromaOklch.ToString(CultureInfo.InvariantCulture); 552 case "Oh": 553 var (_, _, hueOklch) = ConvertToOklchColor(color); 554 hueOklch = Math.Round(hueOklch, 2); 555 return hueOklch.ToString(CultureInfo.InvariantCulture); 556 case "Xv": 557 var (x, _, _) = ConvertToCIEXYZColor(color); 558 x = Math.Round(x * 100, 4); 559 return x.ToString(CultureInfo.InvariantCulture); 560 case "Yv": 561 var (_, y, _) = ConvertToCIEXYZColor(color); 562 y = Math.Round(y * 100, 4); 563 return y.ToString(CultureInfo.InvariantCulture); 564 case "Zv": 565 var (_, _, z) = ConvertToCIEXYZColor(color); 566 z = Math.Round(z * 100, 4); 567 return z.ToString(CultureInfo.InvariantCulture); 568 case "Dr": 569 return ((color.R * 65536) + (color.G * 256) + color.B).ToString(CultureInfo.InvariantCulture); 570 case "Dv": 571 return (color.R + (color.G * 256) + (color.B * 65536)).ToString(CultureInfo.InvariantCulture); 572 573 // Removed Parameter Na, as the color name gets replaced separately, in localised way 574 // case "Na": 575 // return ColorNameHelper.GetColorName(color); 576 default: return string.Empty; 577 } 578 } 579 580 private static string ColorByteFormatted(byte colorByteValue, char paramFormat) 581 { 582 switch (paramFormat) 583 { 584 case 'b': return colorByteValue.ToString(CultureInfo.InvariantCulture); 585 case 'h': 586 case 'H': 587 return (colorByteValue / 16).ToString(FormatTypeToStringFormatters[paramFormat], CultureInfo.InvariantCulture); 588 case 'x': 589 case 'X': 590 return colorByteValue.ToString(FormatTypeToStringFormatters[paramFormat], CultureInfo.InvariantCulture); 591 case 'f': 592 case 'F': 593 return (colorByteValue / 255d).ToString(FormatTypeToStringFormatters[paramFormat], CultureInfo.InvariantCulture); 594 default: return colorByteValue.ToString(CultureInfo.InvariantCulture); 595 } 596 } 597 598 public static string GetDefaultFormat(string formatName) 599 { 600 switch (formatName) 601 { 602 case "HEX": return "%Rex%Grx%Blx"; 603 case "RGB": return "rgb(%Re, %Gr, %Bl)"; 604 case "HSL": return "hsl(%Hu, %Sl%, %Ll%)"; 605 case "HSV": return "hsv(%Hu, %Sb%, %Va%)"; 606 case "CMYK": return "cmyk(%Cy%, %Ma%, %Ye%, %Bk%)"; 607 case "HSB": return "hsb(%Hu, %Sb%, %Br%)"; 608 case "HSI": return "hsi(%Hu, %Si%, %In%)"; 609 case "HWB": return "hwb(%Hu, %Wh%, %Bn%)"; 610 case "NCol": return "%Hn, %Wh%, %Bn%"; 611 case "CIEXYZ": return "XYZ(%Xv, %Yv, %Zv)"; 612 case "CIELAB": return "CIELab(%Lc, %Ca, %Cb)"; 613 case "Oklab": return "oklab(%Lo, %Oa, %Ob)"; 614 case "Oklch": return "oklch(%Lo, %Oc, %Oh)"; 615 case "VEC4": return "(%Reff, %Grff, %Blff, 1f)"; 616 case "Decimal": return "%Dv"; 617 case "HEX Int": return "0xFF%ReX%GrX%BlX"; 618 default: return string.Empty; 619 } 620 } 621 } 622 }