Skip to content

Use Format_ARGB32_Premultiplied instead of RGBA8888 for Qt backends. #11845

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Aug 13, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 2 additions & 44 deletions lib/matplotlib/backends/backend_cairo.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,48 +42,6 @@
from matplotlib.transforms import Affine2D


# Cairo's image buffers are premultiplied ARGB32,
# Matplotlib's are unmultiplied RGBA8888.


def _premultiplied_argb32_to_unmultiplied_rgba8888(buf):
"""
Convert a premultiplied ARGB32 buffer to an unmultiplied RGBA8888 buffer.
"""
rgba = np.take( # .take() ensures C-contiguity of the result.
buf,
[2, 1, 0, 3] if sys.byteorder == "little" else [1, 2, 3, 0], axis=2)
rgb = rgba[..., :-1]
alpha = rgba[..., -1]
# Un-premultiply alpha. The formula is the same as in cairo-png.c.
mask = alpha != 0
for channel in np.rollaxis(rgb, -1):
channel[mask] = (
(channel[mask].astype(int) * 255 + alpha[mask] // 2)
// alpha[mask])
return rgba


def _unmultipled_rgba8888_to_premultiplied_argb32(rgba8888):
"""
Convert an unmultiplied RGBA8888 buffer to a premultiplied ARGB32 buffer.
"""
if sys.byteorder == "little":
argb32 = np.take(rgba8888, [2, 1, 0, 3], axis=2)
rgb24 = argb32[..., :-1]
alpha8 = argb32[..., -1:]
else:
argb32 = np.take(rgba8888, [3, 0, 1, 2], axis=2)
alpha8 = argb32[..., :1]
rgb24 = argb32[..., 1:]
# Only bother premultiplying 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 alpha8.min() != 0xff:
np.multiply(rgb24, alpha8 / 0xff, out=rgb24, casting="unsafe")
return argb32


if cairo.__name__ == "cairocffi":
# Convert a pycairo context to a cairocffi one.
def _to_context(ctx):
Expand Down Expand Up @@ -380,7 +338,7 @@ def _draw_paths():
_draw_paths()

def draw_image(self, gc, x, y, im):
im = _unmultipled_rgba8888_to_premultiplied_argb32(im[::-1])
im = cbook._unmultipled_rgba8888_to_premultiplied_argb32(im[::-1])
surface = cairo.ImageSurface.create_for_data(
im.ravel().data, cairo.FORMAT_ARGB32,
im.shape[1], im.shape[0], im.shape[1] * 4)
Expand Down Expand Up @@ -586,7 +544,7 @@ def print_png(self, fobj, *args, **kwargs):
def print_rgba(self, fobj, *args, **kwargs):
width, height = self.get_width_height()
buf = self._get_printed_image_surface().get_data()
fobj.write(_premultiplied_argb32_to_unmultiplied_rgba8888(
fobj.write(cbook._premultiplied_argb32_to_unmultiplied_rgba8888(
np.asarray(buf).reshape((width, height, 4))))

print_raw = print_rgba
Expand Down
8 changes: 5 additions & 3 deletions lib/matplotlib/backends/backend_qt5agg.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

from matplotlib.transforms import Bbox

from .. import cbook
from .backend_agg import FigureCanvasAgg
from .backend_qt5 import (
QtCore, QtGui, QtWidgets, _BackendQT5, FigureCanvasQT, FigureManagerQT,
Expand Down Expand Up @@ -52,9 +53,10 @@ def paintEvent(self, event):
[[left, self.renderer.height - (top + height * self._dpi_ratio)],
[left + width * self._dpi_ratio, self.renderer.height - top]])
reg = self.copy_from_bbox(bbox)
buf = memoryview(reg)
qimage = QtGui.QImage(
buf, buf.shape[1], buf.shape[0], QtGui.QImage.Format_RGBA8888)
buf = cbook._unmultipled_rgba8888_to_premultiplied_argb32(
memoryview(reg))
qimage = QtGui.QImage(buf, buf.shape[1], buf.shape[0],
QtGui.QImage.Format_ARGB32_Premultiplied)
if hasattr(qimage, 'setDevicePixelRatio'):
# Not available on Qt4 or some older Qt5.
qimage.setDevicePixelRatio(self._dpi_ratio)
Expand Down
42 changes: 42 additions & 0 deletions lib/matplotlib/cbook/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2033,3 +2033,45 @@ def add(self, key):

def discard(self, key):
self._od.pop(key, None)


# Agg's buffers are unmultiplied RGBA8888, which neither PyQt4 nor cairo
# support; however, both do support premultiplied ARGB32.


def _premultiplied_argb32_to_unmultiplied_rgba8888(buf):
"""
Convert a premultiplied ARGB32 buffer to an unmultiplied RGBA8888 buffer.
"""
rgba = np.take( # .take() ensures C-contiguity of the result.
buf,
[2, 1, 0, 3] if sys.byteorder == "little" else [1, 2, 3, 0], axis=2)
rgb = rgba[..., :-1]
alpha = rgba[..., -1]
# Un-premultiply alpha. The formula is the same as in cairo-png.c.
mask = alpha != 0
for channel in np.rollaxis(rgb, -1):
channel[mask] = (
(channel[mask].astype(int) * 255 + alpha[mask] // 2)
// alpha[mask])
return rgba


def _unmultipled_rgba8888_to_premultiplied_argb32(rgba8888):
"""
Convert an unmultiplied RGBA8888 buffer to a premultiplied ARGB32 buffer.
"""
if sys.byteorder == "little":
argb32 = np.take(rgba8888, [2, 1, 0, 3], axis=2)
rgb24 = argb32[..., :-1]
alpha8 = argb32[..., -1:]
else:
argb32 = np.take(rgba8888, [3, 0, 1, 2], axis=2)
alpha8 = argb32[..., :1]
rgb24 = argb32[..., 1:]
# Only bother premultiplying 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 alpha8.min() != 0xff:
np.multiply(rgb24, alpha8 / 0xff, out=rgb24, casting="unsafe")
return argb32