diff --git a/doc/api/api_changes.rst b/doc/api/api_changes.rst index 0ed2dd87a796..01f6aa02abf7 100644 --- a/doc/api/api_changes.rst +++ b/doc/api/api_changes.rst @@ -43,6 +43,27 @@ Changes in 1.3.x * The :func:`~matplotlib.cbook.check_output` function has been moved to `~matplotlib.compat.subprocess`. +* :class:`~matplotlib.patches.Patch` now fully supports using RGBA values for + its ``facecolor`` and ``edgecolor`` attributes, which enables faces and + edges to have different alpha values. If the + :class:`~matplotlib.patches.Patch` object's ``alpha`` attribute is set to + anything other than ``None``, that value will override any alpha-channel + value in both the face and edge colors. Previously, if + :class:`~matplotlib.patches.Patch` had ``alpha=None``, the alpha component + of ``edgecolor`` would be applied to both the edge and face. + +* The optional ``isRGB`` argument to + :meth:`~matplotlib.backend_bases.GraphicsContextBase.set_foreground` (and + the other GraphicsContext classes that descend from it) has been renamed to + ``isRGBA``, and should now only be set to ``True`` if the ``fg`` color + argument is known to be an RGBA tuple. + +* For :class:`~matplotlib.patches.Patch`, the ``capstyle`` used is now + ``butt``, to be consistent with the default for most other objects, and to + avoid problems with non-solid ``linestyle`` appearing solid when using a + large ``linewidth``. Previously, :class:`~matplotlib.patches.Patch` used + ``capstyle='projecting'``. + Changes in 1.2.x ================ diff --git a/doc/users/whats_new.rst b/doc/users/whats_new.rst index b87bc11ce098..bd9c096c508d 100644 --- a/doc/users/whats_new.rst +++ b/doc/users/whats_new.rst @@ -154,6 +154,16 @@ Wes Campaigne and Phil Elson fixed the Agg backend such that PNGs are now saved with the correct background color when :meth:`fig.patch.get_alpha` is not 1. +Independent alpha values for face and edge colors +------------------------------------------------- +Wes Campaigne modified how :class:`~matplotlib.patches.Patch` objects are +drawn such that (for backends supporting transparency) you can set different +alpha values for faces and edges, by specifying their colors in RGBA format. +Note that if you set the alpha attribute for the patch object (e.g. using +:meth:`~matplotlib.patches.Patch.set_alpha` or the ``alpha`` keyword +argument), that value will override the alpha components set in both the +face and edge colors. + .. _whats-new-1-2-2: diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index c4e3845ab0a7..0f65642909b1 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -362,8 +362,6 @@ def _iter_collection(self, gc, master_transform, all_transforms, gc0 = self.new_gc() gc0.copy_properties(gc) - original_alpha = gc.get_alpha() - if Nfacecolors == 0: rgbFace = None @@ -387,7 +385,6 @@ def _iter_collection(self, gc, master_transform, all_transforms, yo = -(yp - yo) if not (np.isfinite(xo) and np.isfinite(yo)): continue - gc0.set_alpha(original_alpha) if Nfacecolors: rgbFace = facecolors[i % Nfacecolors] if Nedgecolors: @@ -400,16 +397,12 @@ def _iter_collection(self, gc, master_transform, all_transforms, if fg[3] == 0.0: gc0.set_linewidth(0) else: - gc0.set_alpha(gc0.get_alpha() * fg[3]) - gc0.set_foreground(fg[:3]) + gc0.set_foreground(fg) else: gc0.set_foreground(fg) if rgbFace is not None and len(rgbFace) == 4: if rgbFace[3] == 0: rgbFace = None - else: - gc0.set_alpha(gc0.get_alpha() * rgbFace[3]) - rgbFace = rgbFace[:3] gc0.set_antialiased(antialiaseds[i % Naa]) if Nurls: gc0.set_url(urls[i % Nurls]) @@ -562,7 +555,7 @@ def _draw_text_as_path(self, gc, x, y, s, prop, angle, ismath): path, transform = self._get_text_path_transform( x, y, s, prop, angle, ismath) - color = gc.get_rgb()[:3] + color = gc.get_rgb() gc.set_linewidth(0.0) self.draw_path(gc, path, transform, rgbFace=color) @@ -702,7 +695,8 @@ def __init__(self): self._joinstyle = 'round' self._linestyle = 'solid' self._linewidth = 1 - self._rgb = (0.0, 0.0, 0.0) + self._rgb = (0.0, 0.0, 0.0, 1.0) + self._orig_color = (0.0, 0.0, 0.0, 1.0) self._hatch = None self._url = None self._gid = None @@ -711,6 +705,7 @@ def __init__(self): def copy_properties(self, gc): 'Copy properties from gc to self' self._alpha = gc._alpha + self._forced_alpha = gc._forced_alpha self._antialiased = gc._antialiased self._capstyle = gc._capstyle self._cliprect = gc._cliprect @@ -720,6 +715,7 @@ def copy_properties(self, gc): self._linestyle = gc._linestyle self._linewidth = gc._linewidth self._rgb = gc._rgb + self._orig_color = gc._orig_color self._hatch = gc._hatch self._url = gc._url self._gid = gc._gid @@ -781,6 +777,13 @@ def get_dashes(self): """ return self._dashes + def get_forced_alpha(self): + """ + Return whether the value given by get_alpha() should be used to + override any other alpha-channel values. + """ + return self._forced_alpha + def get_joinstyle(self): """ Return the line join style as one of ('miter', 'round', 'bevel') @@ -833,14 +836,19 @@ def get_snap(self): def set_alpha(self, alpha): """ - Set the alpha value used for blending - not supported on - all backends + Set the alpha value used for blending - not supported on all backends. + If ``alpha=None`` (the default), the alpha components of the + foreground and fill colors will be used to set their respective + transparencies (where applicable); otherwise, ``alpha`` will override + them. """ if alpha is not None: self._alpha = alpha self._forced_alpha = True else: + self._alpha = 1.0 self._forced_alpha = False + self.set_foreground(self._orig_color) def set_antialiased(self, b): """ @@ -890,30 +898,28 @@ def set_dashes(self, dash_offset, dash_list): """ self._dashes = dash_offset, dash_list - def set_foreground(self, fg, isRGB=False): + def set_foreground(self, fg, isRGBA=False): """ Set the foreground color. fg can be a MATLAB format string, a html hex color string, an rgb or rgba unit tuple, or a float between 0 and 1. In the latter case, grayscale is used. - If you know fg is rgb or rgba, set ``isRGB=True`` for - efficiency. + If you know fg is rgba, set ``isRGBA=True`` for efficiency. """ - if isRGB: + self._orig_color = fg + if self._forced_alpha: + self._rgb = colors.colorConverter.to_rgba(fg, self._alpha) + elif isRGBA: self._rgb = fg else: self._rgb = colors.colorConverter.to_rgba(fg) - if len(self._rgb) == 4 and not self._forced_alpha: - self.set_alpha(self._rgb[3]) - # Use set_alpha method here so that subclasses will - # be calling their own version, which may set their - # own attributes. def set_graylevel(self, frac): """ Set the foreground color to be a gray level with *frac* """ - self._rgb = (frac, frac, frac) + self._orig_color = frac + self._rgb = (frac, frac, frac, self._alpha) def set_joinstyle(self, js): """ @@ -942,7 +948,7 @@ def set_linestyle(self, style): 'dotted' : (0, (1.0, 3.0)), """ - if style in self.dashd.keys(): + if style in self.dashd: offset, dashes = self.dashd[style] elif isinstance(style, tuple): offset, dashes = style diff --git a/lib/matplotlib/backends/backend_cairo.py b/lib/matplotlib/backends/backend_cairo.py index a5e50e9cfb55..8d6c89ccccde 100644 --- a/lib/matplotlib/backends/backend_cairo.py +++ b/lib/matplotlib/backends/backend_cairo.py @@ -110,13 +110,13 @@ def set_width_height(self, width, height): # font transform? - def _fill_and_stroke (self, ctx, fill_c, alpha): + def _fill_and_stroke (self, ctx, fill_c, alpha, alpha_overrides): if fill_c is not None: ctx.save() - if len(fill_c) == 3: + if len(fill_c) == 3 or alpha_overrides: ctx.set_source_rgba (fill_c[0], fill_c[1], fill_c[2], alpha) else: - ctx.set_source_rgba (fill_c[0], fill_c[1], fill_c[2], alpha*fill_c[3]) + ctx.set_source_rgba (fill_c[0], fill_c[1], fill_c[2], fill_c[3]) ctx.fill_preserve() ctx.restore() ctx.stroke() @@ -150,7 +150,7 @@ def draw_path(self, gc, path, transform, rgbFace=None): ctx.new_path() self.convert_path(ctx, path, transform) - self._fill_and_stroke(ctx, rgbFace, gc.get_alpha()) + self._fill_and_stroke(ctx, rgbFace, gc.get_alpha(), gc.get_forced_alpha()) def draw_image(self, gc, x, y, im): # bbox - not currently used @@ -316,7 +316,10 @@ def set_alpha(self, alpha): GraphicsContextBase.set_alpha(self, alpha) _alpha = self.get_alpha() rgb = self._rgb - self.ctx.set_source_rgba (rgb[0], rgb[1], rgb[2], _alpha) + if self.get_forced_alpha(): + self.ctx.set_source_rgba (rgb[0], rgb[1], rgb[2], _alpha) + else: + self.ctx.set_source_rgba (rgb[0], rgb[1], rgb[2], rgb[3]) #def set_antialiased(self, b): @@ -359,8 +362,8 @@ def set_dashes(self, offset, dashes): self.renderer.points_to_pixels (np.asarray(dashes)), offset) - def set_foreground(self, fg, isRGB=None): - GraphicsContextBase.set_foreground(self, fg, isRGB) + def set_foreground(self, fg, isRGBA=None): + GraphicsContextBase.set_foreground(self, fg, isRGBA) if len(self._rgb) == 3: self.ctx.set_source_rgb(*self._rgb) else: diff --git a/lib/matplotlib/backends/backend_gdk.py b/lib/matplotlib/backends/backend_gdk.py index 188c2806811b..49583dc8649e 100644 --- a/lib/matplotlib/backends/backend_gdk.py +++ b/lib/matplotlib/backends/backend_gdk.py @@ -393,8 +393,8 @@ def set_dashes(self, dash_offset, dash_list): self.gdkGC.line_style = gdk.LINE_ON_OFF_DASH - def set_foreground(self, fg, isRGB=False): - GraphicsContextBase.set_foreground(self, fg, isRGB) + def set_foreground(self, fg, isRGBA=False): + GraphicsContextBase.set_foreground(self, fg, isRGBA) self.gdkGC.foreground = self.rgb_to_gdk_color(self.get_rgb()) diff --git a/lib/matplotlib/backends/backend_macosx.py b/lib/matplotlib/backends/backend_macosx.py index ed17506e4a79..50cfab4dc54e 100644 --- a/lib/matplotlib/backends/backend_macosx.py +++ b/lib/matplotlib/backends/backend_macosx.py @@ -50,13 +50,13 @@ def set_width_height (self, width, height): def draw_path(self, gc, path, transform, rgbFace=None): if rgbFace is not None: - rgbFace = tuple(rgbFace[:3]) + rgbFace = tuple(rgbFace) linewidth = gc.get_linewidth() gc.draw_path(path, transform, linewidth, rgbFace) def draw_markers(self, gc, marker_path, marker_trans, path, trans, rgbFace=None): if rgbFace is not None: - rgbFace = tuple(rgbFace[:3]) + rgbFace = tuple(rgbFace) linewidth = gc.get_linewidth() gc.draw_markers(marker_path, marker_trans, path, trans, linewidth, rgbFace) @@ -183,12 +183,14 @@ def __init__(self): def set_alpha(self, alpha): GraphicsContextBase.set_alpha(self, alpha) _alpha = self.get_alpha() - _macosx.GraphicsContext.set_alpha(self, _alpha) + _macosx.GraphicsContext.set_alpha(self, _alpha, self.get_forced_alpha()) + rgb = self.get_rgb() + _macosx.GraphicsContext.set_foreground(self, rgb) - def set_foreground(self, fg, isRGB=False): - GraphicsContextBase.set_foreground(self, fg, isRGB) + def set_foreground(self, fg, isRGBA=False): + GraphicsContextBase.set_foreground(self, fg, isRGBA) rgb = self.get_rgb() - _macosx.GraphicsContext.set_foreground(self, rgb[:3]) + _macosx.GraphicsContext.set_foreground(self, rgb) def set_graylevel(self, fg): GraphicsContextBase.set_graylevel(self, fg) diff --git a/lib/matplotlib/backends/backend_pdf.py b/lib/matplotlib/backends/backend_pdf.py index bc1e09e34ab3..1e9e4c03f197 100644 --- a/lib/matplotlib/backends/backend_pdf.py +++ b/lib/matplotlib/backends/backend_pdf.py @@ -1064,7 +1064,7 @@ def alphaState(self, alpha): self.nextAlphaState += 1 self.alphaStates[alpha] = \ (name, { 'Type': Name('ExtGState'), - 'CA': alpha, 'ca': alpha }) + 'CA': alpha[0], 'ca': alpha[1] }) return name def hatchPattern(self, hatch_style): @@ -1443,11 +1443,21 @@ def check_gc(self, gc, fillcolor=None): orig_fill = gc._fillcolor gc._fillcolor = fillcolor + orig_alphas = gc._effective_alphas + + if gc._forced_alpha: + gc._effective_alphas = (gc._alpha, gc._alpha) + elif fillcolor is None or len(fillcolor) < 4: + gc._effective_alphas = (gc._rgb[3], 1.0) + else: + gc._effective_alphas = (gc._rgb[3], fillcolor[3]) + delta = self.gc.delta(gc) if delta: self.file.output(*delta) # Restore gc to avoid unwanted side effects gc._fillcolor = orig_fill + gc._effective_alphas = orig_alphas def tex_font_mapping(self, texfont): if self.tex_font_map is None: @@ -2004,6 +2014,7 @@ class GraphicsContextPdf(GraphicsContextBase): def __init__(self, file): GraphicsContextBase.__init__(self) self._fillcolor = (0.0, 0.0, 0.0) + self._effective_alphas = (1.0, 1.0) self.file = file self.parent = None @@ -2072,8 +2083,8 @@ def dash_cmd(self, dashes): offset = 0 return [list(dash), offset, Op.setdash] - def alpha_cmd(self, alpha): - name = self.file.alphaState(alpha) + def alpha_cmd(self, alpha, forced, effective_alphas): + name = self.file.alphaState(effective_alphas) return [name, Op.setgstate] def hatch_cmd(self, hatch): @@ -2138,7 +2149,7 @@ def clip_cmd(self, cliprect, clippath): commands = ( (('_cliprect', '_clippath'), clip_cmd), # must come first since may pop - (('_alpha',), alpha_cmd), + (('_alpha', '_forced_alpha', '_effective_alphas'), alpha_cmd), (('_capstyle',), capstyle_cmd), (('_fillcolor',), fillcolor_cmd), (('_joinstyle',), joinstyle_cmd), @@ -2183,6 +2194,7 @@ def copy_properties(self, other): """ GraphicsContextBase.copy_properties(self, other) self._fillcolor = other._fillcolor + self._effective_alphas = other._effective_alphas def finalize(self): """ diff --git a/lib/matplotlib/backends/backend_pgf.py b/lib/matplotlib/backends/backend_pgf.py index 9bb02a62236c..c9015d3a5237 100644 --- a/lib/matplotlib/backends/backend_pgf.py +++ b/lib/matplotlib/backends/backend_pgf.py @@ -513,14 +513,18 @@ def _print_pgf_path_styles(self, gc, rgbFace): # filling has_fill = rgbFace is not None - path_is_transparent = gc.get_alpha() != 1.0 - fill_is_transparent = has_fill and (len(rgbFace) > 3) and (rgbFace[3] != 1.0) + + if gc.get_forced_alpha(): + fillopacity = strokeopacity = gc.get_alpha() + else: + strokeopacity = gc.get_rgb()[3] + fillopacity = rgbFace[3] if has_fill and len(rgbFace) > 3 else 1.0 + if has_fill: writeln(self.fh, r"\definecolor{currentfill}{rgb}{%f,%f,%f}" % tuple(rgbFace[:3])) writeln(self.fh, r"\pgfsetfillcolor{currentfill}") - if has_fill and (path_is_transparent or fill_is_transparent): - opacity = gc.get_alpha() * 1.0 if not fill_is_transparent else rgbFace[3] - writeln(self.fh, r"\pgfsetfillopacity{%f}" % opacity) + if has_fill and fillopacity != 1.0: + writeln(self.fh, r"\pgfsetfillopacity{%f}" % fillopacity) # linewidth and color lw = gc.get_linewidth() * mpl_pt_to_in * latex_in_to_pt @@ -528,15 +532,14 @@ def _print_pgf_path_styles(self, gc, rgbFace): writeln(self.fh, r"\pgfsetlinewidth{%fpt}" % lw) writeln(self.fh, r"\definecolor{currentstroke}{rgb}{%f,%f,%f}" % stroke_rgba[:3]) writeln(self.fh, r"\pgfsetstrokecolor{currentstroke}") - if gc.get_alpha() != 1.0: - writeln(self.fh, r"\pgfsetstrokeopacity{%f}" % gc.get_alpha()) + if strokeopacity != 1.0: + writeln(self.fh, r"\pgfsetstrokeopacity{%f}" % strokeopacity) # line style dash_offset, dash_list = gc.get_dashes() - ls = gc.get_linestyle(None) - if ls == "solid": + if dash_list is None: writeln(self.fh, r"\pgfsetdash{}{0pt}") - elif (ls == "dashed" or ls == "dashdot" or ls == "dotted"): + else: dash_str = r"\pgfsetdash{" for dash in dash_list: dash_str += r"{%fpt}" % dash diff --git a/lib/matplotlib/backends/backend_svg.py b/lib/matplotlib/backends/backend_svg.py index dad6ed060746..80029b279504 100644 --- a/lib/matplotlib/backends/backend_svg.py +++ b/lib/matplotlib/backends/backend_svg.py @@ -289,7 +289,7 @@ def _write_default_style(self): writer = self.writer default_style = generate_css({ u'stroke-linejoin': u'round', - u'stroke-linecap': u'square'}) + u'stroke-linecap': u'butt'}) writer.start(u'defs') writer.start(u'style', type=u'text/css') writer.data(u'*{%s}\n' % default_style) @@ -386,15 +386,21 @@ def _get_style_dict(self, gc, rgbFace): """ attrib = {} + forced_alpha = gc.get_forced_alpha() + if gc.get_hatch() is not None: attrib[u'fill'] = u"url(#%s)" % self._get_hatch(gc, rgbFace) + if rgbFace is not None and len(rgbFace) == 4 and rgbFace[3] != 1.0 and not forced_alpha: + attrib[u'fill-opacity'] = str(rgbFace[3]) else: if rgbFace is None: attrib[u'fill'] = u'none' elif tuple(rgbFace[:3]) != (0, 0, 0): attrib[u'fill'] = rgb2hex(rgbFace) + if len(rgbFace) == 4 and rgbFace[3] != 1.0 and not forced_alpha: + attrib[u'fill-opacity'] = str(rgbFace[3]) - if gc.get_alpha() != 1.0: + if forced_alpha and gc.get_alpha() != 1.0: attrib[u'opacity'] = str(gc.get_alpha()) offset, seq = gc.get_dashes() @@ -404,12 +410,15 @@ def _get_style_dict(self, gc, rgbFace): linewidth = gc.get_linewidth() if linewidth: - attrib[u'stroke'] = rgb2hex(gc.get_rgb()) + rgb = gc.get_rgb() + attrib[u'stroke'] = rgb2hex(rgb) + if not forced_alpha and rgb[3] != 1.0: + attrib[u'stroke-opacity'] = str(rgb[3]) if linewidth != 1.0: attrib[u'stroke-width'] = str(linewidth) if gc.get_joinstyle() != 'round': attrib[u'stroke-linejoin'] = gc.get_joinstyle() - if gc.get_capstyle() != 'projecting': + if gc.get_capstyle() != 'butt': attrib[u'stroke-linecap'] = _capstyle_d[gc.get_capstyle()] return attrib diff --git a/lib/matplotlib/backends/backend_wx.py b/lib/matplotlib/backends/backend_wx.py index 67da1db4189b..3868968e2a29 100644 --- a/lib/matplotlib/backends/backend_wx.py +++ b/lib/matplotlib/backends/backend_wx.py @@ -517,7 +517,7 @@ def unselect(self): self.dc.SelectObject(wx.NullBitmap) self.IsSelected = False - def set_foreground(self, fg, isRGB=None): + def set_foreground(self, fg, isRGBA=None): """ Set the foreground color. fg can be a matlab format string, a html hex color string, an rgb unit tuple, or a float between 0 @@ -530,7 +530,7 @@ def set_foreground(self, fg, isRGB=None): # Same goes for text foreground... DEBUG_MSG("set_foreground()", 1, self) self.select() - GraphicsContextBase.set_foreground(self, fg, isRGB) + GraphicsContextBase.set_foreground(self, fg, isRGBA) self._pen.SetColour(self.get_wxcolour(self.get_rgb())) self.gfx_ctx.SetPen(self._pen) diff --git a/lib/matplotlib/patches.py b/lib/matplotlib/patches.py index 986b5f6c1c21..fd1b01c97ae4 100644 --- a/lib/matplotlib/patches.py +++ b/lib/matplotlib/patches.py @@ -251,6 +251,7 @@ def set_edgecolor(self, color): """ if color is None: color = mpl.rcParams['patch.edgecolor'] + self._original_edgecolor = color self._edgecolor = colors.colorConverter.to_rgba(color, self._alpha) def set_ec(self, color): @@ -304,8 +305,7 @@ def set_alpha(self, alpha): artist.Artist.set_alpha(self, alpha) self.set_facecolor(self._original_facecolor) # using self._fill and # self._alpha - self._edgecolor = colors.colorConverter.to_rgba( - self._edgecolor[:3], self._alpha) + self.set_edgecolor(self._original_edgecolor) def set_linewidth(self, w): """ @@ -404,7 +404,7 @@ def draw(self, renderer): renderer.open_group('patch', self.get_gid()) gc = renderer.new_gc() - gc.set_foreground(self._edgecolor, isRGB=True) + gc.set_foreground(self._edgecolor, isRGBA=True) lw = self._linewidth if self._edgecolor[3] == 0: @@ -414,7 +414,6 @@ def draw(self, renderer): gc.set_antialiased(self._antialiased) self._set_gc_clip(gc) - gc.set_capstyle('projecting') gc.set_url(self._url) gc.set_snap(self.get_snap()) @@ -422,9 +421,7 @@ def draw(self, renderer): if rgbFace[3] == 0: rgbFace = None # (some?) renderers expect this as no-fill signal - gc.set_alpha(self._edgecolor[3]) - if self._edgecolor[3] == 0: - gc.set_alpha(self._facecolor[3]) + gc.set_alpha(self._alpha) if self._hatch: gc.set_hatch(self._hatch) @@ -3995,7 +3992,7 @@ def draw(self, renderer): renderer.open_group('patch', self.get_gid()) gc = renderer.new_gc() - gc.set_foreground(self._edgecolor, isRGB=True) + gc.set_foreground(self._edgecolor, isRGBA=True) lw = self._linewidth if self._edgecolor[3] == 0: @@ -4012,9 +4009,7 @@ def draw(self, renderer): if rgbFace[3] == 0: rgbFace = None # (some?) renderers expect this as no-fill signal - gc.set_alpha(self._edgecolor[3]) - if self._edgecolor[3] == 0: - gc.set_alpha(self._facecolor[3]) + gc.set_alpha(self._alpha) if self._hatch: gc.set_hatch(self._hatch) diff --git a/lib/matplotlib/tests/baseline_images/test_patches/clip_to_bbox.pdf b/lib/matplotlib/tests/baseline_images/test_patches/clip_to_bbox.pdf index dcd72cbe7701..16974c4c7d3e 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_patches/clip_to_bbox.pdf and b/lib/matplotlib/tests/baseline_images/test_patches/clip_to_bbox.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_patches/clip_to_bbox.png b/lib/matplotlib/tests/baseline_images/test_patches/clip_to_bbox.png index 13f1113d1eb3..16a5ddfd0425 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_patches/clip_to_bbox.png and b/lib/matplotlib/tests/baseline_images/test_patches/clip_to_bbox.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_patches/clip_to_bbox.svg b/lib/matplotlib/tests/baseline_images/test_patches/clip_to_bbox.svg index f1744ab11cbd..2d2968aacc1c 100644 --- a/lib/matplotlib/tests/baseline_images/test_patches/clip_to_bbox.svg +++ b/lib/matplotlib/tests/baseline_images/test_patches/clip_to_bbox.svg @@ -5,7 +5,7 @@ @@ -58,7 +58,7 @@ C295.83 144.173 283.453 179.338 283.453 216 C283.453 252.662 295.83 287.827 317.86 313.75 C339.889 339.674 369.772 354.24 400.926 354.24 z -" style="fill:#ff7f50;opacity:0.5;stroke:#000000;"/> +" style="fill:#ff7f50;opacity:0.5;"/> +L0 -4" id="m93b0483c22" style="stroke:#000000;stroke-width:0.5;"/> - + +L0 4" id="m741efc42ff" style="stroke:#000000;stroke-width:0.5;"/> - + @@ -158,7 +158,7 @@ Q14.8906 38.1406 10.7969 36.2812 z " id="BitstreamVeraSans-Roman-35"/> - + @@ -168,12 +168,12 @@ z - + - + @@ -199,7 +199,7 @@ Q6.59375 17.9688 6.59375 36.375 Q6.59375 54.8281 13.0625 64.5156 Q19.5312 74.2188 31.7812 74.2188" id="BitstreamVeraSans-Roman-30"/> - + @@ -209,17 +209,17 @@ Q19.5312 74.2188 31.7812 74.2188" id="BitstreamVeraSans-Roman-30"/> - + - + - + @@ -228,17 +228,17 @@ Q19.5312 74.2188 31.7812 74.2188" id="BitstreamVeraSans-Roman-30"/> - + - + - + @@ -246,17 +246,17 @@ Q19.5312 74.2188 31.7812 74.2188" id="BitstreamVeraSans-Roman-30"/> - + - + - + @@ -264,17 +264,17 @@ Q19.5312 74.2188 31.7812 74.2188" id="BitstreamVeraSans-Roman-30"/> - + - + - + @@ -283,17 +283,17 @@ Q19.5312 74.2188 31.7812 74.2188" id="BitstreamVeraSans-Roman-30"/> - + - + - + @@ -302,12 +302,12 @@ Q19.5312 74.2188 31.7812 74.2188" id="BitstreamVeraSans-Roman-30"/> - + - + @@ -337,7 +337,7 @@ Q49.8594 40.875 45.4062 35.4062 Q44.1875 33.9844 37.6406 27.2188 Q31.1094 20.4531 19.1875 8.29688" id="BitstreamVeraSans-Roman-32"/> - + @@ -350,25 +350,25 @@ Q31.1094 20.4531 19.1875 8.29688" id="BitstreamVeraSans-Roman-32"/> +L4 0" id="m728421d6d4" style="stroke:#000000;stroke-width:0.5;"/> - + +L-4 0" id="mcb0005524f" style="stroke:#000000;stroke-width:0.5;"/> - + - + @@ -379,17 +379,17 @@ L-4 0" id="m0d5b0a6425" style="stroke:#000000;stroke-linecap:butt;stroke-width:0 - + - + - + @@ -400,17 +400,17 @@ L-4 0" id="m0d5b0a6425" style="stroke:#000000;stroke-linecap:butt;stroke-width:0 - + - + - + @@ -420,17 +420,17 @@ L-4 0" id="m0d5b0a6425" style="stroke:#000000;stroke-linecap:butt;stroke-width:0 - + - + - + @@ -438,17 +438,17 @@ L-4 0" id="m0d5b0a6425" style="stroke:#000000;stroke-linecap:butt;stroke-width:0 - + - + - + @@ -457,17 +457,17 @@ L-4 0" id="m0d5b0a6425" style="stroke:#000000;stroke-linecap:butt;stroke-width:0 - + - + - + diff --git a/lib/matplotlib/tests/baseline_images/test_patches/patch_alpha_coloring.pdf b/lib/matplotlib/tests/baseline_images/test_patches/patch_alpha_coloring.pdf new file mode 100644 index 000000000000..10ec4d3d3847 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_patches/patch_alpha_coloring.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_patches/patch_alpha_coloring.png b/lib/matplotlib/tests/baseline_images/test_patches/patch_alpha_coloring.png new file mode 100644 index 000000000000..b968c0ddf27f Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_patches/patch_alpha_coloring.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_patches/patch_alpha_coloring.svg b/lib/matplotlib/tests/baseline_images/test_patches/patch_alpha_coloring.svg new file mode 100644 index 000000000000..b4e1e646b0f1 --- /dev/null +++ b/lib/matplotlib/tests/baseline_images/test_patches/patch_alpha_coloring.svg @@ -0,0 +1,311 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/matplotlib/tests/baseline_images/test_patches/patch_alpha_override.pdf b/lib/matplotlib/tests/baseline_images/test_patches/patch_alpha_override.pdf new file mode 100644 index 000000000000..b7a5322613a4 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_patches/patch_alpha_override.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_patches/patch_alpha_override.png b/lib/matplotlib/tests/baseline_images/test_patches/patch_alpha_override.png new file mode 100644 index 000000000000..3eb70b362ac8 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_patches/patch_alpha_override.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_patches/patch_alpha_override.svg b/lib/matplotlib/tests/baseline_images/test_patches/patch_alpha_override.svg new file mode 100644 index 000000000000..7fde38ba1621 --- /dev/null +++ b/lib/matplotlib/tests/baseline_images/test_patches/patch_alpha_override.svg @@ -0,0 +1,311 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/matplotlib/tests/baseline_images/test_patches/patch_custom_linestyle.pdf b/lib/matplotlib/tests/baseline_images/test_patches/patch_custom_linestyle.pdf new file mode 100644 index 000000000000..c7db3f087979 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_patches/patch_custom_linestyle.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_patches/patch_custom_linestyle.png b/lib/matplotlib/tests/baseline_images/test_patches/patch_custom_linestyle.png new file mode 100644 index 000000000000..1c2a6bf40164 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_patches/patch_custom_linestyle.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_patches/patch_custom_linestyle.svg b/lib/matplotlib/tests/baseline_images/test_patches/patch_custom_linestyle.svg new file mode 100644 index 000000000000..3367ef4c3142 --- /dev/null +++ b/lib/matplotlib/tests/baseline_images/test_patches/patch_custom_linestyle.svg @@ -0,0 +1,311 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/matplotlib/tests/test_patches.py b/lib/matplotlib/tests/test_patches.py index 166b2c820583..d469f0232e75 100644 --- a/lib/matplotlib/tests/test_patches.py +++ b/lib/matplotlib/tests/test_patches.py @@ -9,7 +9,8 @@ from matplotlib.patches import Rectangle from matplotlib.testing.decorators import image_comparison import matplotlib.pyplot as plt -from matplotlib import patches as mpatches +import matplotlib.patches as mpatches +import matplotlib.collections as mcollections from matplotlib import path as mpath from matplotlib import transforms as mtrans @@ -104,3 +105,108 @@ def test_clip_to_bbox(): result_path, alpha=0.5, facecolor='green', lw=4, edgecolor='black') ax.add_patch(result_patch) + + +@image_comparison(baseline_images=['patch_alpha_coloring'], remove_text=True) +def test_patch_alpha_coloring(): + """ + Test checks that the patch and collection are rendered with the specified + alpha values in their facecolor and edgecolor. + """ + star = mpath.Path.unit_regular_star(6) + circle = mpath.Path.unit_circle() + # concatenate the star with an internal cutout of the circle + verts = np.concatenate([circle.vertices, star.vertices[::-1]]) + codes = np.concatenate([circle.codes, star.codes]) + cut_star1 = mpath.Path(verts, codes) + cut_star2 = mpath.Path(verts + 1, codes) + + ax = plt.axes() + patch = mpatches.PathPatch(cut_star1, + linewidth=5, linestyle='dashdot', + facecolor=(1, 0, 0, 0.5), + edgecolor=(0, 0, 1, 0.75)) + ax.add_patch(patch) + + col = mcollections.PathCollection([cut_star2], + linewidth=5, linestyles='dashdot', + facecolor=(1, 0, 0, 0.5), + edgecolor=(0, 0, 1, 0.75)) + ax.add_collection(col) + + ax.set_xlim([-1, 2]) + ax.set_ylim([-1, 2]) + + + +@image_comparison(baseline_images=['patch_alpha_override'], remove_text=True) +def test_patch_alpha_override(): + """ + Test checks that specifying an alpha attribute for a patch or collection + will override any alpha component of the facecolor or edgecolor. + """ + star = mpath.Path.unit_regular_star(6) + circle = mpath.Path.unit_circle() + # concatenate the star with an internal cutout of the circle + verts = np.concatenate([circle.vertices, star.vertices[::-1]]) + codes = np.concatenate([circle.codes, star.codes]) + cut_star1 = mpath.Path(verts, codes) + cut_star2 = mpath.Path(verts + 1, codes) + + ax = plt.axes() + patch = mpatches.PathPatch(cut_star1, + linewidth=5, linestyle='dashdot', + alpha=0.25, + facecolor=(1, 0, 0, 0.5), + edgecolor=(0, 0, 1, 0.75)) + ax.add_patch(patch) + + col = mcollections.PathCollection([cut_star2], + linewidth=5, linestyles='dashdot', + alpha=0.25, + facecolor=(1, 0, 0, 0.5), + edgecolor=(0, 0, 1, 0.75)) + ax.add_collection(col) + + ax.set_xlim([-1, 2]) + ax.set_ylim([-1, 2]) + + + +@image_comparison(baseline_images=['patch_custom_linestyle'], remove_text=True) +def test_patch_custom_linestyle(): + """ + A test to check that patches and collections accept custom dash patterns + as linestyle and that they display correctly. + """ + star = mpath.Path.unit_regular_star(6) + circle = mpath.Path.unit_circle() + # concatenate the star with an internal cutout of the circle + verts = np.concatenate([circle.vertices, star.vertices[::-1]]) + codes = np.concatenate([circle.codes, star.codes]) + cut_star1 = mpath.Path(verts, codes) + cut_star2 = mpath.Path(verts + 1, codes) + + ax = plt.axes() + patch = mpatches.PathPatch(cut_star1, + linewidth=5, linestyle=(0.0, (5.0, 7.0, 10.0, 7.0)), + facecolor=(1, 0, 0), + edgecolor=(0, 0, 1)) + ax.add_patch(patch) + + col = mcollections.PathCollection([cut_star2], + linewidth=5, linestyles=[(0.0, (5.0, 7.0, 10.0, 7.0))], + facecolor=(1, 0, 0), + edgecolor=(0, 0, 1)) + ax.add_collection(col) + + ax.set_xlim([-1, 2]) + ax.set_ylim([-1, 2]) + + + + + +if __name__=='__main__': + import nose + nose.runmodule(argv=['-s','--with-doctest'], exit=False) diff --git a/src/_backend_agg.cpp b/src/_backend_agg.cpp index 96ddf920e3a4..31487bf8361e 100644 --- a/src/_backend_agg.cpp +++ b/src/_backend_agg.cpp @@ -195,6 +195,7 @@ GCAgg::GCAgg(const Py::Object &gc, double dpi) : _VERBOSE("GCAgg::GCAgg"); linewidth = points_to_pixels(gc.getAttr("_linewidth")) ; alpha = Py::Float(gc.getAttr("_alpha")); + forced_alpha = Py::Boolean(gc.getAttr("_forced_alpha")); color = get_color(gc); _set_antialiased(gc); _set_linecap(gc); @@ -221,12 +222,11 @@ GCAgg::get_color(const Py::Object& gc) _VERBOSE("GCAgg::get_color"); Py::Tuple rgb = Py::Tuple(gc.getAttr("_rgb")); - double alpha = Py::Float(gc.getAttr("_alpha")); - double r = Py::Float(rgb[0]); double g = Py::Float(rgb[1]); double b = Py::Float(rgb[2]); - return agg::rgba(r, g, b, alpha); + double a = Py::Float(rgb[3]); + return agg::rgba(r, g, b, a); } @@ -458,7 +458,7 @@ RendererAgg::set_clipbox(const Py::Object& cliprect, R& rasterizer) std::pair -RendererAgg::_get_rgba_face(const Py::Object& rgbFace, double alpha) +RendererAgg::_get_rgba_face(const Py::Object& rgbFace, double alpha, bool forced_alpha) { _VERBOSE("RendererAgg::_get_rgba_face"); std::pair face; @@ -471,7 +471,14 @@ RendererAgg::_get_rgba_face(const Py::Object& rgbFace, double alpha) { face.first = true; Py::Tuple rgb = Py::Tuple(rgbFace); - face.second = rgb_to_color(rgb, alpha); + if (forced_alpha || rgb.length() < 4) + { + face.second = rgb_to_color(rgb, alpha); + } + else + { + face.second = rgb_to_color(rgb, Py::Float(rgb[3])); + } } return face; } @@ -683,7 +690,7 @@ RendererAgg::draw_markers(const Py::Tuple& args) curve_t path_curve(path_snapped); path_curve.rewind(0); - facepair_t face = _get_rgba_face(face_obj, gc.alpha); + facepair_t face = _get_rgba_face(face_obj, gc.alpha, gc.forced_alpha); //maxim's suggestions for cached scanlines agg::scanline_storage_aa8 scanlines; @@ -1401,7 +1408,7 @@ RendererAgg::draw_path(const Py::Tuple& args) if (args.size() == 4) face_obj = args[3]; - facepair_t face = _get_rgba_face(face_obj, gc.alpha); + facepair_t face = _get_rgba_face(face_obj, gc.alpha, gc.forced_alpha); theRasterizer.reset_clipping(); rendererBase.reset_clipping(true); diff --git a/src/_backend_agg.h b/src/_backend_agg.h index d3067726ed3f..00f71c7c3bb5 100644 --- a/src/_backend_agg.h +++ b/src/_backend_agg.h @@ -122,6 +122,7 @@ class GCAgg double linewidth; double alpha; + bool forced_alpha; agg::rgba color; Py::Object cliprect; @@ -238,7 +239,7 @@ class RendererAgg: public Py::PythonExtension protected: double points_to_pixels(const Py::Object& points); agg::rgba rgb_to_color(const Py::SeqBase& rgb, double alpha); - facepair_t _get_rgba_face(const Py::Object& rgbFace, double alpha); + facepair_t _get_rgba_face(const Py::Object& rgbFace, double alpha, bool forced_alpha); template void set_clipbox(const Py::Object& cliprect, R& rasterizer); diff --git a/src/_macosx.m b/src/_macosx.m index b33e6d0a613a..244133435af5 100644 --- a/src/_macosx.m +++ b/src/_macosx.m @@ -446,6 +446,7 @@ - (int)index; CGContextRef cr; NSSize size; int level; + BOOL forced_alpha; CGFloat color[4]; float dpi; } GraphicsContext; @@ -512,6 +513,7 @@ static int _get_snap(GraphicsContext* self, enum e_snap_mode* mode) if (!self) return NULL; self->cr = NULL; self->level = 0; + self->forced_alpha = FALSE; #ifndef COMPILING_FOR_10_5 if (ngc==0) @@ -589,7 +591,8 @@ static int _get_snap(GraphicsContext* self, enum e_snap_mode* mode) GraphicsContext_set_alpha (GraphicsContext* self, PyObject* args) { float alpha; - if (!PyArg_ParseTuple(args, "f", &alpha)) return NULL; + int forced = 0; + if (!PyArg_ParseTuple(args, "f|i", &alpha, &forced)) return NULL; CGContextRef cr = self->cr; if (!cr) { @@ -597,8 +600,7 @@ static int _get_snap(GraphicsContext* self, enum e_snap_mode* mode) return NULL; } CGContextSetAlpha(cr, alpha); - - self->color[3] = alpha; + self->forced_alpha = (BOOL)(forced || (alpha != 1.0)); Py_INCREF(Py_None); return Py_None; @@ -832,8 +834,8 @@ static int _get_snap(GraphicsContext* self, enum e_snap_mode* mode) static PyObject* GraphicsContext_set_foreground(GraphicsContext* self, PyObject* args) { - float r, g, b; - if(!PyArg_ParseTuple(args, "(fff)", &r, &g, &b)) return NULL; + float r, g, b, a; + if(!PyArg_ParseTuple(args, "(ffff)", &r, &g, &b, &a)) return NULL; CGContextRef cr = self->cr; if (!cr) @@ -842,13 +844,21 @@ static int _get_snap(GraphicsContext* self, enum e_snap_mode* mode) return NULL; } - CGContextSetRGBStrokeColor(cr, r, g, b, 1.0); - CGContextSetRGBFillColor(cr, r, g, b, 1.0); + if (self->forced_alpha) + { + // Transparency is applied to layer + // Let it override (rather than multiply with) the alpha of the + // stroke/fill colors + a = 1.0; + } + + CGContextSetRGBStrokeColor(cr, r, g, b, a); + CGContextSetRGBFillColor(cr, r, g, b, a); self->color[0] = r; self->color[1] = g; self->color[2] = b; - self->color[3] = 1.0; + self->color[3] = a; Py_INCREF(Py_None); return Py_None; @@ -977,14 +987,18 @@ static int _get_snap(GraphicsContext* self, enum e_snap_mode* mode) if(rgbFace) { - float r, g, b; - if (!PyArg_ParseTuple(rgbFace, "fff", &r, &g, &b)) + float r, g, b, a; + a = 1.0; + if (!PyArg_ParseTuple(rgbFace, "fff|f", &r, &g, &b, &a)) return NULL; + if (self->forced_alpha) + a = 1.0; + n = _draw_path(cr, iterator, INT_MAX); if (n > 0) { CGContextSaveGState(cr); - CGContextSetRGBFillColor(cr, r, g, b, 1.0); + CGContextSetRGBFillColor(cr, r, g, b, a); CGContextDrawPath(cr, kCGPathFillStroke); CGContextRestoreGState(cr); } @@ -1083,7 +1097,7 @@ static int _get_snap(GraphicsContext* self, enum e_snap_mode* mode) PyObject* rgbFace; int ok; - float r, g, b; + float r, g, b, a; CGMutablePathRef marker; void* iterator; @@ -1112,12 +1126,15 @@ static int _get_snap(GraphicsContext* self, enum e_snap_mode* mode) if (rgbFace) { - ok = PyArg_ParseTuple(rgbFace, "fff", &r, &g, &b); + a = 1.0; + ok = PyArg_ParseTuple(rgbFace, "fff|f", &r, &g, &b, &a); if (!ok) { return NULL; } - CGContextSetRGBFillColor(cr, r, g, b, 1.0); + if (self->forced_alpha) + a = 1.0; + CGContextSetRGBFillColor(cr, r, g, b, a); } ok = _get_snap(self, &mode);