/ src / common / ManagedCommon / ColorFormatHelper.cs
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  }