1""" 2================================== 3Faster rendering by using blitting 4================================== 5 6*Blitting* is a `standard technique 7<https://en.wikipedia.org/wiki/Bit_blit>`__ in raster graphics that, 8in the context of Matplotlib, can be used to (drastically) improve 9performance of interactive figures. For example, the 10:mod:`~.animation` and :mod:`~.widgets` modules use blitting 11internally. Here, we demonstrate how to implement your own blitting, outside 12of these classes. 13 14Blitting speeds up repetitive drawing by rendering all non-changing 15graphic elements into a background image once. Then, for every draw, only the 16changing elements need to be drawn onto this background. For example, 17if the limits of an Axes have not changed, we can render the empty Axes 18including all ticks and labels once, and only draw the changing data later. 19 20The strategy is 21 22- Prepare the constant background: 23 24 - Draw the figure, but exclude all artists that you want to animate by 25 marking them as *animated* (see `.Artist.set_animated`). 26 - Save a copy of the RBGA buffer. 27 28- Render the individual images: 29 30 - Restore the copy of the RGBA buffer. 31 - Redraw the animated artists using `.Axes.draw_artist` / 32 `.Figure.draw_artist`. 33 - Show the resulting image on the screen. 34 35One consequence of this procedure is that your animated artists are always 36drawn on top of the static artists. 37 38Not all backends support blitting. You can check if a given canvas does via 39the `.FigureCanvasBase.supports_blit` property. 40 41.. warning:: 42 43 This code does not work with the OSX backend (but does work with other 44 GUI backends on mac). 45 46Minimal example 47--------------- 48 49We can use the `.FigureCanvasAgg` methods 50`~.FigureCanvasAgg.copy_from_bbox` and 51`~.FigureCanvasAgg.restore_region` in conjunction with setting 52``animated=True`` on our artist to implement a minimal example that 53uses blitting to accelerate rendering 54 55""" 56 57import matplotlib.pyplot as plt 58import numpy as np 59 60x = np.linspace(0, 2 * np.pi, 100) 61 62fig, ax = plt.subplots() 63 64# animated=True tells matplotlib to only draw the artist when we 65# explicitly request it 66(ln,) = ax.plot(x, np.sin(x), animated=True) 67 68# make sure the window is raised, but the script keeps going 69plt.show(block=False) 70 71# stop to admire our empty window axes and ensure it is rendered at 72# least once. 73# 74# We need to fully draw the figure at its final size on the screen 75# before we continue on so that : 76# a) we have the correctly sized and drawn background to grab 77# b) we have a cached renderer so that ``ax.draw_artist`` works 78# so we spin the event loop to let the backend process any pending operations 79plt.pause(0.1) 80 81# get copy of entire figure (everything inside fig.bbox) sans animated artist 82bg = fig.canvas.copy_from_bbox(fig.bbox) 83# draw the animated artist, this uses a cached renderer 84ax.draw_artist(ln) 85# show the result to the screen, this pushes the updated RGBA buffer from the 86# renderer to the GUI framework so you can see it 87fig.canvas.blit(fig.bbox) 88 89for j in range(100): 90 # reset the background back in the canvas state, screen unchanged 91 fig.canvas.restore_region(bg) 92 # update the artist, neither the canvas state nor the screen have changed 93 ln.set_ydata(np.sin(x + (j / 100) * np.pi)) 94 # re-render the artist, updating the canvas state, but not the screen 95 ax.draw_artist(ln) 96 # copy the image to the GUI state, but screen might not be changed yet 97 fig.canvas.blit(fig.bbox) 98 # flush any pending GUI events, re-painting the screen if needed 99 fig.canvas.flush_events() 100 # you can put a pause in if you want to slow things down 101 # plt.pause(.1) 102 103############################################################################### 104# This example works and shows a simple animation, however because we 105# are only grabbing the background once, if the size of the figure in 106# pixels changes (due to either the size or dpi of the figure 107# changing) , the background will be invalid and result in incorrect 108# (but sometimes cool looking!) images. There is also a global 109# variable and a fair amount of boiler plate which suggests we should 110# wrap this in a class. 111# 112# Class-based example 113# ------------------- 114# 115# We can use a class to encapsulate the boilerplate logic and state of 116# restoring the background, drawing the artists, and then blitting the 117# result to the screen. Additionally, we can use the ``'draw_event'`` 118# callback to capture a new background whenever a full re-draw 119# happens to handle resizes correctly. 120 121 122class BlitManager: 123 def __init__(self, canvas, animated_artists=()): 124 """ 125 Parameters 126 ---------- 127 canvas : FigureCanvasAgg 128 The canvas to work with, this only works for sub-classes of the Agg 129 canvas which have the `~FigureCanvasAgg.copy_from_bbox` and 130 `~FigureCanvasAgg.restore_region` methods. 131 132 animated_artists : Iterable[Artist] 133 List of the artists to manage 134 """ 135 self.canvas = canvas 136 self._bg = None 137 self._artists = [] 138 139 for a in animated_artists: 140 self.add_artist(a) 141 # grab the background on every draw 142 self.cid = canvas.mpl_connect("draw_event", self.on_draw) 143 144 def on_draw(self, event): 145 """Callback to register with 'draw_event'.""" 146 cv = self.canvas 147 if event is not None: 148 if event.canvas != cv: 149 raise RuntimeError 150 self._bg = cv.copy_from_bbox(cv.figure.bbox) 151 self._draw_animated() 152 153 def add_artist(self, art): 154 """ 155 Add an artist to be managed. 156 157 Parameters 158 ---------- 159 art : Artist 160 161 The artist to be added. Will be set to 'animated' (just 162 to be safe). *art* must be in the figure associated with 163 the canvas this class is managing. 164 165 """ 166 if art.figure != self.canvas.figure: 167 raise RuntimeError 168 art.set_animated(True) 169 self._artists.append(art) 170 171 def _draw_animated(self): 172 """Draw all of the animated artists.""" 173 fig = self.canvas.figure 174 for a in self._artists: 175 fig.draw_artist(a) 176 177 def update(self): 178 """Update the screen with animated artists.""" 179 cv = self.canvas 180 fig = cv.figure 181 # paranoia in case we missed the draw event, 182 if self._bg is None: 183 self.on_draw(None) 184 else: 185 # restore the background 186 cv.restore_region(self._bg) 187 # draw all of the animated artists 188 self._draw_animated() 189 # update the GUI state 190 cv.blit(fig.bbox) 191 # let the GUI event loop process anything it has to do 192 cv.flush_events() 193 194 195############################################################################### 196# Here is how we would use our class. This is a slightly more complicated 197# example than the first case as we add a text frame counter as well. 198 199# make a new figure 200fig, ax = plt.subplots() 201# add a line 202(ln,) = ax.plot(x, np.sin(x), animated=True) 203# add a frame number 204fr_number = ax.annotate( 205 "0", 206 (0, 1), 207 xycoords="axes fraction", 208 xytext=(10, -10), 209 textcoords="offset points", 210 ha="left", 211 va="top", 212 animated=True, 213) 214bm = BlitManager(fig.canvas, [ln, fr_number]) 215# make sure our window is on the screen and drawn 216plt.show(block=False) 217plt.pause(.1) 218 219for j in range(100): 220 # update the artists 221 ln.set_ydata(np.sin(x + (j / 100) * np.pi)) 222 fr_number.set_text("frame: {j}".format(j=j)) 223 # tell the blitting manager to do its thing 224 bm.update() 225 226############################################################################### 227# This class does not depend on `.pyplot` and is suitable to embed 228# into larger GUI application. 229