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