Skip to content

Commit b30d50d

Browse files
committed
Fixed the differencing of images for the webagg/nbagg backends.
1 parent f9feefe commit b30d50d

File tree

3 files changed

+57
-11
lines changed

3 files changed

+57
-11
lines changed

lib/matplotlib/backends/backend_nbagg.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,20 @@ def __call__(self, block=None):
3535
if not managers:
3636
return
3737

38+
interactive = is_interactive()
39+
3840
for manager in managers:
3941
manager.show()
4042

41-
if not is_interactive() and manager in Gcf._activeQue:
42-
Gcf._activeQue.remove(manager)
43+
# plt.figure adds an event which puts the figure in focus
44+
# in the activeQue. Disable this behaviour, as it results in
45+
# figures being put as the active figure after they have been
46+
# shown, even in non-interactive mode.
47+
if hasattr(manager, '_cidgcf'):
48+
manager.canvas.mpl_disconnect(manager._cidgcf)
4349

50+
if not interactive and manager in Gcf._activeQue:
51+
Gcf._activeQue.remove(manager)
4452

4553
show = Show()
4654

lib/matplotlib/backends/backend_webagg_core.py

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,12 @@ def __init__(self, *args, **kwargs):
6565
# sent to the clients will be a full frame.
6666
self._force_full = True
6767

68+
# Store the current image mode so that at any point, clients can
69+
# request the information. This should be changed by calling
70+
# self.set_image_mode(mode) so that the notification can be given
71+
# to the connected clients.
72+
self._current_image_mode = 'full'
73+
6874
def show(self):
6975
# show the figure window
7076
from matplotlib.pyplot import show
@@ -86,24 +92,41 @@ def draw(self):
8692
def draw_idle(self):
8793
self.send_event("draw")
8894

95+
def set_image_mode(self, mode):
96+
"""
97+
Set the image mode for any subsequent images which will be sent
98+
to the clients. The modes may currently be either 'full' or 'diff'.
99+
100+
Note: diff images may not contain transparency, therefore upon
101+
draw this mode may be changed if the resulting image has any
102+
transparent component.
103+
104+
"""
105+
if mode not in ['full', 'diff']:
106+
raise ValueError('image mode must be either full or diff.')
107+
if self._current_image_mode != mode:
108+
self._current_image_mode = mode
109+
self.handle_send_image_mode(None)
110+
89111
def get_diff_image(self):
90112
if self._png_is_old:
113+
renderer = self.get_renderer()
114+
91115
# The buffer is created as type uint32 so that entire
92116
# pixels can be compared in one numpy call, rather than
93117
# needing to compare each plane separately.
94-
renderer = self.get_renderer()
95118
buff = np.frombuffer(renderer.buffer_rgba(), dtype=np.uint32)
96-
97119
buff.shape = (renderer.height, renderer.width)
98120

99-
# If any pixels have transparency, we need to force a full draw
100-
# as we cannot overlay new on top of old.
121+
# If any pixels have transparency, we need to force a full
122+
# draw as we cannot overlay new on top of old.
101123
pixels = buff.view(dtype=np.uint8).reshape(buff.shape + (4,))
102-
some_transparency = np.any(pixels[:, :, 3] != 255)
103-
104-
output = buff
105124

106-
if not self._force_full and not some_transparency:
125+
if self._force_full or np.any(pixels[:, :, 3] != 255):
126+
self.set_image_mode('full')
127+
output = buff
128+
else:
129+
self.set_image_mode('diff')
107130
last_buffer = np.frombuffer(self._last_renderer.buffer_rgba(),
108131
dtype=np.uint32)
109132
last_buffer.shape = (renderer.height, renderer.width)
@@ -230,6 +253,10 @@ def handle_resize(self, event):
230253
self._png_is_old = True
231254
self.manager.resize(w, h)
232255

256+
def handle_send_image_mode(self, event):
257+
# The client requests notification of what the current image mode is.
258+
self.send_event('image_mode', mode=self._current_image_mode)
259+
233260
def send_event(self, event_type, **kwargs):
234261
self.manager._send_event(event_type, **kwargs)
235262

lib/matplotlib/backends/web_backend/mpl.js

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ mpl.figure = function(figure_id, websocket, ondownload, parent_element) {
4141
this.format_dropdown = undefined;
4242

4343
this.focus_on_mousover = false;
44+
this.image_mode = 'full';
4445

4546
this.root = $('<div/>');
4647
this.root.attr('style', 'display: inline-block');
@@ -56,11 +57,17 @@ mpl.figure = function(figure_id, websocket, ondownload, parent_element) {
5657

5758
this.ws.onopen = function () {
5859
fig.send_message("supports_binary", {value: fig.supports_binary});
60+
fig.send_message("send_image_mode", {});
5961
fig.send_message("refresh", {});
6062
}
6163

6264
this.imageObj.onload = function() {
63-
fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);
65+
if (fig.image_mode == 'full') {
66+
// Full images could contain transparency (where diff images
67+
// almost always do), so we need to clear the canvas so that
68+
// there is no ghosting.
69+
fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);
70+
}
6471
fig.context.drawImage(fig.imageObj, 0, 0);
6572
fig.waiting = false;
6673
};
@@ -302,6 +309,10 @@ mpl.figure.prototype.handle_draw = function(fig, msg) {
302309
fig.send_draw_message();
303310
}
304311

312+
mpl.figure.prototype.handle_image_mode = function(fig, msg) {
313+
fig.image_mode = msg['mode'];
314+
}
315+
305316
mpl.figure.prototype.updated_canvas_event = function() {
306317
// Called whenever the canvas gets updated.
307318
this.send_message("ack", {});

0 commit comments

Comments
 (0)