From 359b5017268b64e0338ad77dd96c5b6c5488feab Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Mon, 22 Jan 2018 17:38:25 -0800 Subject: [PATCH] Fix gtk3agg alpha channel. The gtk3agg backend works by drawing the ARGB32 buffer (from Agg) onto a cairo context (passed by gtk3). However, cairo wants a *premultiplied* ARGB32 buffer (i.e., where 100% blue with 50% transparency is represented by (r=0%, g=0%, b=50%, a=50%) instead of (r=0%, g=0%, b=100%, a=50%), which we didn't do before. This is only apparent if the entire buffer contains some transparency, e.g. if the figure background is transparent. Consider e.g. under gtk3agg: from pylab import * rcParams["figure.facecolor"] = (0, 0, 0, 0) gca() show() Without the patch, the area surrounding the axes is white (because of the misinterpretation of premultiplied alpha). With the patch, it is (correctly) gray, which is the background color of the gtk widget. (Note that when running the example under qt5agg or tkagg, the situation is complicated by the fact that these backends themselves set the widget background color to white rather than gray.) As a comparison point, qt5agg builds the QImage using Format_ARGB32, instead of Format_ARB32_Premultiplied; i.e. Qt provides its own conversion from non-premultiplied to premultiplied. The premultiplication step involves allocating a full new buffer, so check whether there is actually any transparency before doing it. --- lib/matplotlib/backends/backend_gtk3agg.py | 28 ++++++++++++++++------ 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/lib/matplotlib/backends/backend_gtk3agg.py b/lib/matplotlib/backends/backend_gtk3agg.py index f54849cf049c..c7b79c75a109 100644 --- a/lib/matplotlib/backends/backend_gtk3agg.py +++ b/lib/matplotlib/backends/backend_gtk3agg.py @@ -1,6 +1,8 @@ -import numpy as np +import sys import warnings +import numpy as np + from . import backend_agg, backend_cairo, backend_gtk3 from ._gtk3_compat import gi from .backend_cairo import cairo @@ -31,7 +33,7 @@ def _render_figure(self, width, height): backend_agg.FigureCanvasAgg.draw(self) def on_draw_event(self, widget, ctx): - """ GtkDrawable draw event, like expose_event in GTK 2.X + """GtkDrawable draw event, like expose_event in GTK 2.X. """ allocation = self.get_allocation() w, h = allocation.width, allocation.height @@ -45,17 +47,29 @@ def on_draw_event(self, widget, ctx): ctx = backend_cairo._to_context(ctx) for bbox in bbox_queue: - area = self.copy_from_bbox(bbox) - buf = np.fromstring(area.to_string_argb(), dtype='uint8') - x = int(bbox.x0) y = h - int(bbox.y1) width = int(bbox.x1) - int(bbox.x0) height = int(bbox.y1) - int(bbox.y0) + buf = (np.fromstring(self.copy_from_bbox(bbox).to_string_argb(), + dtype='uint8') + .reshape((width, height, 4))) + # cairo wants premultiplied alpha. Only bother doing the + # conversion when the alpha channel is not fully opaque, as the + # cost is not negligible. (The unsafe cast is needed to do the + # multiplication in-place in an integer buffer.) + if sys.byteorder == "little": + rgb24 = buf[..., :-1] + alpha8 = buf[..., -1:] + else: + alpha8 = buf[..., :1] + rgb24 = buf[..., 1:] + if alpha8.min() != 0xff: + np.multiply(rgb24, alpha8 / 0xff, out=rgb24, casting="unsafe") + image = cairo.ImageSurface.create_for_data( - buf.ravel().data, cairo.FORMAT_ARGB32, - width, height, width * 4) + buf.ravel().data, cairo.FORMAT_ARGB32, width, height) ctx.set_source_surface(image, x, y) ctx.paint()