From b30d50d31dc0f1c1e6633bd3e3e5a1d47427c04e Mon Sep 17 00:00:00 2001 From: Phil Elson Date: Thu, 25 Sep 2014 14:47:49 +0100 Subject: [PATCH] Fixed the differencing of images for the webagg/nbagg backends. --- lib/matplotlib/backends/backend_nbagg.py | 12 +++++- .../backends/backend_webagg_core.py | 43 +++++++++++++++---- lib/matplotlib/backends/web_backend/mpl.js | 13 +++++- 3 files changed, 57 insertions(+), 11 deletions(-) diff --git a/lib/matplotlib/backends/backend_nbagg.py b/lib/matplotlib/backends/backend_nbagg.py index 32c9ebf988a5..bea523622454 100644 --- a/lib/matplotlib/backends/backend_nbagg.py +++ b/lib/matplotlib/backends/backend_nbagg.py @@ -35,12 +35,20 @@ def __call__(self, block=None): if not managers: return + interactive = is_interactive() + for manager in managers: manager.show() - if not is_interactive() and manager in Gcf._activeQue: - Gcf._activeQue.remove(manager) + # plt.figure adds an event which puts the figure in focus + # in the activeQue. Disable this behaviour, as it results in + # figures being put as the active figure after they have been + # shown, even in non-interactive mode. + if hasattr(manager, '_cidgcf'): + manager.canvas.mpl_disconnect(manager._cidgcf) + if not interactive and manager in Gcf._activeQue: + Gcf._activeQue.remove(manager) show = Show() diff --git a/lib/matplotlib/backends/backend_webagg_core.py b/lib/matplotlib/backends/backend_webagg_core.py index 31b641ac0982..dfd77ec450c8 100644 --- a/lib/matplotlib/backends/backend_webagg_core.py +++ b/lib/matplotlib/backends/backend_webagg_core.py @@ -65,6 +65,12 @@ def __init__(self, *args, **kwargs): # sent to the clients will be a full frame. self._force_full = True + # Store the current image mode so that at any point, clients can + # request the information. This should be changed by calling + # self.set_image_mode(mode) so that the notification can be given + # to the connected clients. + self._current_image_mode = 'full' + def show(self): # show the figure window from matplotlib.pyplot import show @@ -86,24 +92,41 @@ def draw(self): def draw_idle(self): self.send_event("draw") + def set_image_mode(self, mode): + """ + Set the image mode for any subsequent images which will be sent + to the clients. The modes may currently be either 'full' or 'diff'. + + Note: diff images may not contain transparency, therefore upon + draw this mode may be changed if the resulting image has any + transparent component. + + """ + if mode not in ['full', 'diff']: + raise ValueError('image mode must be either full or diff.') + if self._current_image_mode != mode: + self._current_image_mode = mode + self.handle_send_image_mode(None) + def get_diff_image(self): if self._png_is_old: + renderer = self.get_renderer() + # The buffer is created as type uint32 so that entire # pixels can be compared in one numpy call, rather than # needing to compare each plane separately. - renderer = self.get_renderer() buff = np.frombuffer(renderer.buffer_rgba(), dtype=np.uint32) - buff.shape = (renderer.height, renderer.width) - # If any pixels have transparency, we need to force a full draw - # as we cannot overlay new on top of old. + # If any pixels have transparency, we need to force a full + # draw as we cannot overlay new on top of old. pixels = buff.view(dtype=np.uint8).reshape(buff.shape + (4,)) - some_transparency = np.any(pixels[:, :, 3] != 255) - - output = buff - if not self._force_full and not some_transparency: + if self._force_full or np.any(pixels[:, :, 3] != 255): + self.set_image_mode('full') + output = buff + else: + self.set_image_mode('diff') last_buffer = np.frombuffer(self._last_renderer.buffer_rgba(), dtype=np.uint32) last_buffer.shape = (renderer.height, renderer.width) @@ -230,6 +253,10 @@ def handle_resize(self, event): self._png_is_old = True self.manager.resize(w, h) + def handle_send_image_mode(self, event): + # The client requests notification of what the current image mode is. + self.send_event('image_mode', mode=self._current_image_mode) + def send_event(self, event_type, **kwargs): self.manager._send_event(event_type, **kwargs) diff --git a/lib/matplotlib/backends/web_backend/mpl.js b/lib/matplotlib/backends/web_backend/mpl.js index 5856b2347e32..6d6a1f3a4e59 100644 --- a/lib/matplotlib/backends/web_backend/mpl.js +++ b/lib/matplotlib/backends/web_backend/mpl.js @@ -41,6 +41,7 @@ mpl.figure = function(figure_id, websocket, ondownload, parent_element) { this.format_dropdown = undefined; this.focus_on_mousover = false; + this.image_mode = 'full'; this.root = $('
'); this.root.attr('style', 'display: inline-block'); @@ -56,11 +57,17 @@ mpl.figure = function(figure_id, websocket, ondownload, parent_element) { this.ws.onopen = function () { fig.send_message("supports_binary", {value: fig.supports_binary}); + fig.send_message("send_image_mode", {}); fig.send_message("refresh", {}); } this.imageObj.onload = function() { - fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height); + if (fig.image_mode == 'full') { + // Full images could contain transparency (where diff images + // almost always do), so we need to clear the canvas so that + // there is no ghosting. + fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height); + } fig.context.drawImage(fig.imageObj, 0, 0); fig.waiting = false; }; @@ -302,6 +309,10 @@ mpl.figure.prototype.handle_draw = function(fig, msg) { fig.send_draw_message(); } +mpl.figure.prototype.handle_image_mode = function(fig, msg) { + fig.image_mode = msg['mode']; +} + mpl.figure.prototype.updated_canvas_event = function() { // Called whenever the canvas gets updated. this.send_message("ack", {});