Skip to content

Commit f556d1a

Browse files
committed
update hex validation
1 parent 4056c30 commit f556d1a

File tree

2 files changed

+60
-52
lines changed

2 files changed

+60
-52
lines changed

lib/matplotlib/tests/test_traitlets.py

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,35 +2,36 @@
22

33
from nose.tools import *
44
from unittest import TestCase
5-
from matplotlib.mpl_traitlets import Color, HasTraits
5+
from matplotlib.traitlets import Color, HasTraits, TraitError
66

77
class ColorTestCase(TestCase):
88
"""Tests for the Color traits"""
99

1010
def setUp(self):
1111
self.transparent_values = [None, False, '', 'none']
12-
self.black_values = ['#000000', (0,0,0,0), 0, 0.0, (.0,.0,.0), (.0,.0,.0,.0)]
12+
self.black_values = ['#000000', '#000',(0,0,0,255), 0, 0.0, (.0,.0,.0), (.0,.0,.0,1.0)]
1313
self.colored_values = ['#BE3537', (190,53,55), (0.7451, 0.20784, 0.21569)]
14-
self.invalid_values = ['áfaef', '#FFF', '#0SX#$S', (0,0,0), (0.45,0.3), (()), {}, True]
14+
self.invalid_values = ['áfaef', '#0SX#$S', (0.45,0.3), 3.4, 344, (()), {}, True]
1515

1616
def _evaluate_unvalids(self, a):
1717
for values in self.invalid_values:
1818
try:
1919
a.color = values
20-
except:
21-
assert_raises(TypeError)
20+
assert_true(False)
21+
except TraitError:
22+
assert_raises(TraitError)
2223

2324
def test_noargs(self):
2425
class A(HasTraits):
2526
color = Color()
2627
a = A()
2728
for values in self.black_values:
2829
a.color = values
29-
assert_equal(a.color, (0.0,0.0,0.0,0.0))
30+
assert_equal(a.color, (0.0,0.0,0.0,1.0))
3031

3132
for values in self.colored_values:
3233
a.color = values
33-
assert_equal(a.color, (0.7451, 0.20784, 0.21569, 0.0))
34+
assert_equal(a.color, (0.7451, 0.20784, 0.21569, 1.0))
3435
self._evaluate_unvalids(a)
3536

3637

@@ -79,25 +80,26 @@ class A(HasTraits):
7980

8081
for colorname in ncolors:
8182
a.color = colorname
82-
assert_equal(a.color, (0.0,0.0,1.0,0.0))
83+
assert_equal(a.color, (0.0,0.0,1.0,1.0))
8384

8485
def test_alpha(self):
8586
class A(HasTraits):
8687
color = Color(default_alpha=0.4)
8788

8889
a = A()
8990

90-
assert_equal(a.color, (0.0, 0.0, 0.0, 0.0))
91+
assert_equal(a.color, (0.0, 0.0, 0.0, 1.0))
9192

9293
for values in self.transparent_values:
9394
a.color = values
94-
assert_equal(a.color, (0.0,0.0,0.0,1.0))
95+
assert_equal(a.color, (0.0,0.0,0.0,0.0))
9596

9697
for values in self.black_values:
9798
a.color = values
9899
if isinstance(values, (tuple,list)) and len(values) == 4:
99-
assert_equal(a.color, (0.0,0.0,0.0,0.0))
100+
assert_equal(a.color, (0.0,0.0,0.0,1.0))
100101
else:
102+
# User not provide alpha value so return default_alpha
101103
assert_equal(a.color, (0.0,0.0,0.0,0.4))
102104

103105
for values in self.colored_values:

lib/matplotlib/traitlets.py

Lines changed: 47 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
# Union, TraitError, HasTraits,
1515
# NoDefaultSpecified, TraitType)
1616
import numpy as np
17+
import re
1718

