Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit a1af495

Browse files
committed
Added new blending modes to LightSource for more realistic rendering
1 parent 4faa454 commit a1af495

1 file changed

Lines changed: 85 additions & 11 deletions

File tree

lib/matplotlib/colors.py

Lines changed: 85 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1457,7 +1457,6 @@ def hsv_to_rgb(hsv):
14571457

14581458
return rgb
14591459

1460-
14611460
class LightSource(object):
14621461
"""
14631462
Create a light source coming from the specified azimuth and elevation.
@@ -1469,9 +1468,8 @@ class LightSource(object):
14691468
The :meth:`shade_rgb`
14701469
The :meth:`hillshade` produces an illumination map of a surface.
14711470
"""
1472-
def __init__(self, azdeg=315, altdeg=45,
1473-
hsv_min_val=0, hsv_max_val=1, hsv_min_sat=1,
1474-
hsv_max_sat=0):
1471+
def __init__(self, azdeg=315, altdeg=45, hsv_min_val=0, hsv_max_val=1,
1472+
hsv_min_sat=1, hsv_max_sat=0):
14751473
"""
14761474
Specify the azimuth (measured clockwise from south) and altitude
14771475
(measured up from the plane of the surface) of the light source
@@ -1567,8 +1565,8 @@ def hillshade(self, elevation, vert_exag=1, dx=1, dy=1, fraction=1.):
15671565
np.clip(intensity, 0, 1, intensity)
15681566
return intensity
15691567

1570-
def shade(self, data, cmap, norm=None, vert_exag=1, dx=1, dy=1, fraction=1,
1571-
**kwargs):
1568+
def shade(self, data, cmap, norm=None, blend_mode='hsv',
1569+
vert_exag=1, dx=1, dy=1, fraction=1, **kwargs):
15721570
"""
15731571
Combine colormapped data values with an illumination intensity map
15741572
(a.k.a. "hillshade") of the values.
@@ -1586,6 +1584,16 @@ def shade(self, data, cmap, norm=None, vert_exag=1, dx=1, dy=1, fraction=1,
15861584
norm : `~matplotlib.colors.Normalize` instance, optional
15871585
The normalization used to scale values before colormapping. If
15881586
None, the input will be linearly scaled between its min and max.
1587+
blend_mode : {'hsv', 'overlay', 'soft'} or callable, optional
1588+
The type of blending used to combine the colormapped data values
1589+
with the illumination intensity. For backwards compatibility, this
1590+
defaults to "hsv". Note that for most topographic surfaces,
1591+
"overlay" or "soft" appear more visually realistic. If a
1592+
user-defined function is supplied, it is expected to combine an
1593+
MxNx3 RGB array of floats (ranging 0 to 1) with an MxNx1 hillshade
1594+
array (also 0 to 1). (Call signature `func(rgb, illum, **kwargs)`)
1595+
Additional kwargs supplied to this function will be passed on to
1596+
the *blend_mode* function.
15891597
vert_exag : number, optional
15901598
The amount to exaggerate the elevation values by when calculating
15911599
illumination. This can be used either to correct for differences in
@@ -1622,8 +1630,8 @@ def shade(self, data, cmap, norm=None, vert_exag=1, dx=1, dy=1, fraction=1,
16221630
rgb0[..., :3] = rgb1[..., :3]
16231631
return rgb0
16241632

1625-
def shade_rgb(self, rgb, elevation, fraction=1., vert_exag=1, dx=1, dy=1,
1626-
**kwargs):
1633+
def shade_rgb(self, rgb, elevation, fraction=1., blend_mode='hsv',
1634+
vert_exag=1, dx=1, dy=1, **kwargs):
16271635
"""
16281636
Take the input RGB array (ny*nx*3) adjust their color values
16291637
to given the impression of a shaded relief map with a
@@ -1646,6 +1654,16 @@ def shade_rgb(self, rgb, elevation, fraction=1., vert_exag=1, dx=1, dy=1,
16461654
decreasing contrast. Note that this is not mathematically or
16471655
visually the same as increasing/decreasing the vertical
16481656
exaggeration.
1657+
blend_mode : {'hsv', 'overlay', 'soft'} or callable, optional
1658+
The type of blending used to combine the colormapped data values
1659+
with the illumination intensity. For backwards compatibility, this
1660+
defaults to "hsv". Note that for most topographic surfaces,
1661+
"overlay" or "soft" appear more visually realistic. If a
1662+
user-defined function is supplied, it is expected to combine an
1663+
MxNx3 RGB array of floats (ranging 0 to 1) with an MxNx1 hillshade
1664+
array (also 0 to 1). (Call signature `func(rgb, illum, **kwargs)`)
1665+
Additional kwargs supplied to this function will be passed on to
1666+
the *blend_mode* function.
16491667
vert_exag : number, optional
16501668
The amount to exaggerate the elevation values by when calculating
16511669
illumination. This can be used either to correct for differences in
@@ -1656,15 +1674,31 @@ def shade_rgb(self, rgb, elevation, fraction=1., vert_exag=1, dx=1, dy=1,
16561674
The x-spacing (columns) of the input *elevation* grid.
16571675
dy : number, optional
16581676
The y-spacing (rows) of the input *elevation* grid.
1659-
Additional kwargs are passed on to :meth:`blend_hsv`.
1677+
Additional kwargs are passed on to the *blend_mode* function.
16601678
16611679
Returns
16621680
-------
16631681
shaded_rgb : ndarray
16641682
An MxNx3 array of floats ranging between 0-1.
16651683
"""
1666-
intensity = self.hillshade(elevation, fraction=fraction)
1667-
return self.blend_hsv(rgb, intensity, **kwargs)
1684+
# Calculate the "hillshade" intensity.
1685+
intensity = self.hillshade(elevation, vert_exag, dx, dy, fraction)
1686+
intensity = intensity[..., np.newaxis]
1687+
1688+
# Blend the hillshade and rgb data using the specified mode
1689+
lookup = {
1690+
'hsv':self.blend_hsv,
1691+
'soft':self.blend_soft_light,
1692+
'overlay':self.blend_overlay,
1693+
}
1694+
if blend_mode in lookup:
1695+
return lookup[blend_mode](rgb, intensity, **kwargs)
1696+
else:
1697+
try:
1698+
return blend_mode(rgb, intensity, **kwargs)
1699+
except TypeError:
1700+
msg = '"blend_mode" must be callable or one of {}'
1701+
raise ValueError(msg.format(lookup.keys))
16681702

16691703
def blend_hsv(self, rgb, intensity, hsv_max_sat=None, hsv_max_val=None,
16701704
hsv_min_val=None, hsv_min_sat=None):
@@ -1716,6 +1750,7 @@ def blend_hsv(self, rgb, intensity, hsv_max_sat=None, hsv_max_val=None,
17161750
hsv_min_val = self.hsv_min_val
17171751

17181752
# Expects a 2D intensity array scaled between -1 to 1...
1753+
intensity = intensity[..., 0]
17191754
intensity = 2 * intensity - 1
17201755

17211756
# convert to rgb, then rgb to hsv
@@ -1747,6 +1782,45 @@ def blend_hsv(self, rgb, intensity, hsv_max_sat=None, hsv_max_val=None,
17471782
# convert modified hsv back to rgb.
17481783
return hsv_to_rgb(hsv)
17491784

1785+
def blend_soft_light(self, rgb, intensity):
1786+
"""
1787+
Combines an rgb image with an intensity map using "soft light"
1788+
blending. Uses the "pegtop" formula.
1789+
1790+
Parameters
1791+
----------
1792+
rgb : ndarray
1793+
An MxNx3 RGB array of floats ranging from 0 to 1 (color image).
1794+
intensity : ndarray
1795+
An MxNx1 array of floats ranging from 0 to 1 (grayscale image).
1796+
1797+
Returns
1798+
-------
1799+
rgb : ndarray
1800+
An MxNx3 RGB array representing the combined images.
1801+
"""
1802+
return 2 * intensity * rgb + (1 - 2 * intensity) * rgb**2
1803+
1804+
def blend_overlay(self, rgb, intensity):
1805+
"""
1806+
Combines an rgb image with an intensity map using "overlay" blending.
1807+
1808+
Parameters
1809+
----------
1810+
rgb : ndarray
1811+
An MxNx3 RGB array of floats ranging from 0 to 1 (color image).
1812+
intensity : ndarray
1813+
An MxNx1 array of floats ranging from 0 to 1 (grayscale image).
1814+
1815+
Returns
1816+
-------
1817+
rgb : ndarray
1818+
An MxNx3 RGB array representing the combined images.
1819+
"""
1820+
low = 2 * intensity * rgb
1821+
high = 1 - 2 * (1 - intensity) * (1 - rgb)
1822+
return np.where(rgb <= 0.5, low, high)
1823+
17501824
def from_levels_and_colors(levels, colors, extend='neither'):
17511825
"""
17521826
A helper routine to generate a cmap and a norm instance which

0 commit comments

Comments
 (0)