python (3.11.7)
       1  from math import sqrt
       2  from functools import lru_cache
       3  from typing import Sequence, Tuple, TYPE_CHECKING
       4  
       5  from .color_triplet import ColorTriplet
       6  
       7  if TYPE_CHECKING:
       8      from pip._vendor.rich.table import Table
       9  
      10  
      11  class ESC[4;38;5;81mPalette:
      12      """A palette of available colors."""
      13  
      14      def __init__(self, colors: Sequence[Tuple[int, int, int]]):
      15          self._colors = colors
      16  
      17      def __getitem__(self, number: int) -> ColorTriplet:
      18          return ColorTriplet(*self._colors[number])
      19  
      20      def __rich__(self) -> "Table":
      21          from pip._vendor.rich.color import Color
      22          from pip._vendor.rich.style import Style
      23          from pip._vendor.rich.text import Text
      24          from pip._vendor.rich.table import Table
      25  
      26          table = Table(
      27              "index",
      28              "RGB",
      29              "Color",
      30              title="Palette",
      31              caption=f"{len(self._colors)} colors",
      32              highlight=True,
      33              caption_justify="right",
      34          )
      35          for index, color in enumerate(self._colors):
      36              table.add_row(
      37                  str(index),
      38                  repr(color),
      39                  Text(" " * 16, style=Style(bgcolor=Color.from_rgb(*color))),
      40              )
      41          return table
      42  
      43      # This is somewhat inefficient and needs caching
      44      @lru_cache(maxsize=1024)
      45      def match(self, color: Tuple[int, int, int]) -> int:
      46          """Find a color from a palette that most closely matches a given color.
      47  
      48          Args:
      49              color (Tuple[int, int, int]): RGB components in range 0 > 255.
      50  
      51          Returns:
      52              int: Index of closes matching color.
      53          """
      54          red1, green1, blue1 = color
      55          _sqrt = sqrt
      56          get_color = self._colors.__getitem__
      57  
      58          def get_color_distance(index: int) -> float:
      59              """Get the distance to a color."""
      60              red2, green2, blue2 = get_color(index)
      61              red_mean = (red1 + red2) // 2
      62              red = red1 - red2
      63              green = green1 - green2
      64              blue = blue1 - blue2
      65              return _sqrt(
      66                  (((512 + red_mean) * red * red) >> 8)
      67                  + 4 * green * green
      68                  + (((767 - red_mean) * blue * blue) >> 8)
      69              )
      70  
      71          min_index = min(range(len(self._colors)), key=get_color_distance)
      72          return min_index
      73  
      74  
      75  if __name__ == "__main__":  # pragma: no cover
      76      import colorsys
      77      from typing import Iterable
      78      from pip._vendor.rich.color import Color
      79      from pip._vendor.rich.console import Console, ConsoleOptions
      80      from pip._vendor.rich.segment import Segment
      81      from pip._vendor.rich.style import Style
      82  
      83      class ESC[4;38;5;81mColorBox:
      84          def __rich_console__(
      85              self, console: Console, options: ConsoleOptions
      86          ) -> Iterable[Segment]:
      87              height = console.size.height - 3
      88              for y in range(0, height):
      89                  for x in range(options.max_width):
      90                      h = x / options.max_width
      91                      l = y / (height + 1)
      92                      r1, g1, b1 = colorsys.hls_to_rgb(h, l, 1.0)
      93                      r2, g2, b2 = colorsys.hls_to_rgb(h, l + (1 / height / 2), 1.0)
      94                      bgcolor = Color.from_rgb(r1 * 255, g1 * 255, b1 * 255)
      95                      color = Color.from_rgb(r2 * 255, g2 * 255, b2 * 255)
      96                      yield Segment("▄", Style(color=color, bgcolor=bgcolor))
      97                  yield Segment.line()
      98  
      99      console = Console()
     100      console.print(ColorBox())