Skip to content

New color conversion machinery. #6382

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
May 14, 2016
16 changes: 15 additions & 1 deletion doc/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -334,5 +334,19 @@ def getapi(*args):
sys.modules['sip'] = mocksip
sys.modules['PyQt4'] = mockpyqt4

################# numpydoc config ####################
# numpydoc config

numpydoc_show_class_members = False

# Skip deprecated members

def skip_deprecated(app, what, name, obj, skip, options):
if skip:
return skip
skipped = {"matplotlib.colors": ["ColorConverter", "hex2color", "rgb2hex"]}
skip_list = skipped.get(getattr(obj, "__module__", None))
if skip_list is not None:
return getattr(obj, "__name__", None) in skip_list

def setup(app):
app.connect('autodoc-skip-member', skip_deprecated)
2 changes: 1 addition & 1 deletion doc/users/colors.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ it can be provided as:

* ``(r, g, b)`` tuples
* ``(r, g, b, a)`` tuples
* hex string, ex ``#OFOFOF``
* hex string, ex ``#0F0F0F``, or ``#0F0F0F0F`` (with alpha channel)
* float value between [0, 1] for gray level
* One of ``{'b', 'g', 'r', 'c', 'm', 'y', 'k', 'w'}``
* valid CSS4/X11 color names
Expand Down
11 changes: 11 additions & 0 deletions doc/users/whats_new/rgba-support.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
Improved color conversion API and RGBA support
----------------------------------------------

The :module:`~matplotlib.colors` gained a new color conversion API with
full support for the alpha channel. The main public functions are
:func:`~matplotlib.colors.is_color_like`, :func:`matplotlib.colors.to_rgba`,
:func:`matplotlib.colors.to_rgba_array` and :func:`~matplotlib.colors.to_hex`.
RGBA quadruplets are encoded in hex format as `#rrggbbaa`.

