Skip to content

Commit ccb46cd

Browse files
committed
better color handling
1 parent c1b043e commit ccb46cd

File tree

1 file changed

+62
-54
lines changed

1 file changed

+62
-54
lines changed

src/tikzplotlib/_color.py

Lines changed: 62 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,51 @@
11
import matplotlib as mpl
22
import numpy as np
3+
import webcolors
4+
5+
# RGB values (as taken from xcolor.dtx):
6+
builtin_colors = {
7+
# List white first such that for gray values, the combination
8+
# white!<x>!black is preferred over, e.g., gray!<y>!black. Note that
9+
# the order of the dictionary is respected from Python 3.6 on.
10+
"white": np.array([1, 1, 1]),
11+
"lightgray": np.array([0.75, 0.75, 0.75]),
12+
"gray": np.array([0.5, 0.5, 0.5]),
13+
"darkgray": np.array([0.25, 0.25, 0.25]),
14+
"black": np.array([0, 0, 0]),
15+
#
16+
"red": np.array([1, 0, 0]),
17+
"green": np.array([0, 1, 0]),
18+
"blue": np.array([0, 0, 1]),
19+
"brown": np.array([0.75, 0.5, 0.25]),
20+
"lime": np.array([0.75, 1, 0]),
21+
"orange": np.array([1, 0.5, 0]),
22+
"pink": np.array([1, 0.75, 0.75]),
23+
"purple": np.array([0.75, 0, 0.25]),
24+
"teal": np.array([0, 0.5, 0.5]),
25+
"violet": np.array([0.5, 0, 0.5]),
26+
# The colors cyan, magenta, yellow, and olive are also
27+
# predefined by xcolor, but their RGB approximation of the
28+
# native CMYK values is not very good. Don't use them here.
29+
}
30+
31+
32+
def _get_closest_colour_name(rgb):
33+
match = None
34+
mindiff = 1.0e15
35+
for h, name in webcolors.CSS3_HEX_TO_NAMES.items():
36+
r = int(h[1:3], 16)
37+
g = int(h[3:5], 16)
38+
b = int(h[5:7], 16)
39+
40+
diff = (rgb[0] - r) ** 2 + (rgb[1] - g) ** 2 + (rgb[2] - b) ** 2
41+
if diff < mindiff:
42+
match = name
43+
mindiff = diff
44+
45+
if mindiff == 0:
46+
break
47+
48+
return match, mindiff
349

450

551
def mpl_color2xcolor(data, matplotlib_color):
@@ -12,66 +58,28 @@ def mpl_color2xcolor(data, matplotlib_color):
1258
if my_col[-1] == 0.0:
1359
return data, "none", my_col
1460

15-
xcol = None
16-
# RGB values (as taken from xcolor.dtx):
17-
available_colors = {
18-
# List white first such that for gray values, the combination
19-
# white!<x>!black is preferred over, e.g., gray!<y>!black. Note that
20-
# the order of the dictionary is respected from Python 3.6 on.
21-
"white": np.array([1, 1, 1]),
22-
"lightgray": np.array([0.75, 0.75, 0.75]),
23-
"gray": np.array([0.5, 0.5, 0.5]),
24-
"darkgray": np.array([0.25, 0.25, 0.25]),
25-
"black": np.array([0, 0, 0]),
26-
#
27-
"red": np.array([1, 0, 0]),
28-
"green": np.array([0, 1, 0]),
29-
"blue": np.array([0, 0, 1]),
30-
"brown": np.array([0.75, 0.5, 0.25]),
31-
"lime": np.array([0.75, 1, 0]),
32-
"orange": np.array([1, 0.5, 0]),
33-
"pink": np.array([1, 0.75, 0.75]),
34-
"purple": np.array([0.75, 0, 0.25]),
35-
"teal": np.array([0, 0.5, 0.5]),
36-
"violet": np.array([0.5, 0, 0.5]),
37-
# The colors cyan, magenta, yellow, and olive are also
38-
# predefined by xcolor, but their RGB approximation of the
39-
# native CMYK values is not very good. Don't use them here.
40-
}
41-
42-
available_colors.update(data["custom colors"])
43-
4461
# Check if it exactly matches any of the colors already available.
4562
# This case is actually treated below (alpha==1), but that loop
4663
# may pick up combinations with black before finding the exact
4764
# match. Hence, first check all colors.
48-
for name, rgb in available_colors.items():
65+
for name, rgb in builtin_colors.items():
4966
if all(my_col[:3] == rgb):
50-
xcol = name
51-
return data, xcol, my_col
52-
53-
# Check if my_col is a multiple of a predefined color and 'black'.
54-
for name, rgb in available_colors.items():
55-
if name == "black":
56-
continue
67+
return data, name, my_col
5768

58-
if rgb[0] != 0.0:
59-
alpha = my_col[0] / rgb[0]
60-
elif rgb[1] != 0.0:
61-
alpha = my_col[1] / rgb[1]
62-
else:
63-
assert rgb[2] != 0.0
64-
alpha = my_col[2] / rgb[2]
69+
if np.all(my_col[0] == my_col[:3]):
70+
# gray
71+
# my_col[0] = 0.70123112 -> gray070
72+
name = f"gray{int(100 * my_col[0]):03d}"
73+
if name not in data["custom colors"]:
74+
data["custom colors"][name] = ("gray", str(my_col[0]))
75+
return data, name, my_col
6576

66-
# The cases 0.0 (my_col == black) and 1.0 (my_col == rgb) are
67-
# already accounted for by checking in available_colors above.
68-
if all(my_col[:3] == alpha * rgb) and 0.0 < alpha < 1.0:
69-
ff = data["float format"]
70-
xcol = name + f"!{alpha*100:{ff}}!black"
71-
return data, xcol, my_col
77+
# convert to RGB255
78+
rgb255 = np.array(my_col[:3] * 255, dtype=int)
7279

73-
# Lookup failed, add it to the custom list.
74-
xcol = "color" + str(len(data["custom colors"]))
75-
data["custom colors"][xcol] = my_col[:3]
80+
name, diff = _get_closest_colour_name(rgb255)
81+
if diff > 0:
82+
name = f"{name}{rgb255[0]}{rgb255[1]}{rgb255[2]}"
83+
data["custom colors"][name] = ("RGB", ",".join([str(val) for val in rgb255]))
7684

77-
return data, xcol, my_col
85+
return data, name, my_col

0 commit comments

Comments
 (0)