Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 0e2b40f

Browse filesBrowse files
committed
Implement alpha for Gouraud shading
1 parent d135d87 commit 0e2b40f
Copy full SHA for 0e2b40f

File tree

Expand file treeCollapse file tree

1 file changed

+133
-22
lines changed
Filter options
Expand file treeCollapse file tree

1 file changed

+133
-22
lines changed

‎lib/matplotlib/backends/backend_pdf.py

Copy file name to clipboardExpand all lines: lib/matplotlib/backends/backend_pdf.py
+133-22Lines changed: 133 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -481,7 +481,7 @@ def __init__(self, filename, metadata=None):
481481
self.pagesObject = self.reserveObject('pages')
482482
self.pageList = []
483483
self.fontObject = self.reserveObject('fonts')
484-
self.alphaStateObject = self.reserveObject('extended graphics states')
484+
self.extGStateObject = self.reserveObject('extended graphics states')
485485
self.hatchObject = self.reserveObject('tiling patterns')
486486
self.gouraudObject = self.reserveObject('Gouraud triangles')
487487
self.XObjectObject = self.reserveObject('external objects')
@@ -519,6 +519,9 @@ def __init__(self, filename, metadata=None):
519519

520520
self.alphaStates = {} # maps alpha values to graphics state objects
521521
self.alphaStateName = (Name(f'A{i}') for i in itertools.count(1))
522+
self.softmaskStates = {}
523+
self.softmaskStateName = (Name(f'SM{i}') for i in itertools.count(1))
524+
self.softmaskGroups = []
522525
# reproducible writeHatches needs an ordered dict:
523526
self.hatchPatterns = collections.OrderedDict()
524527
self.hatchPatternName = (Name(f'H{i}') for i in itertools.count(1))
@@ -543,7 +546,7 @@ def __init__(self, filename, metadata=None):
543546
# ColorSpace Pattern Shading Properties
544547
resources = {'Font': self.fontObject,
545548
'XObject': self.XObjectObject,
546-
'ExtGState': self.alphaStateObject,
549+
'ExtGState': self.extGStateObject,
547550
'Pattern': self.hatchObject,
548551
'Shading': self.gouraudObject,
549552
'ProcSet': procsets}
@@ -593,9 +596,8 @@ def finalize(self):
593596

594597
self.endStream()
595598
self.writeFonts()
596-
self.writeObject(
597-
self.alphaStateObject,
598-
{val[0]: val[1] for val in self.alphaStates.values()})
599+
self.writeExtGSTates()
600+
self.writeSoftmaskGroups()
599601
self.writeHatches()
600602
self.writeGouraudTriangles()
601603
xobjects = {
@@ -1219,6 +1221,72 @@ def alphaState(self, alpha):
12191221
'CA': alpha[0], 'ca': alpha[1]})
12201222
return name
12211223

1224+
def softmaskState(self, smask):
1225+
"""Return the name of an ExtGState that sets the soft mask to the given shading.
1226+
1227+
Parameters
1228+
----------
1229+
smask : Reference
1230+
reference to a shading in DeviceGray color space, whose luminosity is
1231+
to be used as the alpha channel
1232+
1233+
Returns
1234+
-------
1235+
Name
1236+
"""
1237+
1238+
state = self.softmaskStates.get(smask, None)
1239+
if state is not None:
1240+
return state[0]
1241+
1242+
name = next(self.softmaskStateName)
1243+
groupOb = self.reserveObject('transparency group for soft mask')
1244+
self.softmaskStates[smask] = (
1245+
name,
1246+
{
1247+
'Type': Name('ExtGState'),
1248+
'AIS': False,
1249+
'SMask': {
1250+
'Type': Name('Mask'),
1251+
'S': Name('Luminosity'),
1252+
'BC': [1],
1253+
'G': groupOb
1254+
}
1255+
}
1256+
)
1257+
self.softmaskGroups.append((
1258+
groupOb,
1259+
{
1260+
'Type': Name('XObject'),
1261+
'Subtype': Name('Form'),
1262+
'FormType': 1,
1263+
'Group': {
1264+
'S': Name('Transparency'),
1265+
'CS': Name('DeviceGray')
1266+
},
1267+
'Matrix': [1, 0, 0, 1, 0, 0],
1268+
'Resources': {'Shading': {'S': smask}},
1269+
'BBox': [0, 0, 1, 1]
1270+
},
1271+
[Name('S'), Op.shading]
1272+
))
1273+
return name
1274+
1275+
def writeExtGSTates(self):
1276+
self.writeObject(
1277+
self.extGStateObject,
1278+
dict(itertools.chain(
1279+
self.alphaStates.values(),
1280+
self.softmaskStates.values()
1281+
))
1282+
)
1283+
1284+
def writeSoftmaskGroups(self):
1285+
for ob, attributes, content in self.softmaskGroups:
1286+
self.beginStream(ob.id, None, attributes)
1287+
self.output(*content)
1288+
self.endStream()
1289+
12221290
def hatchPattern(self, hatch_style):
12231291
# The colors may come in as numpy arrays, which aren't hashable
12241292
if hatch_style is not None:
@@ -1276,18 +1344,39 @@ def writeHatches(self):
12761344
self.writeObject(self.hatchObject, hatchDict)
12771345