A side benefit is that the Qt options editor now allows setting the alpha
channel of the artists as well.
5 changes: 2 additions & 3 deletions examples/api/collections_demo.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,7 @@
'''

import matplotlib.pyplot as plt
from matplotlib import collections, transforms
from matplotlib.colors import colorConverter
from matplotlib import collections, colors, transforms
import numpy as np

nverts = 50
Expand All @@ -38,7 +37,7 @@
xyo = list(zip(xo, yo))

# Make a list of colors cycling through the default series.
colors = [colorConverter.to_rgba(c)
colors = [colors.to_rgba(c)
for c in plt.rcParams['axes.prop_cycle'].by_key()['color']]

fig, axes = plt.subplots(2, 2)
Expand Down
41 changes: 13 additions & 28 deletions examples/color/named_colors.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,36 +11,19 @@

import numpy as np
import matplotlib.pyplot as plt
from matplotlib import colors
from matplotlib import colors as mcolors


colors_ = list(six.iteritems(colors.cnames))

# Add the single letter colors.
for name, rgb in six.iteritems(colors.ColorConverter.colors):
hex_ = colors.rgb2hex(rgb)
colors_.append((name, hex_))

# Transform to hex color values.
hex_ = [color[1] for color in colors_]
# Get the rgb equivalent.
rgb = [colors.hex2color(color) for color in hex_]
# Get the hsv equivalent.
hsv = [colors.rgb_to_hsv(color) for color in rgb]

# Split the hsv values to sort.
hue = [color[0] for color in hsv]
sat = [color[1] for color in hsv]
val = [color[2] for color in hsv]

# Get the color names by themselves.
names = [color[0] for color in colors_]
colors = dict(mcolors.BASE_COLORS, **mcolors.CSS4_COLORS)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add xkcd here or does that just blow the example up too much?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Try making a figure with 3244 lines and text elements and get back to me :-)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fair point.


# Sort by hue, saturation, value and name.
ind = np.lexsort((names, val, sat, hue))
sorted_colors = [colors_[i] for i in ind]
by_hsv = sorted((tuple(mcolors.rgb_to_hsv(mcolors.to_rgba(color)[:3])), name)
for name, color in colors.items())

# Get the sorted color names.
sorted_names = [name for hsv, name in by_hsv]

n = len(sorted_colors)
n = len(sorted_names)
ncols = 4
nrows = int(np.ceil(1. * n / ncols))

Expand All @@ -53,7 +36,7 @@
# col width
w = X / ncols

for i, (name, color) in enumerate(sorted_colors):
for i, name in enumerate(sorted_names):
col = i % ncols
row = int(i / ncols)
y = Y - (row * h) - h
Expand All @@ -68,8 +51,10 @@

# Add extra black line a little bit thicker to make
# clear colors more visible.
ax.hlines(y, xi_line, xf_line, color='black', linewidth=(h * 0.7))
ax.hlines(y + h * 0.1, xi_line, xf_line, color=color, linewidth=(h * 0.6))
ax.hlines(
y, xi_line, xf_line, color='black', linewidth=(h * 0.7))
ax.hlines(
y + h * 0.1, xi_line, xf_line, color=colors[name], linewidth=(h * 0.6))

ax.set_xlim(0, X)
ax.set_ylim(0, Y)
Expand Down
7 changes: 3 additions & 4 deletions examples/event_handling/lasso_demo.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,17 @@
usable as is). There will be some refinement of the API.
"""
from matplotlib.widgets import Lasso
from matplotlib.colors import colorConverter
from matplotlib.collections import RegularPolyCollection
from matplotlib import path
from matplotlib import colors as mcolors, path

import matplotlib.pyplot as plt
from numpy import nonzero
from numpy.random import rand


class Datum(object):
colorin = colorConverter.to_rgba('red')
colorout = colorConverter.to_rgba('blue')
colorin = mcolors.to_rgba("red")
colorout = mcolors.to_rgba("blue")

def __init__(self, x, y, include=False):
self.x = x
Expand Down
4 changes: 2 additions & 2 deletions examples/mplot3d/polys3d_demo.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,16 @@

from mpl_toolkits.mplot3d import Axes3D
from matplotlib.collections import PolyCollection
from matplotlib.colors import colorConverter
import matplotlib.pyplot as plt
from matplotlib import colors as mcolors
import numpy as np


def cc(arg):
'''
Shorthand to convert 'named' colors to rgba format at 60% opacity.
'''
return colorConverter.to_rgba(arg, alpha=0.6)
return mcolors.to_rgba(arg, alpha=0.6)


def polygon_under_graph(xlist, ylist):
Expand Down
4 changes: 2 additions & 2 deletions examples/pylab_examples/colours.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@
Some simple functions to generate colours.
"""
import numpy as np
from matplotlib.colors import colorConverter
from matplotlib import colors as mcolors


def pastel(colour, weight=2.4):
""" Convert colour into a nice pastel shade"""
rgb = np.asarray(colorConverter.to_rgb(colour))
rgb = np.asarray(mcolors.to_rgba(colour)[:3])
# scale colour
maxc = max(rgb)
if maxc < 1.0 and maxc > 0:
Expand Down
2 changes: 1 addition & 1 deletion examples/pylab_examples/demo_ribbon_box.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class RibbonBox(object):
nx = original_image.shape[1]

def __init__(self, color):
rgb = matplotlib.colors.colorConverter.to_rgb(color)
rgb = matplotlib.colors.to_rgba(color)[:3]

im = np.empty(self.original_image.shape,
self.original_image.dtype)
Expand Down
4 changes: 2 additions & 2 deletions examples/pylab_examples/line_collection.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import matplotlib.pyplot as plt
from matplotlib.collections import LineCollection
from matplotlib.colors import colorConverter
from matplotlib import colors as mcolors

import numpy as np

Expand Down Expand Up @@ -30,7 +30,7 @@
# where onoffseq is an even length tuple of on and off ink in points.
# If linestyle is omitted, 'solid' is used
# See matplotlib.collections.LineCollection for more information
colors = [colorConverter.to_rgba(c)
colors = [mcolors.to_rgba(c)
for c in plt.rcParams['axes.prop_cycle'].by_key()['color']]

line_segments = LineCollection(segs, linewidths=(0.5, 1, 1.5, 2),
Expand Down
4 changes: 2 additions & 2 deletions examples/widgets/menu.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ def __init__(self, fontsize=14, labelcolor='black', bgcolor='yellow',
self.bgcolor = bgcolor
self.alpha = alpha

self.labelcolor_rgb = colors.colorConverter.to_rgb(labelcolor)
self.bgcolor_rgb = colors.colorConverter.to_rgb(bgcolor)
self.labelcolor_rgb = colors.to_rgba(labelcolor)[:3]
self.bgcolor_rgb = colors.to_rgba(bgcolor)[:3]


class MenuItem(artist.Artist):
Expand Down
12 changes: 12 additions & 0 deletions lib/matplotlib/_color_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,18 @@

from matplotlib.externals import six


BASE_COLORS = {
'b': (0, 0, 1),
'g': (0, 0.5, 0),
'r': (1, 0, 0),
'c': (0, 0.75, 0.75),
'm': (0.75, 0, 0.75),
'y': (0.75, 0.75, 0),
'k': (0, 0, 0),
'w': (1, 1, 1)}


# This mapping of color names -> hex values is taken from
# a survey run by Randel Monroe see:
# http://blog.xkcd.com/2010/05/03/color-survey-results/
Expand Down
8 changes: 4 additions & 4 deletions lib/matplotlib/axes/_axes.py
Original file line number Diff line number Diff line change
Expand Up @@ -2066,7 +2066,7 @@ def make_iterable(x):
if color is None:
color = [None] * nbars
else:
color = list(mcolors.colorConverter.to_rgba_array(color))
color = list(mcolors.to_rgba_array(color))
if len(color) == 0: # until to_rgba_array is changed
color = [[0, 0, 0, 0]]
if len(color) < nbars:
Expand All @@ -2075,7 +2075,7 @@ def make_iterable(x):
if edgecolor is None:
edgecolor = [None] * nbars
else:
edgecolor = list(mcolors.colorConverter.to_rgba_array(edgecolor))
edgecolor = list(mcolors.to_rgba_array(edgecolor))
if len(edgecolor) == 0: # until to_rgba_array is changed
edgecolor = [[0, 0, 0, 0]]
if len(edgecolor) < nbars:
Expand Down Expand Up @@ -3844,7 +3844,7 @@ def scatter(self, x, y, s=None, c=None, marker=None, cmap=None, norm=None,
co = kwargs.pop('color', None)
if co is not None:
try:
mcolors.colorConverter.to_rgba_array(co)
mcolors.to_rgba_array(co)
except ValueError:
raise ValueError("'color' kwarg must be an mpl color"
" spec or sequence of color specs.\n"
Expand Down Expand Up @@ -6045,7 +6045,7 @@ def _normalize_input(inp, ename='input'):
if color is None:
color = [self._get_lines.get_next_color() for i in xrange(nx)]
else:
color = mcolors.colorConverter.to_rgba_array(color)
color = mcolors.to_rgba_array(color)
if len(color) != nx:
raise ValueError("color kwarg must have one color per dataset")

Expand Down
8 changes: 4 additions & 4 deletions lib/matplotlib/axes/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ def _process_plot_format(fmt):

# Is fmt just a colorspec?
try:
color = mcolors.colorConverter.to_rgb(fmt)
color = mcolors.to_rgba(fmt)

# We need to differentiate grayscale '1.0' from tri_down marker '1'
try:
Expand Down Expand Up @@ -112,14 +112,14 @@ def _process_plot_format(fmt):
raise ValueError(
'Illegal format string "%s"; two marker symbols' % fmt)
marker = c
elif c in mcolors.colorConverter.colors:
elif c in mcolors.get_named_colors_mapping():
if color is not None:
raise ValueError(
'Illegal format string "%s"; two color symbols' % fmt)
color = c
elif c == 'C' and i < len(chars) - 1:
color_cycle_number = int(chars[i + 1])
color = mcolors.colorConverter._get_nth_color(color_cycle_number)
color = mcolors.to_rgba("C{}".format(color_cycle_number))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still like the idea of using get_nth_color as an API here. We may change the exact formatting and specifics of this in the future. (Plus, why parse the string twice?)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The previous version may look format independent, but if you check the parsing code just around it it's clearly also dependent on the exact format of CN colors. Plus, it uses a private API. (And I wouldn't worry too much about the performance implications of doing an extra regexp match.)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like a wash either way. The elif statement above is encoding the details of the formatting. Using _get_nth_color only buries half the body.

On the other hand, it is our policy that we can use mpl private API anywhere in the mpl code base (that is the point of the private APIs).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tend to think that private APIs are module-level (or class-level) private (rather than project-level), but don't feel strongly about this so either way works for me.

i += 1
else:
raise ValueError(
Expand Down Expand Up @@ -3687,7 +3687,7 @@ def set_cursor_props(self, *args):
lw, c = args
else:
raise ValueError('args must be a (linewidth, color) tuple')
c = mcolors.colorConverter.to_rgba(c)
c = mcolors.to_rgba(c)
self._cursorProps = lw, c

def get_children(self):
Expand Down
4 changes: 2 additions & 2 deletions lib/matplotlib/backend_bases.py
Original file line number Diff line number Diff line change
Expand Up @@ -1022,11 +1022,11 @@ def set_foreground(self, fg, isRGBA=False):
if self._forced_alpha and isRGBA:
self._rgb = fg[:3] + (self._alpha,)
elif self._forced_alpha:
self._rgb = colors.colorConverter.to_rgba(fg, self._alpha)
self._rgb = colors.to_rgba(fg, self._alpha)
elif isRGBA:
self._rgb = fg
else:
self._rgb = colors.colorConverter.to_rgba(fg)
self._rgb = colors.to_rgba(fg)

def set_graylevel(self, frac):
"""
Expand Down
5 changes: 2 additions & 3 deletions lib/matplotlib/backends/backend_agg.py
Original file line number Diff line number Diff line change
Expand Up @@ -568,9 +568,8 @@ def print_jpg(self, filename_or_obj, *args, **kwargs):
# The image is "pasted" onto a white background image to safely
# handle any transparency
image = Image.frombuffer('RGBA', size, buf, 'raw', 'RGBA', 0, 1)
color = mcolors.colorConverter.to_rgb(
rcParams.get('savefig.facecolor', 'white'))
color = tuple([int(x * 255.0) for x in color])
rgba = mcolors.to_rgba(rcParams.get('savefig.facecolor', 'white'))
color = tuple([int(x * 255.0) for x in rgba[:3]])
background = Image.new('RGB', size, color)
background.paste(image, image)
options = restrict_dict(kwargs, ['quality', 'optimize',
Expand Down
16 changes: 6 additions & 10 deletions lib/matplotlib/backends/backend_gtk.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,11 @@ def fn_name(): return sys._getframe(1).f_code.co_name

from matplotlib.backends.backend_gdk import RendererGDK, FigureCanvasGDK
from matplotlib.cbook import is_string_like, is_writable_file_like
from matplotlib.colors import colorConverter
from matplotlib.figure import Figure
from matplotlib.widgets import SubplotTool

from matplotlib import lines
from matplotlib import markers
from matplotlib import cbook
from matplotlib import verbose
from matplotlib import rcParams
from matplotlib import (
cbook, colors as mcolors, lines, markers, rcParams, verbose)

backend_version = "%d.%d.%d" % gtk.pygtk_version

Expand Down Expand Up @@ -1003,13 +999,13 @@ def on_combobox_lineprops_changed(self, item):
if marker is None: marker = 'None'
self.cbox_markers.set_active(self.markerd[marker])

r,g,b = colorConverter.to_rgb(line.get_color())
color = gtk.gdk.Color(*[int(val*65535) for val in (r,g,b)])
rgba = mcolors.to_rgba(line.get_color())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like a reasonable place to use to_rgb

Copy link
Contributor Author

@anntzer anntzer May 11, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does GTK have the concept of an RGBA color? (Not according to http://www.pygtk.org/pygtk2reference/class-gdkcolor.html, but I don't known much about GTK...)
Frankly I'd rather not expose to_rgb as a public API anymore, it's too easy to lose alpha information on the way...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@fariza is the GTK expert.

There are some applications which do not know / use / care about alpha, forcing all of them to explicitly drop the last value is falling a bit too far to the 'purity' side of API design.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, I have restored to_rgb, but will wait until we get a response re. GTK to decide what to do here (and won't push for now).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a section of the code that I was waiting for MEP27 to get rid of it. I plan to introduce the same functionality as generic tool for all the backends.

This class DialogLineprops is used only by one example: examples/user_interfaces/lineprops_dialog_gtk.py

The class was copy/paste to Gtk3 but last time I checked it wasn't working.

My advice would be, if the example runs, then it is fine. (I don't have a working gtk2 environment anymore to test)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The example runs with py2/gtk2. However, it is the typical example of why I want to get rid of to_rgb: the line props dialog seemingly support setting alpha (the glade file explicitly sets use_alpha to True), but the alpha is silently dropped after.

color = gtk.gdk.Color(*[int(val*65535) for val in rgba[:3]])
button = self.wtree.get_widget('colorbutton_linestyle')
button.set_color(color)

r,g,b = colorConverter.to_rgb(line.get_markerfacecolor())
color = gtk.gdk.Color(*[int(val*65535) for val in (r,g,b)])
rgba = mcolors.to_rgba(line.get_markerfacecolor())
color = gtk.gdk.Color(*[int(val*65535) for val in rgba[:3]])
button = self.wtree.get_widget('colorbutton_markerface')
button.set_color(color)
self._updateson = True
Expand Down
6 changes: 1 addition & 5 deletions lib/matplotlib/backends/backend_gtk3.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,10 @@ def fn_name(): return sys._getframe(1).f_code.co_name
from matplotlib import backend_tools

from matplotlib.cbook import is_string_like, is_writable_file_like
from matplotlib.colors import colorConverter
from matplotlib.figure import Figure
from matplotlib.widgets import SubplotTool

from matplotlib import lines
from matplotlib import cbook
from matplotlib import verbose
from matplotlib import rcParams
from matplotlib import cbook, colors as mcolors, lines, verbose, rcParams

backend_version = "%s.%s.%s" % (Gtk.get_major_version(), Gtk.get_micro_version(), Gtk.get_minor_version())

Expand Down
Loading