File indexing completed on 2025-01-05 04:00:26

0001 #!/usr/bin/env python3
0002 
0003 import sys
0004 import os
0005 from functools import reduce
0006 import argparse
0007 sys.path.insert(0, os.path.join(
0008     os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
0009     "lib"
0010 ))
0011 from lottie.parsers.svg.importer import parse_color
0012 from lottie.parsers.svg.svgdata import color_table
0013 from lottie import NVector
0014 from lottie.utils.color import ColorMode, Color
0015 
0016 
0017 class ColorCompare:
0018     def __init__(self, name):
0019         self.name = name
0020         rgb = Color(*parse_color(name)[:3])
0021         xyz = rgb.converted(ColorMode.XYZ)
0022         self.representations = {
0023             "RGB": rgb,
0024             "HSV": rgb.converted(ColorMode.HSV),
0025             "XYZ": xyz,
0026             "LAB": xyz.converted(ColorMode.LAB),
0027             "LUV": xyz.converted(ColorMode.LUV),
0028             "LCH_uv": xyz.converted(ColorMode.LCH_uv),
0029         }
0030 
0031     def __repr__(self):
0032         return "<ColorCompare %s>" % self.name
0033 
0034     def dist(self, oth, space):
0035         return (self.representations[space] - oth.representations[space]).length
0036 
0037     def rep(self, space):
0038         return self.representations[space]
0039 
0040     def ansi_str(self, length):
0041         return color_str(self.rep("RGB"), length)
0042 
0043 
0044 class SimilarColor:
0045     def __init__(self, name, rgbvec, space):
0046         self.name = name
0047         self.rgb = Color(*rgbvec[:3])
0048         self.vec = self.rgb.converted(ColorMode[space])
0049         self.space = space
0050 
0051     def dist(self, colorcompare):
0052         return (self.vec - colorcompare.rep(self.space)).length
0053 
0054     def ansi_str(self, length):
0055         return color_str(self.rgb, length)
0056 
0057 
0058 def color_str(rgbvec, length):
0059     comps = [
0060         str(int(round(c * 255)))
0061         for c in rgbvec[:3]
0062     ]
0063     return "\x1b[48;2;%sm%s\x1b[m" % (";".join(comps), " " * length)
0064 
0065 
0066 def table_row(name, items=[""], pad=" "):
0067     print("|".join([name.ljust(titlepad, pad)]+[item.center(itempad, pad) for item in items])+"|")
0068 
0069 
0070 def table_sep(length):
0071     table_row("", [""]*length, "-")
0072 
0073 
0074 def vfmt(vec):
0075     return "%6.2f %6.2f %6.2f" % tuple(vec[:3])
0076 
0077 
0078 spaces = ["RGB", "HSV", "XYZ", "LAB", "LUV", "LCH_uv"]
0079 
0080 parser = argparse.ArgumentParser(
0081     description="Compares colors in different spaces"
0082 )
0083 parser.add_argument(
0084     "colors",
0085     help="Colors to inspect (in one of the CSS color formats)",
0086     nargs="+",
0087     type=ColorCompare,
0088 )
0089 parser.add_argument(
0090     "--space", "-s",
0091     choices=spaces+["all"],
0092     default="LCH_uv",
0093     help="Color space for the distance"
0094 )
0095 parser.add_argument(
0096     "--count", "-c",
0097     type=int,
0098     default=1,
0099     help="Number of similar colors to get"
0100 )
0101 parser.add_argument(
0102     "--colors",
0103     action="store_true",
0104     default=os.environ.get("COLORTERM", "") in {"truecolor", "24bit"},
0105     dest="term_colors",
0106     help="Enables color previews",
0107 )
0108 parser.add_argument(
0109     "--no-colors",
0110     action="store_false",
0111     dest="term_colors",
0112     help="Disables color previews",
0113 )
0114 
0115 
0116 def compare(colors, space, similar_count):
0117     print("=" * linelen)
0118     print(("Distances [%s]" % space).center(linelen))
0119     print("-" * linelen)
0120     table_row("", (c.name for c in colors))
0121     table_sep(len(colors))
0122 
0123     for color in colors:
0124         table_row(color.name, ("%10.8f" % color.dist(c2, space) if c2 is not color else "-" for c2 in colors))
0125 
0126     if similar_count:
0127         print("=" * linelen)
0128         print(("Nearest CSS Name [%s]" % space).center(linelen))
0129         print("-" * linelen)
0130         table_row(space, (c.name for c in colors))
0131         if term_colors:
0132             table_row("", (c.ansi_str(itempad) for c in colors))
0133         table_sep(len(colors))
0134 
0135         csscolors = [
0136             SimilarColor(name, vec, space)
0137             for name, vec in color_table.items()
0138         ]
0139 
0140         for color in colors:
0141             color.matches = sorted(
0142                 ((c, c.dist(color)) for c in csscolors),
0143                 key=lambda c: c[1]
0144             )
0145 
0146         for i in range(similar_count):
0147             table_row("", (c.matches[i][0].name for c in colors))
0148             if term_colors:
0149                 table_row("", (c.matches[i][0].ansi_str(itempad) for c in colors))
0150             table_row("", ("%10.8f" % c.matches[i][1] for c in colors))
0151             table_sep(len(colors))
0152 
0153 
0154 if __name__ == "__main__":
0155     ns = parser.parse_args()
0156 
0157     namepad = max(len(c.name) for c in ns.colors)
0158     titlepad = max(namepad, 6)
0159     itempad = max(namepad, 6*3+2)
0160     linelen = titlepad + (itempad + 1) * len(ns.colors) + 1
0161     term_colors = ns.term_colors
0162 
0163     print("=" * linelen)
0164     print("Colors".center(linelen))
0165     print("-" * linelen)
0166     table_row("Color", (c.name for c in ns.colors))
0167     if term_colors:
0168         table_row("", (c.ansi_str(itempad) for c in ns.colors))
0169     table_sep(len(ns.colors))
0170     for cs in ["RGB", "HSV", "XYZ", "LAB", "LUV", "LCH_uv"]:
0171         table_row(cs, (vfmt(c.representations[cs]) for c in ns.colors))
0172     table_sep(len(ns.colors))
0173 
0174     if ns.space == "all":
0175         for space in spaces:
0176             compare(ns.colors, space, ns.count)
0177     else:
0178         compare(ns.colors, ns.space, ns.count)
0179 
0180     print("=" * linelen)