|
| 1 | +""" |
| 2 | +.. _blend-modes: |
| 3 | +
|
| 4 | +================================ |
| 5 | +Blending and compositing artists |
| 6 | +================================ |
| 7 | +
|
| 8 | +When an artist is drawn on top of existing elements, the default behavior is for |
| 9 | +the artist's colors to be blended with the colors underneath the artist using |
| 10 | +:ref:`alpha-based transparency <colors_transparency>`. An *alpha* value of 1 |
| 11 | +normally means that the underlying colors are completely hidden. |
| 12 | +
|
| 13 | +An example of an alternative to normal alpha blending is the |
| 14 | +`"multiply" blend mode <https://en.wikipedia.org/wiki/Blend_modes#Multiply>`__, |
| 15 | +where the RGB channel values (in the range [0, 1]) of the artist colors and the |
| 16 | +underlying colors are multiplied together. For this blend mode, the underlying |
| 17 | +colors can still affect the final color even when the *alpha* value is 1. |
| 18 | +
|
| 19 | +""" |
| 20 | + |
| 21 | +import matplotlib.pyplot as plt |
| 22 | +from matplotlib.patches import Circle |
| 23 | + |
| 24 | +fig, ax = plt.subplots(figsize=(6, 3), layout='constrained') |
| 25 | + |
| 26 | +ax.text(1.5, 1.2, 'default behavior\n(a.k.a. "normal" blend mode)', ha='center') |
| 27 | +ax.add_patch(Circle((1, 0), 1, color='c', ec='none')) |
| 28 | +ax.add_patch(Circle((2, 0), 1, color='m', ec='none')) |
| 29 | +ax.add_patch(Circle((1.5, -0.87), 1, color='y', ec='none')) |
| 30 | + |
| 31 | +ax.text(5.5, 1.2, '"multiply" blend mode', ha='center') |
| 32 | +ax.add_patch(Circle((5, 0), 1, color='c', ec='none')) |
| 33 | +ax.add_patch(Circle((6, 0), 1, color='m', ec='none', blend_mode='multiply')) |
| 34 | +ax.add_patch(Circle((5.5, -0.87), 1, color='y', ec='none', blend_mode='multiply')) |
| 35 | + |
| 36 | +ax.set_xlim(-0.2, 7.2) |
| 37 | +ax.set_ylim(-1.9, 1.5) |
| 38 | +ax.set_aspect('equal') |
| 39 | +ax.axis('off') |
| 40 | + |
| 41 | + |
| 42 | +# %% |
| 43 | +# |
| 44 | +# Matplotlib provides a wide range of alternative behaviors to the default |
| 45 | +# ("normal") behavior: |
| 46 | +# |
| 47 | +# * 15 `blend modes`_ |
| 48 | +# * 6 `Porter-Duff compositing operators`_ |
| 49 | +# |
| 50 | +# (See also :ref:`blend-groups` for the additional capability of blending groups |
| 51 | +# of artists.) |
| 52 | +# |
| 53 | +# These behaviors are specified via the artist's ``blend_mode`` property. You |
| 54 | +# can set the property when creating a new artist, or you can call |
| 55 | +# `.Artist.set_blend_mode` on an existing artist. You can specify the behavior |
| 56 | +# either by string or by member of the `.BlendMode` enumeration. |
| 57 | +# |
| 58 | +# Below is a gallery illustrating the effect of each ``blend_mode`` option for a |
| 59 | +# variety of artists. Although each panel in the gallery has all of its artists |
| 60 | +# using the same blend mode, artists in the same axes can have different blend |
| 61 | +# modes from each other. Be aware that the background of the axes and the |
| 62 | +# background of the figure are artists as well, so their respective colors may |
| 63 | +# affect the blending result. |
| 64 | +# |
| 65 | +# Backends using the Agg renderer (the default) or the Cairo renderer natively |
| 66 | +# support all of these ``blend_mode`` options. The vector backends do not |
| 67 | +# natively support some of the options, but one can use rasterization (see |
| 68 | +# :doc:`/gallery/misc/rasterization_demo`) to achieve the blending effect if the |
| 69 | +# fixed resolution of the result is acceptable. |
| 70 | +# |
| 71 | +# .. _blend modes: https://en.wikipedia.org/wiki/Blend_modes |
| 72 | +# .. _Porter-Duff compositing operators: https://www.w3.org/TR/compositing-1/#advancedcompositing |
| 73 | + |
| 74 | + |
| 75 | +import matplotlib.pyplot as plt |
| 76 | +import numpy as np |
| 77 | + |
| 78 | +from matplotlib.patches import Circle, Rectangle |
| 79 | + |
| 80 | +N = 10 |
| 81 | +data = np.arange(N**2).reshape((N, N)) % (N-1) |
| 82 | + |
| 83 | +fig, axs = plt.subplots(3, 8, figsize=(10, 6), layout='tight') |
| 84 | +axs = axs.flatten() |
| 85 | +fig.set_facecolor('none') |
| 86 | + |
| 87 | +blend_modes = ['normal', |
| 88 | + |
| 89 | + # Blend modes |
| 90 | + 'multiply', 'screen', 'overlay', 'darken', 'lighten', |
| 91 | + 'color dodge', 'color burn', 'hard light', 'soft light', |
| 92 | + 'difference', 'exclusion', |
| 93 | + 'hue', 'saturation', 'color', 'luminosity', |
| 94 | + |
| 95 | + # Porter-Duff compositing operators |
| 96 | + 'knockout', 'erase', 'clear', 'atop', 'xor', 'plus'] |
| 97 | + |
| 98 | +for ax in axs: |
| 99 | + ax.set_facecolor('none') |
| 100 | + ax.set_xlim(0, 1) |
| 101 | + ax.set_ylim(0, 1.2) |
| 102 | + ax.set_axis_off() |
| 103 | + |
| 104 | +for i, blend_mode in enumerate(blend_modes): |
| 105 | + axs[i].imshow(data, cmap='Reds', alpha=0.75, extent=(0, 0.8, 0, 0.8)) |
| 106 | + |
| 107 | + # Four different artist types drawn using this blend_mode setting |
| 108 | + axs[i].imshow(data[::-1, :], cmap='Blues', alpha=0.75, extent=(0.2, 1, 0.4, 1.2), |
| 109 | + blend_mode=blend_mode) |
| 110 | + axs[i].text(0.05, 0.15, 'Test', weight='bold', color='c', |
| 111 | + blend_mode=blend_mode) |
| 112 | + axs[i].plot([0, 1], [1.2, 0], color='y', |
| 113 | + blend_mode=blend_mode) |
| 114 | + circ = Circle((.65, 0.5), .3, facecolor='g', alpha=0.5, zorder=2, |
| 115 | + blend_mode=blend_mode) |
| 116 | + axs[i].add_artist(circ) |
| 117 | + |
| 118 | + rect = Rectangle((0, 1.2), 1, .3, facecolor='lightgray', clip_on=False) |
| 119 | + axs[i].add_artist(rect) |
| 120 | + axs[i].set_title(blend_mode) |
| 121 | + |
| 122 | +plt.show() |
| 123 | + |
| 124 | + |
| 125 | +# %% |
| 126 | +# |
| 127 | +# This table shows by backend which options for ``blend_mode`` are supported |
| 128 | +# natively (✅) versus supported only through rasterization (🟡). |
| 129 | +# |
| 130 | +# +----------------+-----+-------+-----+-----+-----+----+ |
| 131 | +# | Option | Agg | Cairo | SVG | PDF | PGF | PS | |
| 132 | +# +================+=====+=======+=====+=====+=====+====+ |
| 133 | +# | normal [#]_ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | |
| 134 | +# +----------------+-----+-------+-----+-----+-----+----+ |
| 135 | +# | multiply, | ✅ | ✅ | ✅ | ✅ | ✅ | 🟡 | |
| 136 | +# | screen, | | | | | | | |
| 137 | +# | overlay, | | | | | | | |
| 138 | +# | darken, | | | | | | | |
| 139 | +# | lighten, | | | | | | | |
| 140 | +# | color dodge, | | | | | | | |
| 141 | +# | color burn, | | | | | | | |
| 142 | +# | hard light, | | | | | | | |
| 143 | +# | soft light, | | | | | | | |
| 144 | +# | difference, | | | | | | | |
| 145 | +# | exclusion, | | | | | | | |
| 146 | +# | hue, | | | | | | | |
| 147 | +# | saturation, | | | | | | | |
| 148 | +# | color, | | | | | | | |
| 149 | +# | luminosity | | | | | | | |
| 150 | +# +----------------+-----+-------+-----+-----+-----+----+ |
| 151 | +# | knockout [#]_, | ✅ | ✅ | 🟡 | 🟡 | 🟡 | 🟡 | |
| 152 | +# | erase [#]_, | | | | | | | |
| 153 | +# | clear, | | | | | | | |
| 154 | +# | atop, | | | | | | | |
| 155 | +# | xor, | | | | | | | |
| 156 | +# | plus | | | | | | | |
| 157 | +# +----------------+-----+-------+-----+-----+-----+----+ |
| 158 | +# |
| 159 | +# .. [#] also known as "over" |
| 160 | +# .. [#] also known as "source" |
| 161 | +# .. [#] also known as "destination out" |
0 commit comments