12781346
def addGouraudTriangles(self, points, colors):
1347+
"""Add a Gouraud triangle shading
1348+
1349+
Parameters
1350+
----------
1351+
1352+
points : np.ndarray
1353+
triangle vertices, shape (n, 3, 2)
1354+
where n = number of triangles, 3 = vertices, 2 = x, y
1355+
colors : np.ndarray
1356+
vertex colors, shape (n, 3, 1) or (n, 3, 4)
1357+
as with points, but last dimension is either (gray,) or (r, g, b, alpha)
1358+
1359+
Returns
1360+
-------
1361+
Name, Reference
1362+
"""
12791363
name = Name('GT%d' % len(self.gouraudTriangles))
1280-
self.gouraudTriangles.append((name, points, colors))
1281-
return name
1364+
ob = self.reserveObject(f'Gouraud triangle {name}')
1365+
self.gouraudTriangles.append((name, ob, points, colors))
1366+
return name, ob
12821367

12831368
def writeGouraudTriangles(self):
12841369
gouraudDict = dict()
1285-
for name, points, colors in self.gouraudTriangles:
1286-
ob = self.reserveObject('Gouraud triangle')
1370+
for name, ob, points, colors in self.gouraudTriangles:
12871371
gouraudDict[name] = ob
12881372
shape = points.shape
12891373
flat_points = points.reshape((shape[0] * shape[1], 2))
1290-
flat_colors = colors.reshape((shape[0] * shape[1], 4))
1374+
colordim = colors.shape[2]
1375+
assert colordim in (1, 4)
1376+
flat_colors = colors.reshape((shape[0] * shape[1], colordim))
1377+
if colordim == 4:
1378+
# strip the alpha channel
1379+
colordim = 3
12911380
points_min = np.min(flat_points, axis=0) - (1 << 8)
12921381
points_max = np.max(flat_points, axis=0) + (1 << 8)
12931382
factor = 0xffffffff / (points_max - points_min)
@@ -1298,21 +1387,20 @@ def writeGouraudTriangles(self):
12981387
'BitsPerCoordinate': 32,
12991388
'BitsPerComponent': 8,
13001389
'BitsPerFlag': 8,
1301-
'ColorSpace': Name('DeviceRGB'),
1302-
'AntiAlias': True,
1303-
'Decode': [points_min[0], points_max[0],
1304-
points_min[1], points_max[1],
1305-
0, 1, 0, 1, 0, 1]
1390+
'ColorSpace': Name('DeviceRGB' if colordim == 3 else 'DeviceGray'),
1391+
'AntiAlias': False,
1392+
'Decode': ([points_min[0], points_max[0], points_min[1], points_max[1]]
1393+
+ [0, 1] * colordim),
13061394
})
13071395

13081396
streamarr = np.empty(
13091397
(shape[0] * shape[1],),
13101398
dtype=[('flags', 'u1'),
13111399
('points', '>u4', (2,)),
1312-
('colors', 'u1', (3,))])
1400+
('colors', 'u1', (colordim,))])
13131401
streamarr['flags'] = 0
13141402
streamarr['points'] = (flat_points - points_min) * factor
1315-
streamarr['colors'] = flat_colors[:, :3] * 255.0
1403+
streamarr['colors'] = flat_colors[:, :colordim] * 255.0
13161404

13171405
self.write(streamarr.tostring())
13181406
self.endStream()
@@ -1808,20 +1896,43 @@ def draw_gouraud_triangle(self, gc, points, colors, trans):
18081896

18091897
def draw_gouraud_triangles(self, gc, points, colors, trans):
18101898
assert len(points) == len(colors)
1899+
if len(points) == 0:
1900+
return
18111901
assert points.ndim == 3
18121902
assert points.shape[1] == 3
18131903
assert points.shape[2] == 2
18141904
assert colors.ndim == 3
18151905
assert colors.shape[1] == 3
1816-
assert colors.shape[2] == 4
1817-
1906+
assert colors.shape[2] in (1, 4)
1907+
18181908
shape = points.shape
18191909
points = points.reshape((shape[0] * shape[1], 2))
18201910
tpoints = trans.transform(points)
18211911
tpoints = tpoints.reshape(shape)
1822-
name = self.file.addGouraudTriangles(tpoints, colors)
1823-
self.check_gc(gc)
1824-
self.file.output(name, Op.shading)
1912+
name, _ = self.file.addGouraudTriangles(tpoints, colors)
1913+
output = self.file.output
1914+
1915+
if colors.shape[2] == 1:
1916+
# grayscale
1917+
gc.set_alpha(1.0)
1918+
self.check_gc(gc)
1919+
output(name, Op.shading)
1920+
return
1921+
1922+
alpha = colors[0, 0, 3]
1923+
if np.allclose(alpha, colors[:, :, 3]):
1924+
# single alpha value
1925+
gc.set_alpha(alpha)
1926+
self.check_gc(gc)
1927+
output(name, Op.shading)
1928+
else:
1929+
# varying alpha: use a soft mask
1930+
alpha = colors[:, :, 3][:, :, None]
1931+
_, smaskOb = self.file.addGouraudTriangles(tpoints, alpha)
1932+
gstate = self.file.softmaskState(smaskOb)
1933+
output(Op.gsave, gstate, Op.setgstate,
1934+
name, Op.shading,
1935+
Op.grestore)
18251936

18261937
def _setup_textpos(self, x, y, angle, oldx=0, oldy=0, oldangle=0):
18271938
if angle == oldangle == 0:

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.