Private/Colors.psm1

using namespace System
class Color : Object {
  static [rgb] $Red = [rgb]::new(255, 0, 0)
  static [rgb] $DarkRed = [rgb]::new(128, 0, 0)
  static [rgb] $Green = [rgb]::new(0, 255, 0)
  static [rgb] $DarkGreen = [rgb]::new(0, 128, 0)
  static [rgb] $Blue = [rgb]::new(0, 0, 255)
  static [rgb] $DarkBlue = [rgb]::new(0, 0, 128)
  static [rgb] $White = [rgb]::new(255, 255, 255)
  static [rgb] $Black = [rgb]::new(0, 0, 0)
  static [rgb] $Yellow = [rgb]::new(255, 255, 0)
  static [rgb] $DarkGray = [rgb]::new(128, 128, 128)
  static [rgb] $Gray = [rgb]::new(192, 192, 192)
  static [rgb] $LightGray = [rgb]::new(238, 237, 240)
  static [rgb] $Cyan = [rgb]::new(0, 255, 255)
  static [rgb] $DarkCyan = [rgb]::new(0, 128, 128)
  static [rgb] $Magenta = [rgb]::new(255, 0, 255)
  static [rgb] $PSBlue = [rgb]::new(1, 36, 86)
  static [rgb] $AliceBlue = [rgb]::new(240, 248, 255)
  static [rgb] $AntiqueWhite = [rgb]::new(250, 235, 215)
  static [rgb] $AquaMarine = [rgb]::new(127, 255, 212)
  static [rgb] $Azure = [rgb]::new(240, 255, 255)
  static [rgb] $Beige = [rgb]::new(245, 245, 220)
  static [rgb] $Bisque = [rgb]::new(255, 228, 196)
  static [rgb] $BlanchedAlmond = [rgb]::new(255, 235, 205)
  static [rgb] $BlueViolet = [rgb]::new(138, 43, 226)
  static [rgb] $Brown = [rgb]::new(165, 42, 42)
  static [rgb] $Burlywood = [rgb]::new(222, 184, 135)
  static [rgb] $CadetBlue = [rgb]::new(95, 158, 160)
  static [rgb] $Chartreuse = [rgb]::new(127, 255, 0)
  static [rgb] $Chocolate = [rgb]::new(210, 105, 30)
  static [rgb] $Coral = [rgb]::new(255, 127, 80)
  static [rgb] $CornflowerBlue = [rgb]::new(100, 149, 237)
  static [rgb] $CornSilk = [rgb]::new(255, 248, 220)
  static [rgb] $Crimson = [rgb]::new(220, 20, 60)
  static [rgb] $DarkGoldenrod = [rgb]::new(184, 134, 11)
  static [rgb] $DarkKhaki = [rgb]::new(189, 183, 107)
  static [rgb] $DarkMagenta = [rgb]::new(139, 0, 139)
  static [rgb] $DarkOliveGreen = [rgb]::new(85, 107, 47)
  static [rgb] $DarkOrange = [rgb]::new(255, 140, 0)
  static [rgb] $DarkOrchid = [rgb]::new(153, 50, 204)
  static [rgb] $DarkSalmon = [rgb]::new(233, 150, 122)
  static [rgb] $DarkSeaGreen = [rgb]::new(143, 188, 143)
  static [rgb] $DarkSlateBlue = [rgb]::new(72, 61, 139)
  static [rgb] $DarkSlateGray = [rgb]::new(47, 79, 79)
  static [rgb] $DarkTurquoise = [rgb]::new(0, 206, 209)
  static [rgb] $DarkViolet = [rgb]::new(148, 0, 211)
  static [rgb] $DeepPink = [rgb]::new(255, 20, 147)
  static [rgb] $DeepSkyBlue = [rgb]::new(0, 191, 255)
  static [rgb] $DimGray = [rgb]::new(105, 105, 105)
  static [rgb] $DodgerBlue = [rgb]::new(30, 144, 255)
  static [rgb] $FireBrick = [rgb]::new(178, 34, 34)
  static [rgb] $FloralWhite = [rgb]::new(255, 250, 240)
  static [rgb] $ForestGreen = [rgb]::new(34, 139, 34)
  static [rgb] $GainsBoro = [rgb]::new(220, 220, 220)
  static [rgb] $GhostWhite = [rgb]::new(248, 248, 255)
  static [rgb] $Gold = [rgb]::new(255, 215, 0)
  static [rgb] $Goldenrod = [rgb]::new(218, 165, 32)
  static [rgb] $GreenYellow = [rgb]::new(173, 255, 47)
  static [rgb] $HoneyDew = [rgb]::new(240, 255, 240)
  static [rgb] $HotPink = [rgb]::new(255, 105, 180)
  static [rgb] $IndianRed = [rgb]::new(205, 92, 92)
  static [rgb] $Indigo = [rgb]::new(75, 0, 130)
  static [rgb] $Ivory = [rgb]::new(255, 255, 240)
  static [rgb] $Khaki = [rgb]::new(240, 230, 140)
  static [rgb] $Lavender = [rgb]::new(230, 230, 250)
  static [rgb] $LavenderBlush = [rgb]::new(255, 240, 245)
  static [rgb] $LawnGreen = [rgb]::new(124, 252, 0)
  static [rgb] $LemonChiffon = [rgb]::new(255, 250, 205)
  static [rgb] $LightBlue = [rgb]::new(173, 216, 230)
  static [rgb] $LightCoral = [rgb]::new(240, 128, 128)
  static [rgb] $LightCyan = [rgb]::new(224, 255, 255)
  static [rgb] $LightGoldenrodYellow = [rgb]::new(250, 250, 210)
  static [rgb] $LightPink = [rgb]::new(255, 182, 193)
  static [rgb] $LightSalmon = [rgb]::new(255, 160, 122)
  static [rgb] $LightSeaGreen = [rgb]::new(32, 178, 170)
  static [rgb] $LightSkyBlue = [rgb]::new(135, 206, 250)
  static [rgb] $LightSlateGray = [rgb]::new(119, 136, 153)
  static [rgb] $LightSteelBlue = [rgb]::new(176, 196, 222)
  static [rgb] $LightYellow = [rgb]::new(255, 255, 224)
  static [rgb] $LimeGreen = [rgb]::new(50, 205, 50)
  static [rgb] $Linen = [rgb]::new(250, 240, 230)
  static [rgb] $MediumAquaMarine = [rgb]::new(102, 205, 170)
  static [rgb] $MediumOrchid = [rgb]::new(186, 85, 211)
  static [rgb] $MediumPurple = [rgb]::new(147, 112, 219)
  static [rgb] $MediumSeaGreen = [rgb]::new(60, 179, 113)
  static [rgb] $MediumSlateBlue = [rgb]::new(123, 104, 238)
  static [rgb] $MediumSpringGreen = [rgb]::new(0, 250, 154)
  static [rgb] $MediumTurquoise = [rgb]::new(72, 209, 204)
  static [rgb] $MediumVioletRed = [rgb]::new(199, 21, 133)
  static [rgb] $MidnightBlue = [rgb]::new(25, 25, 112)
  static [rgb] $MintCream = [rgb]::new(245, 255, 250)
  static [rgb] $MistyRose = [rgb]::new(255, 228, 225)
  static [rgb] $Moccasin = [rgb]::new(255, 228, 181)
  static [rgb] $NavajoWhite = [rgb]::new(255, 222, 173)
  static [rgb] $OldLace = [rgb]::new(253, 245, 230)
  static [rgb] $Olive = [rgb]::new(128, 128, 0)
  static [rgb] $OliveDrab = [rgb]::new(107, 142, 35)
  static [rgb] $Orange = [rgb]::new(255, 165, 0)
  static [rgb] $OrangeRed = [rgb]::new(255, 69, 0)
  static [rgb] $Orchid = [rgb]::new(218, 112, 214)
  static [rgb] $PaleGoldenrod = [rgb]::new(238, 232, 170)
  static [rgb] $PaleGreen = [rgb]::new(152, 251, 152)
  static [rgb] $PaleTurquoise = [rgb]::new(175, 238, 238)
  static [rgb] $PaleVioletRed = [rgb]::new(219, 112, 147)
  static [rgb] $PapayaWhip = [rgb]::new(255, 239, 213)
  static [rgb] $PeachPuff = [rgb]::new(255, 218, 185)
  static [rgb] $Peru = [rgb]::new(205, 133, 63)
  static [rgb] $Pink = [rgb]::new(255, 192, 203)
  static [rgb] $Plum = [rgb]::new(221, 160, 221)
  static [rgb] $PowderBlue = [rgb]::new(176, 224, 230)
  static [rgb] $Purple = [rgb]::new(128, 0, 128)
  static [rgb] $RosyBrown = [rgb]::new(188, 143, 143)
  static [rgb] $RoyalBlue = [rgb]::new(65, 105, 225)
  static [rgb] $SaddleBrown = [rgb]::new(139, 69, 19)
  static [rgb] $Salmon = [rgb]::new(250, 128, 114)
  static [rgb] $SandyBrown = [rgb]::new(244, 164, 96)
  static [rgb] $SeaGreen = [rgb]::new(46, 139, 87)
  static [rgb] $SeaShell = [rgb]::new(255, 245, 238)
  static [rgb] $Sienna = [rgb]::new(160, 82, 45)
  static [rgb] $SkyBlue = [rgb]::new(135, 206, 235)
  static [rgb] $SlateBlue = [rgb]::new(106, 90, 205)
  static [rgb] $SlateGray = [rgb]::new(112, 128, 144)
  static [rgb] $Snow = [rgb]::new(255, 250, 250)
  static [rgb] $SpringGreen = [rgb]::new(0, 255, 127)
  static [rgb] $SteelBlue = [rgb]::new(70, 130, 180)
  static [rgb] $Tan = [rgb]::new(210, 180, 140)
  static [rgb] $Thistle = [rgb]::new(216, 191, 216)
  static [rgb] $Tomato = [rgb]::new(255, 99, 71)
  static [rgb] $Turquoise = [rgb]::new(64, 224, 208)
  static [rgb] $Violet = [rgb]::new(238, 130, 238)
  static [rgb] $Wheat = [rgb]::new(245, 222, 179)
  static [rgb] $WhiteSmoke = [rgb]::new(245, 245, 245)
  static [rgb] $YellowGreen = [rgb]::new(154, 205, 50)
  Color([string]$Name) {
    if ($null -eq [Color]::$Name) {
      throw [System.Management.Automation.ValidationMetadataException]::new("Please provide a valid color name. see [ConsoleWriter]::Colors for a list of valid colors")
    }
  }
}
class RGB {
  [ValidateRange(0, 255)]
  [int]$Red
  [ValidateRange(0, 255)]
  [int]$Green
  [ValidateRange(0, 255)]
  [int]$Blue
  RGB() {}
  RGB([string]$Name) {
    $IsValid = [color].GetProperties().Name.Contains($Name)
    if (!$IsValid) { throw [System.InvalidCastException]::new("Color name '$Name' is not valid. See: [color].GetProperties().Name") }
    [RGB]$c = [color]::$Name
    $this.Red = $c.Red
    $this.Green = $c.Green
    $this.Blue = $c.Blue
  }
  RGB([int]$r, [int]$g, [int]$b) {
    $this.Red = $r
    $this.Green = $g
    $this.Blue = $b
  }
  [tuple[int, int, int]] ToHsv() {
    return $this.ToHsv($this.Red, $this.Green, $this.Blue)
  }
  [tuple[int, int, int]] ToHsv([rgb]$RGB) {
    return $this.ToHsv($RGB.Red, $RGB.Green, $RGB.Blue)
  }
  [tuple[int, int, int]] ToHsv([int]$Red, [int]$Green, [int]$Blue) {
    $null = [RGB]::new($Red, $Green, $Blue) # for validation
    $redPercent = $Red / 255.0
    $greenPercent = $Green / 255.0
    $bluePercent = $Blue / 255.0

    $max = [Math]::Max([Math]::Max($redPercent, $greenPercent), $bluePercent)
    $min = [Math]::Min([Math]::Min($redPercent, $greenPercent), $bluePercent)
    $delta = $max - $min

    $saturation = 0
    $value = 0
    $hue = 0

    if ($delta -eq 0) {
      $hue = 0
    } elseif ($max -eq $redPercent) {
      $hue = 60 * ((($greenPercent - $bluePercent) / $delta) % 6)
    } elseif ($max -eq $greenPercent) {
      $hue = 60 * ((($bluePercent - $redPercent) / $delta) + 2)
    } elseif ($max -eq $bluePercent) {
      $hue = 60 * ((($redPercent - $greenPercent) / $delta) + 4)
    }
    if ($hue -lt 0) { $hue = 360 + $hue }

    if ($max -eq 0) {
      $saturation = 0
    } else {
      $saturation = $delta / $max * 100
    }
    $value = $max * 100

    return [tuple]::Create([int]$hue, [int]$saturation, [int]$value)
  }
  [RGB] FromHsl([int]$Hue, [int]$Saturation, [int]$Lightness) {
    $huePercent = $Hue / 360.0
    $saturationPercent = $Saturation / 100.0
    $lightnessPercent = $Lightness / 100.0
    ($r, $g, $b) = if ($saturationPercent -eq 0) {
      $lightnessPercent,
      $lightnessPercent,
      $lightnessPercent
    } else {
      $q = ($lightnessPercent -lt 0.5) ? ($lightnessPercent * (1 + $saturationPercent)) : ($lightnessPercent + $saturationPercent - ($lightnessPercent * $saturationPercent))
      $p = 2 * $lightnessPercent - $q

      [Color]::FromPQT($p, $q, ($huePercent + (1 / 3))),
      [Color]::FromPQT($p, $q, $huePercent),
      [Color]::FromPQT($p, $q, ($huePercent - (1 / 3)))
    }
    return [RGB]::new([int]($r * 255), [int]($g * 255), [int]($b * 255))
  }
  static [int] FromPQT([double]$P, [double]$Q, [double]$T) {
    if ($T -lt 0) { $T += 1 }
    if ($T -gt 1) { $T -= 1 }

    if ($T -lt (1 / 6)) { return $P + ($Q - $P) * 6 * $T }

    if ($T -lt (1 / 2)) { return $Q }
    if ($T -lt (2 / 3)) { return $P + ($Q - $P) * (2 / 3 - $T) * 6 }

    return $P
  }
  [int] GetLightness([int]$Red, [int]$Green, [int]$Blue) {
    $redPercent = $Red / 255.0
    $greenPercent = $Green / 255.0
    $bluePercent = $Blue / 255.0
    $max = [Math]::Max([Math]::Max($redPercent, $greenPercent), $bluePercent)
    $min = [Math]::Min([Math]::Min($redPercent, $greenPercent), $bluePercent)

    return ($max + $min) / 2
  }
  [string] GetCategory([int]$Hue, [int]$Saturation, [int]$Value) {
    $categories = @{
      "02 Red"    = @(0..20 + 350..360)
      "03 Orange" = @(21..45)
      "04 Yellow" = @(46..60)
      "05 Green"  = @(61..108)
      "06 Green2" = @(109..150)
      "07 Cyan"   = @(151..190)
      "08 Blue"   = @(191..220)
      "09 Blue2"  = @(221..240)
      "10 Purple" = @(241..280)
      "11 Pink1"  = @(281..300)
      "12 Pink"   = @(301..350)
    }

    if ($Saturation -lt 15) {
      if ($Value -lt 40) {
        return "00 Grey"
      }
      return "00 GreyZMud"
    }
    $res = @()
    foreach ($category in $categories.GetEnumerator()) {
      if ($Hue -in $category.Value) {
        $cat = $category.Key
        if ($Saturation -lt 2) {
          $cat = $cat + "ZMud"
        }
        $res += $cat
      }
    }
    return $res
  }
  [string] ToString() {
    return [color].GetProperties().Name.Where({ [color]::$_ -eq $this })
  }
}