From bdea63d1c852da5393e6844fc7edd1659e0dab70 Mon Sep 17 00:00:00 2001 From: Michael Droettboom Date: Mon, 26 Oct 2015 14:12:47 -0400 Subject: [PATCH 1/5] Fix #5302: Proper alpha-blending for jpeg --- lib/matplotlib/backends/backend_agg.py | 7 +++++-- lib/matplotlib/tests/test_image.py | 22 ++++++++++++++++++++++ 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/backends/backend_agg.py b/lib/matplotlib/backends/backend_agg.py index f3b23ca95531..e37d1d75067f 100644 --- a/lib/matplotlib/backends/backend_agg.py +++ b/lib/matplotlib/backends/backend_agg.py @@ -551,7 +551,6 @@ def print_to_buffer(self): return result if _has_pil: - # add JPEG support def print_jpg(self, filename_or_obj, *args, **kwargs): """ @@ -573,14 +572,18 @@ def print_jpg(self, filename_or_obj, *args, **kwargs): buf, size = self.print_to_buffer() if kwargs.pop("dryrun", False): return + # The image is "pasted" onto a white background image to safely + # handle any transparency image = Image.frombuffer('RGBA', size, buf, 'raw', 'RGBA', 0, 1) + background = Image.new('RGB', size, (255, 255, 255)) + background.paste(image, image) options = restrict_dict(kwargs, ['quality', 'optimize', 'progressive']) if 'quality' not in options: options['quality'] = rcParams['savefig.jpeg_quality'] - return image.save(filename_or_obj, format='jpeg', **options) + return background.save(filename_or_obj, format='jpeg', **options) print_jpeg = print_jpg # add TIFF support diff --git a/lib/matplotlib/tests/test_image.py b/lib/matplotlib/tests/test_image.py index 6f69822b535d..846307173150 100644 --- a/lib/matplotlib/tests/test_image.py +++ b/lib/matplotlib/tests/test_image.py @@ -453,6 +453,28 @@ def test_nonuniformimage_setnorm(): im = NonUniformImage(ax) im.set_norm(plt.Normalize()) +@knownfailureif(not HAS_PIL) +@cleanup +def test_jpeg_alpha(): + plt.figure(figsize=(1, 1), dpi=300) + # Create an image that is all black, with a gradient from 0-1 in + # the alpha channel from left to right. + im = np.zeros((300, 300, 4), dtype=np.float) + im[..., 3] = np.linspace(0.0, 1.0, 300) + + plt.figimage(im) + + buff = io.BytesIO() + plt.savefig(buff, transparent=True, format='jpg', dpi=300) + + buff.seek(0) + image = Image.open(buff) + + # If this fails, there will be only one color (all black). If this + # is working, we should have all 256 shades of grey represented. + assert len(image.getcolors(256)) == 256 + + if __name__=='__main__': import nose nose.runmodule(argv=['-s','--with-doctest'], exit=False) From 3dea66d9e591cefe551214349abb3c12516c5a92 Mon Sep 17 00:00:00 2001 From: Michael Droettboom Date: Tue, 27 Oct 2015 11:43:33 -0400 Subject: [PATCH 2/5] Get background color from savefig.facecolor --- lib/matplotlib/backends/backend_agg.py | 6 +++++- lib/matplotlib/tests/test_image.py | 10 +++++++--- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/lib/matplotlib/backends/backend_agg.py b/lib/matplotlib/backends/backend_agg.py index e37d1d75067f..8a3c93c8fc6f 100644 --- a/lib/matplotlib/backends/backend_agg.py +++ b/lib/matplotlib/backends/backend_agg.py @@ -38,6 +38,7 @@ from matplotlib.mathtext import MathTextParser from matplotlib.path import Path from matplotlib.transforms import Bbox, BboxBase +from matplotlib import colors as mcolors from matplotlib.backends._backend_agg import RendererAgg as _RendererAgg from matplotlib import _png @@ -575,7 +576,10 @@ def print_jpg(self, filename_or_obj, *args, **kwargs): # The image is "pasted" onto a white background image to safely # handle any transparency image = Image.frombuffer('RGBA', size, buf, 'raw', 'RGBA', 0, 1) - background = Image.new('RGB', size, (255, 255, 255)) + color = mcolors.colorConverter.to_rgb( + rcParams.get('savefig.facecolor', 'white')) + color = tuple([int(x * 255.0) for x in color]) + background = Image.new('RGB', size, color) background.paste(image, image) options = restrict_dict(kwargs, ['quality', 'optimize', 'progressive']) diff --git a/lib/matplotlib/tests/test_image.py b/lib/matplotlib/tests/test_image.py index 846307173150..516afe441087 100644 --- a/lib/matplotlib/tests/test_image.py +++ b/lib/matplotlib/tests/test_image.py @@ -8,7 +8,7 @@ from matplotlib.testing.decorators import image_comparison, knownfailureif, cleanup from matplotlib.image import BboxImage, imread, NonUniformImage from matplotlib.transforms import Bbox -from matplotlib import rcParams +from matplotlib import rcParams, rc_context import matplotlib.pyplot as plt from nose.tools import assert_raises from numpy.testing import assert_array_equal, assert_array_almost_equal @@ -465,14 +465,18 @@ def test_jpeg_alpha(): plt.figimage(im) buff = io.BytesIO() - plt.savefig(buff, transparent=True, format='jpg', dpi=300) + with rc_context({'savefig.facecolor': 'red'}): + plt.savefig(buff, transparent=True, format='jpg', dpi=300) buff.seek(0) image = Image.open(buff) # If this fails, there will be only one color (all black). If this # is working, we should have all 256 shades of grey represented. - assert len(image.getcolors(256)) == 256 + assert len(image.getcolors(256)) == 179 + # The fully transparent part should be red, not white or black + # or anything else + assert image.getpixel(0, 0) == (0, 0, 255) if __name__=='__main__': From 183f246cfca1d804d7b905b324d0fe610e2322cb Mon Sep 17 00:00:00 2001 From: Michael Droettboom Date: Tue, 27 Oct 2015 14:18:58 -0400 Subject: [PATCH 3/5] Fix test --- lib/matplotlib/tests/test_image.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/tests/test_image.py b/lib/matplotlib/tests/test_image.py index 516afe441087..114f696173d7 100644 --- a/lib/matplotlib/tests/test_image.py +++ b/lib/matplotlib/tests/test_image.py @@ -473,10 +473,10 @@ def test_jpeg_alpha(): # If this fails, there will be only one color (all black). If this # is working, we should have all 256 shades of grey represented. - assert len(image.getcolors(256)) == 179 + assert len(image.getcolors(256)) >= 170 and len(image.getcolors(256)) <= 180 # The fully transparent part should be red, not white or black # or anything else - assert image.getpixel(0, 0) == (0, 0, 255) + assert image.getpixel((0, 0)) == (254, 0, 0) if __name__=='__main__': From 737da1e59a8fe55e8f0901d9d2561af890ee07a1 Mon Sep 17 00:00:00 2001 From: Michael Droettboom Date: Tue, 27 Oct 2015 15:05:50 -0400 Subject: [PATCH 4/5] Add prints to test --- lib/matplotlib/tests/test_image.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/matplotlib/tests/test_image.py b/lib/matplotlib/tests/test_image.py index 114f696173d7..7ee765427bb8 100644 --- a/lib/matplotlib/tests/test_image.py +++ b/lib/matplotlib/tests/test_image.py @@ -473,9 +473,11 @@ def test_jpeg_alpha(): # If this fails, there will be only one color (all black). If this # is working, we should have all 256 shades of grey represented. + print("num colors: ", len(image.getcolors(256))) assert len(image.getcolors(256)) >= 170 and len(image.getcolors(256)) <= 180 # The fully transparent part should be red, not white or black # or anything else + print("corner pixel: ", image.getpixel((0, 0))) assert image.getpixel((0, 0)) == (254, 0, 0) From fb6ff8ab196f9abbf4fa89b2facd2d46867d534a Mon Sep 17 00:00:00 2001 From: Michael Droettboom Date: Wed, 28 Oct 2015 08:35:27 -0400 Subject: [PATCH 5/5] Adjust acceptable range --- lib/matplotlib/tests/test_image.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/tests/test_image.py b/lib/matplotlib/tests/test_image.py index 7ee765427bb8..ef5108c88457 100644 --- a/lib/matplotlib/tests/test_image.py +++ b/lib/matplotlib/tests/test_image.py @@ -474,7 +474,7 @@ def test_jpeg_alpha(): # If this fails, there will be only one color (all black). If this # is working, we should have all 256 shades of grey represented. print("num colors: ", len(image.getcolors(256))) - assert len(image.getcolors(256)) >= 170 and len(image.getcolors(256)) <= 180 + assert len(image.getcolors(256)) >= 175 and len(image.getcolors(256)) <= 185 # The fully transparent part should be red, not white or black # or anything else print("corner pixel: ", image.getpixel((0, 0)))