|
1 | 1 | """ |
2 | | -A class for converting color arguments to RGB |
| 2 | +A class for converting color arguments to RGB or RGBA |
3 | 3 |
|
4 | 4 | This class instantiates a single instance colorConverter that is used |
5 | 5 | to convert matlab color strings to RGB. RGB is a tuple of float RGB |
|
17 | 17 | k : black |
18 | 18 | w : white |
19 | 19 |
|
| 20 | +Gray shades can be given as a string encoding a float in the 0-1 |
| 21 | +range, e.g., |
| 22 | +
|
| 23 | + color = '0.75' |
| 24 | +
|
20 | 25 | For a greater range of colors, you have two options. You can specify |
21 | 26 | the color using an html hex string, as in |
22 | 27 |
|
|
189 | 194 | k = k.replace('gray', 'grey') |
190 | 195 | cnames[k] = v |
191 | 196 |
|
192 | | -def looks_like_color(c): |
193 | | - warnings.warn('Use is_color_like instead!', DeprecationWarning) |
194 | | - if is_string_like(c): |
195 | | - if cnames.has_key(c): return True |
196 | | - elif len(c)==1: return True |
197 | | - elif len(c)==7 and c.startswith('#') and len(c)==7: return True |
198 | | - else: return False |
199 | | - elif iterable(c) and len(c)==3: |
200 | | - try: |
201 | | - rgb = [float(val) for val in c] |
202 | | - return True |
203 | | - except: |
204 | | - return False |
205 | | - else: |
206 | | - return False |
207 | | - |
208 | 197 | def is_color_like(c): |
209 | 198 | try: |
210 | 199 | colorConverter.to_rgb(c) |
@@ -243,117 +232,106 @@ class ColorConverter: |
243 | 232 | } |
244 | 233 |
|
245 | 234 | cache = {} |
246 | | - def to_rgb(self, arg, warn=True): |
| 235 | + def to_rgb(self, arg): |
247 | 236 | """ |
248 | 237 | Returns an RGB tuple of three floats from 0-1. |
249 | 238 |
|
250 | | - arg can be an RGB sequence or a string in any of several forms: |
| 239 | + arg can be an RGB or RGBA sequence or a string in any of several forms: |
251 | 240 | 1) a letter from the set 'rgbcmykw' |
252 | 241 | 2) a hex color string, like '#00FFFF' |
253 | 242 | 3) a standard name, like 'aqua' |
254 | 243 | 4) a float, like '0.4', indicating gray on a 0-1 scale |
| 244 | +
|
| 245 | + if arg is RGBA, the A will simply be discarded. |
255 | 246 | """ |
256 | | - # warn kwarg will go away when float-as-grayscale does |
257 | 247 | try: return self.cache[arg] |
258 | 248 | except KeyError: pass |
259 | 249 | except TypeError: # could be unhashable rgb seq |
260 | 250 | arg = tuple(arg) |
261 | | - try: self.cache[arg] |
| 251 | + try: return self.cache[arg] |
262 | 252 | except KeyError: pass |
263 | 253 | except TypeError: |
264 | | - raise ValueError('to_rgb: unhashable even inside a tuple') |
| 254 | + raise ValueError( |
| 255 | + 'to_rgb: arg "%s" is unhashable even inside a tuple' |
| 256 | + % (str(arg),)) |
265 | 257 |
|
266 | 258 | try: |
267 | 259 | if is_string_like(arg): |
268 | | - str1 = cnames.get(arg, arg) |
269 | | - if str1.startswith('#'): |
270 | | - color = hex2color(str1) |
271 | | - else: |
272 | | - try: |
273 | | - color = self.colors[arg] |
274 | | - except KeyError: |
275 | | - color = tuple([float(arg)]*3) |
276 | | - elif iterable(arg): # streamline this after removing float case |
| 260 | + color = self.colors.get(arg, None) |
| 261 | + if color is None: |
| 262 | + str1 = cnames.get(arg, arg) |
| 263 | + if str1.startswith('#'): |
| 264 | + color = hex2color(str1) |
| 265 | + else: |
| 266 | + fl = float(arg) |
| 267 | + if fl < 0 or fl > 1: |
| 268 | + raise ValueError( |
| 269 | + 'gray (string) must be in range 0-1') |
| 270 | + color = tuple([fl]*3) |
| 271 | + elif iterable(arg): |
| 272 | + if len(arg) > 4 or len(arg) < 3: |
| 273 | + raise ValueError( |
| 274 | + 'sequence length is %d; must be 3 or 4'%len(arg)) |
277 | 275 | color = tuple(arg[:3]) |
278 | | - if [x for x in color if (x < 0) or (x > 1)]: |
279 | | - raise ValueError('to_rgb: Invalid rgb arg "%s"' % (str(arg))) |
280 | | - elif isinstance(arg, (float,int)): |
281 | | - #raise Exception('number is %s' % str(arg)) |
282 | | - if warn: warnings.warn( |
283 | | - "For gray use a string, '%s', not a float, %s" % |
284 | | - (str(arg), str(arg)), |
285 | | - DeprecationWarning) |
286 | | - else: self._gray = True |
287 | | - if 0 <= arg <= 1: |
288 | | - color = (arg,arg,arg) |
289 | | - else: |
290 | | - raise ValueError('Floating point color arg must be between 0 and 1') |
| 276 | + if [x for x in color if (float(x) < 0) or (x > 1)]: |
| 277 | + # This will raise TypeError if x is not a number. |
| 278 | + raise ValueError('number in rbg sequence outside 0-1 range') |
291 | 279 | else: |
292 | | - raise ValueError('to_rgb: Invalid rgb arg "%s"' % (str(arg))) |
| 280 | + raise ValueError('cannot convert argument to rgb sequence') |
293 | 281 |
|
294 | 282 | self.cache[arg] = color |
295 | 283 |
|
296 | 284 | except (KeyError, ValueError, TypeError), exc: |
297 | 285 | raise ValueError('to_rgb: Invalid rgb arg "%s"\n%s' % (str(arg), exc)) |
298 | | - |
| 286 | + # Error messages could be improved by handling TypeError |
| 287 | + # separately; but this should be rare and not too hard |
| 288 | + # for the user to figure out as-is. |
299 | 289 | return color |
300 | 290 |
|
301 | | - def to_rgba(self, arg, alpha=None, warn=True): |
| 291 | + def to_rgba(self, arg, alpha=None): |
302 | 292 | """ |
303 | 293 | Returns an RGBA tuple of four floats from 0-1. |
304 | 294 |
|
305 | | - For acceptable values of arg, see to_rgb. In |
306 | | - addition, arg may already be an rgba sequence, in which |
307 | | - case it is returned unchanged if the alpha kwarg is None, |
308 | | - or takes on the specified alpha. |
| 295 | + For acceptable values of arg, see to_rgb. |
| 296 | + If arg is an RGBA sequence and alpha is not None, |
| 297 | + alpha will replace the original A. |
309 | 298 | """ |
310 | | - if not is_string_like(arg) and iterable(arg): |
311 | | - if len(arg) == 4 and alpha is None: |
312 | | - return tuple(arg) |
313 | | - r,g,b = arg[:3] |
314 | | - else: |
315 | | - r,g,b = self.to_rgb(arg, warn) |
316 | | - if alpha is None: |
317 | | - alpha = 1.0 |
318 | | - return r,g,b,alpha |
319 | | - |
320 | | - def to_rgba_list(self, c): |
| 299 | + try: |
| 300 | + if not is_string_like(arg) and iterable(arg): |
| 301 | + if len(arg) == 4 and alpha is None: |
| 302 | + if [x for x in arg if (float(x) < 0) or (x > 1)]: |
| 303 | + # This will raise TypeError if x is not a number. |
| 304 | + raise ValueError('number in rbga sequence outside 0-1 range') |
| 305 | + return tuple(arg) |
| 306 | + r,g,b = arg[:3] |
| 307 | + if [x for x in (r,g,b) if (float(x) < 0) or (x > 1)]: |
| 308 | + raise ValueError('number in rbg sequence outside 0-1 range') |
| 309 | + else: |
| 310 | + r,g,b = self.to_rgb(arg) |
| 311 | + if alpha is None: |
| 312 | + alpha = 1.0 |
| 313 | + return r,g,b,alpha |
| 314 | + except (TypeError, ValueError), exc: |
| 315 | + raise ValueError('to_rgba: Invalid rgba arg "%s"\n%s' % (str(arg), exc)) |
| 316 | + |
| 317 | + def to_rgba_list(self, c, alpha=None): |
321 | 318 | """ |
322 | 319 | Returns a list of rgba tuples. |
323 | 320 |
|
324 | 321 | Accepts a single mpl color spec or a sequence of specs. |
325 | 322 | If the sequence is a list, the list items are changed in place. |
326 | 323 | """ |
327 | | - # This can be improved after removing float-as-grayscale. |
328 | | - if not is_string_like(c): |
329 | | - try: |
330 | | - N = len(c) # raises TypeError if it is not a sequence |
331 | | - # Temporary hack: keep single rgb or rgba from being |
332 | | - # treated as grayscale. |
333 | | - if N==3 or N==4: |
334 | | - L = [x for x in c if x>=0 and x<=1] |
335 | | - if len(L) == N: |
336 | | - raise ValueError |
337 | | - # If c is a list, we need to return the same list but |
338 | | - # with modified items so that items can be appended to |
339 | | - # it. This is needed for examples/dynamic_collections.py. |
340 | | - if not isinstance(c, list): # specific; don't need duck-typing |
341 | | - c = list(c) |
342 | | - self._gray = False |
343 | | - for i, cc in enumerate(c): |
344 | | - c[i] = self.to_rgba(cc, warn=False) # change in place |
345 | | - if self._gray: |
346 | | - msg = "In argument %s: use string, not float, for grayscale" % str(c) |
347 | | - warnings.warn(msg, DeprecationWarning) |
348 | | - return c |
349 | | - except (ValueError, TypeError): |
350 | | - pass |
351 | 324 | try: |
352 | | - return [self.to_rgba(c)] |
353 | | - except (ValueError, TypeError): |
354 | | - raise TypeError('c must be a matplotlib color arg or a sequence of them') |
355 | | - |
356 | | - |
| 325 | + return [self.to_rgba(c, alpha)] |
| 326 | + except ValueError: |
| 327 | + # If c is a list it must be maintained as the same list |
| 328 | + # with modified items so that items can be appended to |
| 329 | + # it. This is needed for examples/dynamic_collections.py. |
| 330 | + if not isinstance(c, list): # specific; don't need duck-typing |
| 331 | + c = list(c) |
| 332 | + for i, cc in enumerate(c): |
| 333 | + c[i] = self.to_rgba(cc, alpha) # change in place |
| 334 | + return c |
357 | 335 |
|
358 | 336 | colorConverter = ColorConverter() |
359 | 337 |
|
|
0 commit comments