Skip to content

Commit a373dc3

Browse files
authored
Merge pull request #11724 from anntzer/fix-cairo-image
Fix cairo's image inversion and alpha misapplication.
2 parents 0887e2a + fde8971 commit a373dc3

File tree

2 files changed

+28
-27
lines changed

2 files changed

+28
-27
lines changed

lib/matplotlib/backends/backend_cairo.py

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,13 @@
4242
from matplotlib.transforms import Affine2D
4343

4444

45+
# Cairo's image buffers are premultiplied ARGB32,
46+
# Matplotlib's are unmultiplied RGBA8888.
47+
48+
4549
def _premultiplied_argb32_to_unmultiplied_rgba8888(buf):
4650
"""
4751
Convert a premultiplied ARGB32 buffer to an unmultiplied RGBA8888 buffer.
48-
49-
Cairo uses the former format, Matplotlib the latter.
5052
"""
5153
rgba = np.take( # .take() ensures C-contiguity of the result.
5254
buf,
@@ -62,6 +64,26 @@ def _premultiplied_argb32_to_unmultiplied_rgba8888(buf):
6264
return rgba
6365

6466

67+
def _unmultipled_rgba8888_to_premultiplied_argb32(rgba8888):
68+
"""
69+
Convert an unmultiplied RGBA8888 buffer to a premultiplied ARGB32 buffer.
70+
"""
71+
if sys.byteorder == "little":
72+
argb32 = np.take(rgba8888, [2, 1, 0, 3], axis=2)
73+
rgb24 = argb32[..., :-1]
74+
alpha8 = argb32[..., -1:]
75+
else:
76+
argb32 = np.take(rgba8888, [3, 0, 1, 2], axis=2)
77+
alpha8 = argb32[..., :1]
78+
rgb24 = argb32[..., 1:]
79+
# Only bother premultiplying when the alpha channel is not fully opaque,
80+
# as the cost is not negligible. The unsafe cast is needed to do the
81+
# multiplication in-place in an integer buffer.
82+
if alpha8.min() != 0xff:
83+
np.multiply(rgb24, alpha8 / 0xff, out=rgb24, casting="unsafe")
84+
return argb32
85+
86+
6587
if cairo.__name__ == "cairocffi":
6688
# Convert a pycairo context to a cairocffi one.
6789
def _to_context(ctx):
@@ -358,11 +380,7 @@ def _draw_paths():
358380
_draw_paths()
359381

360382
def draw_image(self, gc, x, y, im):
361-
# bbox - not currently used
362-
if sys.byteorder == 'little':
363-
im = im[:, :, (2, 1, 0, 3)]
364-
else:
365-
im = im[:, :, (3, 0, 1, 2)]
383+
im = _unmultipled_rgba8888_to_premultiplied_argb32(im[::-1])
366384
surface = cairo.ImageSurface.create_for_data(
367385
im.ravel().data, cairo.FORMAT_ARGB32,
368386
im.shape[1], im.shape[0], im.shape[1] * 4)
@@ -371,10 +389,7 @@ def draw_image(self, gc, x, y, im):
371389

372390
ctx.save()
373391
ctx.set_source_surface(surface, float(x), float(y))
374-
if gc.get_alpha() != 1:
375-
ctx.paint_with_alpha(gc.get_alpha())
376-
else:
377-
ctx.paint()
392+
ctx.paint()
378393
ctx.restore()
379394

380395
def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None):

lib/matplotlib/backends/backend_gtk3agg.py

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -45,22 +45,8 @@ def on_draw_event(self, widget, ctx):
4545
width = int(bbox.x1) - int(bbox.x0)
4646
height = int(bbox.y1) - int(bbox.y0)
4747

48-
buf = (np.fromstring(self.copy_from_bbox(bbox).to_string_argb(),
49-
dtype='uint8')
50-
.reshape((width, height, 4)))
51-
# cairo wants premultiplied alpha. Only bother doing the
52-
# conversion when the alpha channel is not fully opaque, as the
53-
# cost is not negligible. (The unsafe cast is needed to do the
54-
# multiplication in-place in an integer buffer.)
55-
if sys.byteorder == "little":
56-
rgb24 = buf[..., :-1]
57-
alpha8 = buf[..., -1:]
58-
else:
59-
alpha8 = buf[..., :1]
60-
rgb24 = buf[..., 1:]
61-
if alpha8.min() != 0xff:
62-
np.multiply(rgb24, alpha8 / 0xff, out=rgb24, casting="unsafe")
63-
48+
buf = backend_cairo._unmultipled_rgba8888_to_premultiplied_argb32(
49+
np.asarray(self.copy_from_bbox(bbox)))
6450
image = cairo.ImageSurface.create_for_data(
6551
buf.ravel().data, cairo.FORMAT_ARGB32, width, height)
6652
ctx.set_source_surface(image, x, y)

0 commit comments

Comments
 (0)