1819
# override for backward compatability
1920
class Configurable(Configurable): pass
@@ -60,31 +61,30 @@ class Color(TraitType):
6061
Arguments:
6162
force_rgb: bool: Force the return in RGB format instead of RGB. Default: False
6263
as_hex: bool: Return the hex value instead. Default: False
63-
default_alpha: float (0.0-1.0) or integer (0-255) default alpha value.
64+
default_alpha: float (0.0-1.0) or integer (0-255). Default alpha value (1.0)
6465
6566
Accepts:
66-
string: a valid hex color string (i.e. #FFFFFF). 7 chars
67+
string: a valid hex color string (i.e. #FFFFFF). With 4 or 7 chars.
6768
tuple: a tuple of ints (0-255), or tuple of floats (0.0-1.0)
6869
float: A gray shade (0-1)
6970
integer: A gray shade (0-255)
7071
71-
Defaults: RGBA tuple, color black (0.0, 0.0, 0.0, 0.0)
72+
Defaults: RGBA tuple, color black (0.0, 0.0, 0.0, 1.0)
7273
7374
Return:
74-
A hex color string, a rgb or a rgba tuple. Defaults to rgba. When
75-
returning hex string, the alpha property will be ignored. A warning
76-
will be emitted if alpha information is passed different then 0.0
75+
A tuple of floats (r,g,b,a), (r,g,b) or a hex color string. i.e. "#FFFFFF".
7776
7877
"""
7978
metadata = {
8079
'force_rgb': False,
8180
'as_hex' : False,
82-
'default_alpha' : 0.0,
81+
'default_alpha' : 1.0,
8382
}
8483
allow_none = True
8584
info_text = 'float, int, tuple of float or int, or a hex string color'
86-
default_value = (0.0,0.0,0.0,0.0)
85+
default_value = (0.0,0.0,0.0, metadata['default_alpha'])
8786
named_colors = {}
87+
_re_color_hex = re.compile(r'#[a-fA-F0-9]{3}(?:[a-fA-F0-9]{3})?$')
8888

8989
def _int_to_float(self, value):
9090
as_float = (np.array(value)/255).tolist()
@@ -100,18 +100,13 @@ def _int_to_hex(self, value):
100100
return as_hex
101101

102102
def _hex_to_float(self, value):
103-
# Expects #FFFFFF format
104-
split_hex = (value[1:3],value[3:5],value[5:7])
105-
as_float = (np.array([int(v,16) for v in split_hex])/255.0).tolist()
103+
if len(value) == 7:
104+
split_hex = (value[1:3],value[3:5],value[5:7])
105+
as_float = (np.array([int(v,16) for v in split_hex])/255.0).tolist()
106+
elif len(value) == 4:
107+
as_float = (np.array([int(v+v,16) for v in value[1:]])/255.0).tolist()
106108
return as_float
107109

108-
def _is_hex16(self, value):
109-
try:
110-
int(value, 16)
111-
return True
112-
except:
113-
return False
114-
115110
def _float_to_shade(self, value):
116111
grade = value*255.0
117112
return (grade,grade,grade)
@@ -122,21 +117,28 @@ def _int_to_shade(self, value):
122117

123118
def validate(self, obj, value):
124119
in_range = False
125-
if value is None or value is False or value in ['none','']:
126-
# Return transparent if no other default alpha was set
127-
return (0.0, 0.0, 0.0, 1.0)
120+
if value is True:
121+
self.error(obj, value)
128122

129-
if isinstance(value, float) and 0 <= value <= 1:
130-
value = self._float_to_shade(value)
131-
else:
132-
in_range = False
123+
elif value is None or value is False or value in ['none','']:
124+
value = (0.0, 0.0, 0.0, 0.0)
125+
in_range = True
133126

134-
if isinstance(value, int) and 0 <= value <= 255:
135-
value = self._int_to_shade(value)
136-
else:
137-
in_range = False
127+
elif isinstance(value, float):
128+
if 0 <= value <= 1:
129+
value = self._float_to_shade(value)
130+
in_range = True
131+
else:
132+
in_range = False
138133

139-
if isinstance(value, (tuple, list)) and len(value) in (3,4):
134+
elif isinstance(value, int):
135+
if 0 <= value <= 255:
136+
value = self._int_to_shade(value)
137+
in_range = True
138+
else:
139+
in_range = False
140+
141+
elif isinstance(value, (tuple, list)) and len(value) in (3,4):
140142
is_all_float = np.prod([isinstance(v, (float)) for v in value])
141143
in_range = np.prod([(0 <= v <= 1) for v in value])
142144
if is_all_float and in_range:
@@ -147,10 +149,8 @@ def validate(self, obj, value):
147149
if is_all_int and in_range:
148150
value = self._int_to_float(value)
149151

150-
if isinstance(value, str) and len(value) == 7 and value[0] == '#':
151-
is_all_hex16 = np.prod([self._is_hex16(v) for v in\
152-
(value[1:3],value[3:5],value[5:7])])
153-
if is_all_hex16:
152+
elif isinstance(value, str) and len(value) in [4,7] and value[0] == '#':
153+
if self._re_color_hex.match(value):
154154
value = self._hex_to_float(value)
155155
in_range = np.prod([(0 <= v <= 1) for v in value])
156156
if in_range:
@@ -159,18 +159,24 @@ def validate(self, obj, value):
159159
elif isinstance(value, str) and value in self.named_colors:
160160
value = self.validate(obj, self.named_colors[value])
161161
in_range = True
162-
162+
163163
if in_range:
164+
# Convert to hex color string
164165
if self._metadata['as_hex']:
165166
return self._float_to_hex(value)
167+
168+
# Ignores alpha and return rgb
166169
if self._metadata['force_rgb'] and in_range:
167170
return tuple(np.round(value[:3],5).tolist())
168-
else:
169-
if len(value) == 3:
170-
value = tuple(np.round((value[0], value[1], value[2],
171-
self._metadata['default_alpha']),5).tolist())
172-
elif len(value) == 4:
173-
value = tuple(np.round(value,5).tolist())
171+
172+
# If no alpha provided, use default_alpha, also round the output
173+
if len(value) == 3:
174+
value = tuple(np.round((value[0], value[1], value[2],
175+
self._metadata['default_alpha']),5).tolist())
176+
elif len(value) == 4:
177+
# If no alpha provided, use default_alpha
178+
value = tuple(np.round(value,5).tolist())
179+
174180
return value
175181

176182
self.error(obj, value)

0 commit comments

Comments
 (0)