Skip to content

PDF backend can now do alpha on hatches. #17049

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

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 0 additions & 6 deletions doc/users/next_whats_new/2020-03-24-svg-hatch-alpha.rst

This file was deleted.

6 changes: 6 additions & 0 deletions doc/users/next_whats_new/hatch-alpha.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
The SVG and PDF backends can now render hatches with transparency
-----------------------------------------------------------------

The SVG and PDF backends now respect the hatch stroke alpha. Useful applications
are, among others, semi-transparent hatches as a subtle way to differentiate
columns in bar plots.
Copy link
Member

Choose a reason for hiding this comment

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

This could be a nice demo in the gallery, or perhaps another subplot in https://matplotlib.org/3.2.0/gallery/lines_bars_and_markers/filled_step.html

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Cool idea. As a side note, because hatch color/alpha is linked to the Patch's edge's, a simple plot with hatch alpha will usually not look so good because the translucent edge will half-overlap with the fill. One can plot edge-less with lw=0, but to have an opaque outline one must replot a full opacity, edge-only plot on top of the first.
It's a bit hackish. An independent hatch style API (#7059) would go a long way to simplifying this.

25 changes: 19 additions & 6 deletions lib/matplotlib/backends/backend_pdf.py
Original file line number Diff line number Diff line change
Expand Up @@ -1288,14 +1288,18 @@ def _write_soft_mask_groups(self):
self.output(*content)
self.endStream()

def hatchPattern(self, hatch_style):
def hatchPattern(self, hatch_style, forced_alpha):
# The colors may come in as numpy arrays, which aren't hashable
if hatch_style is not None:
edge, face, hatch = hatch_style
if edge is not None:
edge = tuple(edge)
if forced_alpha: # reset alpha if forced
edge = edge[:3] + (1.0,)
if face is not None:
face = tuple(face)
if forced_alpha: # reset alpha if forced
face = face[:3] + (1.0,)
hatch_style = (edge, face, hatch)

pattern = self.hatchPatterns.get(hatch_style, None)
Expand All @@ -1310,10 +1314,14 @@ def writeHatches(self):
hatchDict = dict()
sidelen = 72.0
for hatch_style, name in self.hatchPatterns.items():
stroke_rgb, fill_rgb, path = hatch_style
ob = self.reserveObject('hatch pattern')
hatchDict[name] = ob
res = {'Procsets':
[Name(x) for x in "PDF Text ImageB ImageC ImageI".split()]}
if stroke_rgb[3] != 1.0:
res['ExtGState'] = self._extGStateObject

self.beginStream(
ob.id, None,
{'Type': Name('Pattern'),
Expand All @@ -1324,7 +1332,9 @@ def writeHatches(self):
# Change origin to match Agg at top-left.
'Matrix': [1, 0, 0, 1, 0, self.height * 72]})

stroke_rgb, fill_rgb, path = hatch_style
if stroke_rgb[3] != 1.0:
gstate = self.alphaState((stroke_rgb[3], fill_rgb[3]))
self.output(gstate, Op.setgstate)
self.output(stroke_rgb[0], stroke_rgb[1], stroke_rgb[2],
Op.setrgb_stroke)
if fill_rgb is not None:
Expand Down Expand Up @@ -2245,15 +2255,15 @@ def alpha_cmd(self, alpha, forced, effective_alphas):
name = self.file.alphaState(effective_alphas)
return [name, Op.setgstate]

def hatch_cmd(self, hatch, hatch_color):
def hatch_cmd(self, hatch, hatch_color, forced_alpha):
if not hatch:
if self._fillcolor is not None:
return self.fillcolor_cmd(self._fillcolor)
else:
return [Name('DeviceRGB'), Op.setcolorspace_nonstroke]
else:
hatch_style = (hatch_color, self._fillcolor, hatch)
name = self.file.hatchPattern(hatch_style)
name = self.file.hatchPattern(hatch_style, forced_alpha)
return [Name('Pattern'), Op.setcolorspace_nonstroke,
name, Op.setcolor_nonstroke]

Expand Down Expand Up @@ -2311,13 +2321,15 @@ def clip_cmd(self, cliprect, clippath):
(('_cliprect', '_clippath'), clip_cmd),
(('_alpha', '_forced_alpha', '_effective_alphas'), alpha_cmd),
(('_capstyle',), capstyle_cmd),
# If you change the next line also fix the check in delta
(('_fillcolor',), fillcolor_cmd),
(('_joinstyle',), joinstyle_cmd),
(('_linewidth',), linewidth_cmd),
(('_dashes',), dash_cmd),
(('_rgb',), rgb_cmd),
# must come after fillcolor and rgb
(('_hatch', '_hatch_color'), hatch_cmd),
# If you change the next line also fix the check in delta
(('_hatch', '_hatch_color', '_forced_alpha'), hatch_cmd),
)

def delta(self, other):
Expand Down Expand Up @@ -2346,7 +2358,8 @@ def delta(self, other):
break

# Need to update hatching if we also updated fillcolor
if params == ('_hatch', '_hatch_color') and fill_performed:
if (params == ('_hatch', '_hatch_color', '_forced_alpha')
and fill_performed):
different = True

if different:
Expand Down
11 changes: 8 additions & 3 deletions lib/matplotlib/backends/backend_svg.py
Original file line number Diff line number Diff line change
Expand Up @@ -350,12 +350,17 @@ def _get_hatch(self, gc, rgbFace):
"""
Create a new hatch pattern
"""
forced_alpha = gc.get_forced_alpha()
if rgbFace is not None:
rgbFace = tuple(rgbFace)
if forced_alpha: # reset alpha if forced
rgbFace = rgbFace[:3] + (1.0,)
edge = gc.get_hatch_color()
if edge is not None:
edge = tuple(edge)
dictkey = (gc.get_hatch(), rgbFace, edge)
if forced_alpha: # reset alpha if forced
edge = edge[:3] + (1.0,)
dictkey = (gc.get_hatch(), rgbFace, edge, gc.get_forced_alpha())
oid = self._hatchd.get(dictkey)
if oid is None:
oid = self._make_id('h', dictkey)
Expand Down Expand Up @@ -398,8 +403,8 @@ def _write_hatches(self):
'stroke-linecap': 'butt',
'stroke-linejoin': 'miter'
}
if stroke[3] < 1:
hatch_style['stroke-opacity'] = str(stroke[3])
if stroke[3] != 1.0:
hatch_style['stroke-opacity'] = short_float_fmt(stroke[3])
writer.element(
'path',
d=path_data,
Expand Down
1 change: 1 addition & 0 deletions lib/matplotlib/collections.py
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,7 @@ def draw(self, renderer):
gc = renderer.new_gc()
self._set_gc_clip(gc)
gc.set_snap(self.get_snap())
gc.set_alpha(self._alpha)

if self._hatch:
gc.set_hatch(self._hatch)
Expand Down
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified lib/matplotlib/tests/baseline_images/test_artist/hatching.pdf
Binary file not shown.
Binary file modified lib/matplotlib/tests/baseline_images/test_artist/hatching.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading