code: ﻿// Copyright (c) Microsoft Corporation
  // The Microsoft Corporation licenses this file to you under the MIT license.
  // See the LICENSE file in the project root for more information.
  
  using System;
  using System.Drawing;
  using ColorPicker.Properties;
  
  namespace ColorPicker.Helpers
  {
      public static class ColorNameHelper
      {
          public const float DBLEPSILON = 2.2204460492503131e-16f;
  
          // For the purposes of naming colors, there are four steps that we go through.
          //
          // 1. For numerical ease, we convert the HSL values from the range [0, 1]
          // to the range [0, 255].
          //
          // 2. If luminosity is sufficiently high or low (> 240 or < 20), or if
          // saturation is sufficiently low (<= 20), then we declare that we're in the
          // achromatic range.  In this case, we return either white, black, or three
          // different shades of gray (depending on luminosity).
          //
          // 3. If we do have a chromatic color, the first thing we need to determine
          // about it is what the hue limits are for its saturation value - at different
          // levels of saturation, we have different hue values that we'll consider the
          // boundaries for different classes of named colors.  The hue limits for various
          // saturation values are as below.
          //
          // The numbers correspond to the following color buckets, with 0 meaning that
          // that bucket does not apply to the given saturation value:
          //
          // 1 - coral, 2 - red, 3 - orange, 4 - brown, 5 - tan, 6 - gold, 7 - yellow, 8 - olive green (with brown),
          // 9 - olive green (with green) 10 - lime green, 11 - green - 12 - bright green 13 - teal, 14 - aqua,
          // 15 - turquoise, 16 - pale blue, 17 - blue, 18 - blue-gray, 19 - indigo, 20 - purple, 21 - pink, 22 - brown, 23 - red
          private static int[] hueLimitsForSatLevel1 = // Sat: 20-75
          {
          // 1,  2,  3,  4,  5,  6,  7,  8,  9,  10,  11,  12,  13,  14,  15,  16,  17,  18,  19,  20,  21,  22,  23
              8,  0,  0, 44,  0,  0,  0, 63,  0,   0, 122,   0, 134,   0,   0,   0,   0, 166, 176, 241,   0, 256,   0,
          };
  
          private static int[] hueLimitsForSatLevel2 = // Sat: 75-115
          {
          // 1,  2,  3,  4,  5,  6,  7,  8,  9,  10,  11,  12,  13,  14,  15,  16,  17,  18,  19,  20,  21,  22,  23
              0, 10,  0, 32, 46,  0,  0,  0, 61,   0, 106,   0, 136, 144,   0,   0,   0, 158, 166, 241,   0,   0, 256,
          };
  
          private static int[] hueLimitsForSatLevel3 = // Sat: 115-150
          {
          // 1,  2,  3,  4,  5,  6,  7,  8,  9,  10,  11,  12,  13,  14,  15,  16,  17,  18,  19,  20,  21,  22,  23
              0,  8,  0,  0, 39, 46,  0,  0,  0,  71, 120,   0, 131, 144,   0,   0, 163,   0, 177, 211, 249,   0, 256,
          };
  
          private static int[] hueLimitsForSatLevel4 = // Sat: 150-240
          {
          // 1,  2,  3,  4,  5,  6,  7,  8,  9,  10,  11,  12,  13,  14,  15,  16,  17,  18,  19,  20,  21,  22,  23
              0, 11, 26,  0,  0, 38, 45,  0,  0,  56, 100, 121, 129,   0, 140,   0, 180,   0,   0, 224, 241,   0, 256,
          };
  
          private static int[] hueLimitsForSatLevel5 = // Sat: 240-255
          {
          // 1,  2,  3,  4,  5,  6,  7,  8,  9,  10,  11,  12,  13,  14,  15,  16,  17,  18,  19,  20,  21,  22,  23
              0, 13, 27,  0,  0, 36, 45,  0,  0,  59, 118,   0, 127, 136, 142,   0, 185,   0,   0, 216, 239,   0, 256,
          };
  
          // 4. Once we have the color bucket, next we have three sub-buckets that we need to worry about,
          // corresponding to three different levels of luminosity.  For example, if we're in the "blue" bucket,
          // that might correspond to light blue, blue, or dark blue, depending on luminosity.
          // For each bucket, the luminosity cutoffs for the purposes of discerning between light, mid, and dark colors
          // are different, so we define luminosity limits for low and high luminosity for each bucket, as follows:
          private static int[] lumLimitsForHueIndexLow =
          {
          // 1,   2,   3,   4,   5,   6,   7,    8,  9,    10, 11,  12,  13,  14,   15, 16,  17,  18,  19,  20,  21,  22,  23
              130, 100, 115, 100, 100, 100, 110,  75, 100,  90, 100, 100, 100, 100,  80, 100, 100, 100, 100, 100, 100, 100, 100,
          };
  
          private static int[] lumLimitsForHueIndexHigh =
          {
          // 1,   2,   3,   4,   5,   6,   7,   8,   9,   10,  11,  12,  13,  14,  15,  16,  17,  18,  19,  20,  21,  22,  23
              170, 170, 170, 155, 170, 170, 170, 170, 170, 115, 170, 170, 170, 170, 170, 170, 170, 170, 150, 150, 170, 140, 165,
          };
  
          // 5. Finally, once we have a luminosity sub-bucket in the saturation color bucket, we have everything we need
          // to retrieve a name.  For each of the 23 buckets, we have names associated with light, mid, and dark variations
          // of that color, which are defined as follows:
          private static string[] colorNamesLight =
          {
              Resources.TEXT_COLOR_CORAL,
              Resources.TEXT_COLOR_ROSE,
              Resources.TEXT_COLOR_LIGHTORANGE,
              Resources.TEXT_COLOR_TAN,
              Resources.TEXT_COLOR_TAN,
              Resources.TEXT_COLOR_LIGHTYELLOW,
              Resources.TEXT_COLOR_LIGHTYELLOW,
              Resources.TEXT_COLOR_TAN,
              Resources.TEXT_COLOR_LIGHTGREEN,
              Resources.TEXT_COLOR_LIME,
              Resources.TEXT_COLOR_LIGHTGREEN,
              Resources.TEXT_COLOR_LIGHTGREEN,
              Resources.TEXT_COLOR_AQUA,
              Resources.TEXT_COLOR_SKYBLUE,
              Resources.TEXT_COLOR_LIGHTTURQUOISE,
              Resources.TEXT_COLOR_PALEBLUE,
              Resources.TEXT_COLOR_LIGHTBLUE,
              Resources.TEXT_COLOR_ICEBLUE,
              Resources.TEXT_COLOR_PERIWINKLE,
              Resources.TEXT_COLOR_LAVENDER,
              Resources.TEXT_COLOR_PINK,
              Resources.TEXT_COLOR_TAN,
              Resources.TEXT_COLOR_ROSE,
          };
  
          private static string[] colorNamesMid =
          {
              Resources.TEXT_COLOR_CORAL,
              Resources.TEXT_COLOR_RED,
              Resources.TEXT_COLOR_ORANGE,
              Resources.TEXT_COLOR_BROWN,
              Resources.TEXT_COLOR_TAN,
              Resources.TEXT_COLOR_GOLD,
              Resources.TEXT_COLOR_YELLOW,
              Resources.TEXT_COLOR_OLIVEGREEN,
              Resources.TEXT_COLOR_OLIVEGREEN,
              Resources.TEXT_COLOR_GREEN,
              Resources.TEXT_COLOR_GREEN,
              Resources.TEXT_COLOR_BRIGHTGREEN,
              Resources.TEXT_COLOR_TEAL,
              Resources.TEXT_COLOR_AQUA,
              Resources.TEXT_COLOR_TURQUOISE,
              Resources.TEXT_COLOR_PALEBLUE,
              Resources.TEXT_COLOR_BLUE,
              Resources.TEXT_COLOR_BLUEGRAY,
              Resources.TEXT_COLOR_INDIGO,
              Resources.TEXT_COLOR_PURPLE,
              Resources.TEXT_COLOR_PINK,
              Resources.TEXT_COLOR_BROWN,
              Resources.TEXT_COLOR_RED,
          };
  
          private static string[] colorNamesDark =
          {
              Resources.TEXT_COLOR_BROWN,
              Resources.TEXT_COLOR_DARKRED,
              Resources.TEXT_COLOR_BROWN,
              Resources.TEXT_COLOR_BROWN,
              Resources.TEXT_COLOR_BROWN,
              Resources.TEXT_COLOR_DARKYELLOW,
              Resources.TEXT_COLOR_DARKYELLOW,
              Resources.TEXT_COLOR_BROWN,
              Resources.TEXT_COLOR_DARKGREEN,
              Resources.TEXT_COLOR_DARKGREEN,
              Resources.TEXT_COLOR_DARKGREEN,
              Resources.TEXT_COLOR_DARKGREEN,
              Resources.TEXT_COLOR_DARKTEAL,
              Resources.TEXT_COLOR_DARKTEAL,
              Resources.TEXT_COLOR_DARKTEAL,
              Resources.TEXT_COLOR_DARKBLUE,
              Resources.TEXT_COLOR_DARKBLUE,
              Resources.TEXT_COLOR_BLUEGRAY,
              Resources.TEXT_COLOR_INDIGO,
              Resources.TEXT_COLOR_DARKPURPLE,
              Resources.TEXT_COLOR_PLUM,
              Resources.TEXT_COLOR_BROWN,
              Resources.TEXT_COLOR_DARKRED,
          };
  
          public static string GetColorName(Color color)
          {
              var (hue, sat, lum) = ColorHelper.ConvertToHSLColor(color);
  
              hue = (hue == 0 ? 0 : hue / 360) * 255; // this implementation is using normalization to 0-255 instead of 0-360°
              sat = sat * 255;
              lum = lum * 255;
  
              // First, if we're in the achromatic state, return the appropriate achromatic color name.
              if (lum > 240)
              {
                  return Resources.TEXT_COLOR_WHITE;
              }
              else if (lum < 20)
              {
                  return Resources.TEXT_COLOR_BLACK;
              }
  
              if (sat <= 20)
              {
                  if (lum > 170)
                  {
                      return Resources.TEXT_COLOR_LIGHTGRAY;
                  }
                  else if (lum > 100)
                  {
                      return Resources.TEXT_COLOR_GRAY;
                  }
                  else
                  {
                      return Resources.TEXT_COLOR_DARKGRAY;
                  }
              }
  
              // If we have a chromatic color, we need to first get the hue limits for the saturation value.
              int[] pHueLimits;
              if (sat > 20 && sat <= 75)
              {
                  pHueLimits = hueLimitsForSatLevel1;
              }
              else if (sat > 75 && sat <= 115)
              {
                  pHueLimits = hueLimitsForSatLevel2;
              }
              else if (sat > 115 && sat <= 150)
              {
                  pHueLimits = hueLimitsForSatLevel3;
              }
              else if (sat > 150 && sat <= 240)
              {
                  pHueLimits = hueLimitsForSatLevel4;
              }
              else
              {
                  pHueLimits = hueLimitsForSatLevel5;
              }
  
              // Now that we have that, we can get the color index, which represents which
              // of the 23 buckets we're located in.
              int colorIndex = -1;
              for (int i = 0; i < colorNamesMid.Length; ++i)
              {
                  if (hue < pHueLimits[i])
                  {
                      colorIndex = i;
                      break;
                  }
              }
  
              // Assuming we got a color index (and we always should get one), then next we need to
              // figure out which luminosity sub-bucket we're located in.
              // Once we have that, we'll return the color name from the appropriate array.
              if (colorIndex != -1)
              {
                  if (lum > lumLimitsForHueIndexHigh[colorIndex])
                  {
                      return colorNamesLight[colorIndex];
                  }
                  else if (lum < lumLimitsForHueIndexLow[colorIndex])
                  {
                      return colorNamesDark[colorIndex];
                  }
                  else
                  {
                      return colorNamesMid[colorIndex];
                  }
              }
  
              return string.Empty;
          }
  
          public static bool AreClose(double a, double b)
          {
              return (float)Math.Abs(a - b) <= DBLEPSILON * (float)Math.Abs(a);
          }
      }
  }
  