From d8fce7b3bb9f3b5cbf91efb65ab764eddd0ad1dd Mon Sep 17 00:00:00 2001 From: pelson Date: Fri, 29 Mar 2013 10:39:01 +0000 Subject: [PATCH 1/2] Fixed background colour of PNGs saved with a non-zero opacity. --- doc/users/whats_new.rst | 5 ++ lib/matplotlib/backends/backend_agg.py | 9 ++-- lib/matplotlib/backends/backend_pdf.py | 2 +- .../test_figure/alpha_background.png | Bin 0 -> 1205 bytes .../test_figure/alpha_background.svg | 47 +++++++++++++++++ lib/matplotlib/tests/test_agg.py | 49 ++++++++++++++++++ lib/matplotlib/tests/test_figure.py | 16 ++++++ src/_backend_agg.cpp | 4 +- 8 files changed, 125 insertions(+), 7 deletions(-) create mode 100644 lib/matplotlib/tests/baseline_images/test_figure/alpha_background.png create mode 100644 lib/matplotlib/tests/baseline_images/test_figure/alpha_background.svg diff --git a/doc/users/whats_new.rst b/doc/users/whats_new.rst index b45bacdf9a9a..b0707b93ffa4 100644 --- a/doc/users/whats_new.rst +++ b/doc/users/whats_new.rst @@ -166,6 +166,11 @@ Multiple images on same axes are correctly transparent When putting multiple images onto the same axes, the background color of the axes will now show through correctly. +Full control of the background color +------------------------------------ +Phil Elson fixed the Agg backend such that PNGs are now saved with the correct +background color when :meth:`fig.patch.get_alpha` is not 1. + .. _whats-new-1-2: new in matplotlib-1.2 diff --git a/lib/matplotlib/backends/backend_agg.py b/lib/matplotlib/backends/backend_agg.py index d7a81b7e8f07..865a8fd0013f 100644 --- a/lib/matplotlib/backends/backend_agg.py +++ b/lib/matplotlib/backends/backend_agg.py @@ -16,9 +16,8 @@ TODO: - * allow save to file handle - * integrate screen dpi w/ ppi and text + """ from __future__ import division import threading @@ -438,7 +437,7 @@ def draw(self): """ if __debug__: verbose.report('FigureCanvasAgg.draw', 'debug-annoying') - self.renderer = self.get_renderer() + self.renderer = self.get_renderer(cleared=True) # acquire a lock on the shared font cache RendererAgg.lock.acquire() @@ -449,7 +448,7 @@ def draw(self): - def get_renderer(self): + def get_renderer(self, cleared=False): l, b, w, h = self.figure.bbox.bounds key = w, h, self.figure.dpi try: self._lastKey, self.renderer @@ -459,6 +458,8 @@ def get_renderer(self): if need_new_renderer: self.renderer = RendererAgg(w, h, self.figure.dpi) self._lastKey = key + elif cleared: + self.renderer.clear() return self.renderer def tostring_rgb(self): diff --git a/lib/matplotlib/backends/backend_pdf.py b/lib/matplotlib/backends/backend_pdf.py index 6b688cee8b10..bc1e09e34ab3 100644 --- a/lib/matplotlib/backends/backend_pdf.py +++ b/lib/matplotlib/backends/backend_pdf.py @@ -436,7 +436,7 @@ def __init__(self, filename): revision = '' self.infoDict = { - 'Creator': 'matplotlib %s, http://matplotlib.sf.net' % __version__, + 'Creator': 'matplotlib %s, http://matplotlib.org' % __version__, 'Producer': 'matplotlib pdf backend%s' % revision, 'CreationDate': datetime.today() } diff --git a/lib/matplotlib/tests/baseline_images/test_figure/alpha_background.png b/lib/matplotlib/tests/baseline_images/test_figure/alpha_background.png new file mode 100644 index 0000000000000000000000000000000000000000..d09453f36b8e75d8f265f9928b9bfb9a9ede22f2 GIT binary patch literal 1205 zcmeAS@N?(olHy`uVBq!ia0vp^CxAGGgAGU?ZmZ`8QY^(zo*^7SP{WbZ0pxQQctjR6 zFz_dWFyjjQ<(WV=;hrvzAr*7p-u2H8372U2_}r(UmHxZFF{DaU-P^j{{k-AM;q^_a7Ezg)#Oooks#z_BH}PTtWw$Kz&I`TYLe z*Gp{dIbu?cp6AHv89$#lnPrYsQ1sUCwv3rmBs)Y51T+&Af(|$=X=wCdWS+>xro_tQ z!X+_OB;wN=UhIC_n*YCH_mTZePM-j-~rzU+wGodhi36uTsivM zkNLU2!&|c@=Nn~hc{VZs;Q7IIN$QH+p}->Z)3)a$Rt119|b>@?(3ej zV5gRTZ&z8PkC;K(o{CPBB@m?}31hnn?%U2-UKY2Pi@O_LHH*R@WraJV;%`%f&y z`kNXL?wMw+2{&joO<;!KNuK$&cfpc19#Mrg^Z!>0@34Ik?d5#=>`JxM zCd!ej^7k*lKdDywK>E|%W|`ybYxhkJ>C8KSG>Yxj{=-+!J2aWNeos$c%V^IK@06@r zud%x7Z`|TD;rt8R zjXyuUaz3lSXWIYjim$PstlHzBW<|d=pD^v3+0EuH|39<)pXl%3;j}I{^Mb>Y|E9no dhi2w~tRXi0&A9EDj{(aa22WQ%mvv4FO#qlh>-GQu literal 0 HcmV?d00001 diff --git a/lib/matplotlib/tests/baseline_images/test_figure/alpha_background.svg b/lib/matplotlib/tests/baseline_images/test_figure/alpha_background.svg new file mode 100644 index 000000000000..85c177195dbb --- /dev/null +++ b/lib/matplotlib/tests/baseline_images/test_figure/alpha_background.svg @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + diff --git a/lib/matplotlib/tests/test_agg.py b/lib/matplotlib/tests/test_agg.py index 7caf5c5fc622..33699d397d39 100644 --- a/lib/matplotlib/tests/test_agg.py +++ b/lib/matplotlib/tests/test_agg.py @@ -1,6 +1,50 @@ from __future__ import print_function import os +import tempfile + + +from numpy.testing import assert_array_almost_equal + + +from matplotlib.image import imread +from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas +from matplotlib.figure import Figure +from matplotlib.testing.decorators import cleanup + + +@cleanup +def test_repeated_save_with_alpha(): + # We want an image which has a background color of blue, with an + # alpha of 0.25. + + fig = Figure([1, 0.4]) + canvas = FigureCanvas(fig) + fig.set_facecolor((0, 0, 0.4)) + fig.patch.set_alpha(0.25) + + # The target color is fig.patch.get_facecolor() + + _, img_fname = tempfile.mkstemp(suffix='.png') + try: + fig.savefig(img_fname, + facecolor=fig.get_facecolor(), + edgecolor='none') + + # Save the figure again to check that the + # colors don't bleed from the previous renderer. + fig.savefig(img_fname, + facecolor=fig.get_facecolor(), + edgecolor='none') + + # Check the first pixel has the desired color & alpha + # (approx: 0, 0, 0.1, 0.25) + assert_array_almost_equal(tuple(imread(img_fname)[0, 0]), + (0.0, 0.0, 0.098, 0.250), + decimal=3) + finally: + os.remove(img_fname) + def report_memory(i): pid = os.getpid() @@ -64,3 +108,8 @@ def report_memory(i): ## # w/o text and w/o write_png: Average memory consumed per loop: 0.02 ## # w/o text and w/ write_png : Average memory consumed per loop: 0.3400 ## # w/ text and w/ write_png : Average memory consumed per loop: 0.32 + + +if __name__ == "__main__": + import nose + nose.runmodule(argv=['-s', '--with-doctest'], exit=False) diff --git a/lib/matplotlib/tests/test_figure.py b/lib/matplotlib/tests/test_figure.py index f6f033feef0b..97d92172bbb1 100644 --- a/lib/matplotlib/tests/test_figure.py +++ b/lib/matplotlib/tests/test_figure.py @@ -74,6 +74,22 @@ def test_suptitle(): fig.suptitle('title', color='g', rotation='30') +@image_comparison(baseline_images=['alpha_background'], + extensions=['png', 'svg'], # PDF is wrong + savefig_kwarg={'facecolor': 'black', 'edgecolor': 'none'}) +def test_alpha(): + # We want an image which has a background color of black, with an + # alpha of 0.3. + fig = plt.figure(figsize=[2, 1]) + fig.set_facecolor('black') + fig.patch.set_alpha(0.3) + + import matplotlib.patches as mpatches + fig.patches.append(mpatches.CirclePolygon([20, 20], + radius=15, + facecolor='red')) + + if __name__ == "__main__": import nose nose.runmodule(argv=['-s', '--with-doctest'], exit=False) diff --git a/src/_backend_agg.cpp b/src/_backend_agg.cpp index fa9f93ae14eb..a50a68d152d2 100644 --- a/src/_backend_agg.cpp +++ b/src/_backend_agg.cpp @@ -411,7 +411,7 @@ RendererAgg::RendererAgg(unsigned int width, unsigned int height, double dpi, renderingBuffer.attach(pixBuffer, width, height, stride); pixFmt.attach(renderingBuffer); rendererBase.attach(pixFmt); - rendererBase.clear(agg::rgba(1, 1, 1, 0)); + rendererBase.clear(agg::rgba(0, 0, 0, 0)); rendererAA.attach(rendererBase); rendererBin.attach(rendererBase); hatchRenderingBuffer.attach(hatchBuffer, HATCH_SIZE, HATCH_SIZE, @@ -2388,7 +2388,7 @@ RendererAgg::clear(const Py::Tuple& args) _VERBOSE("RendererAgg::clear"); args.verify_length(0); - rendererBase.clear(agg::rgba(1, 1, 1, 0)); + rendererBase.clear(agg::rgba(0, 0, 0, 0)); return Py::Object(); } From c9851967370bb43d9319362e67815b68318c9a98 Mon Sep 17 00:00:00 2001 From: Wes Campaigne Date: Sun, 31 Mar 2013 12:54:01 -0400 Subject: [PATCH 2/2] Switch agg backend to use pixfmt_rgba32_plain instead of pixfmt_rgba32, which fixes a problem with alpha-blending partially transparent backgrounds. --- doc/users/whats_new.rst | 12 +++++++----- .../test_figure/alpha_background.png | Bin 1205 -> 1670 bytes .../test_figure/alpha_background.svg | 4 ++-- lib/matplotlib/tests/test_agg.py | 8 ++++---- lib/matplotlib/tests/test_figure.py | 15 +++++++++------ src/_backend_agg.cpp | 2 +- src/_backend_agg.h | 2 +- src/_image.cpp | 2 +- 8 files changed, 25 insertions(+), 20 deletions(-) diff --git a/doc/users/whats_new.rst b/doc/users/whats_new.rst index b0707b93ffa4..b87bc11ce098 100644 --- a/doc/users/whats_new.rst +++ b/doc/users/whats_new.rst @@ -148,6 +148,13 @@ default quality is 95; previously, the default quality was 75. This change minimizes the artifacting inherent in JPEG images, particularly with images that have sharp changes in color as plots often do. +Full control of the background color +------------------------------------ +Wes Campaigne and Phil Elson fixed the Agg backend such that PNGs are now +saved with the correct background color when :meth:`fig.patch.get_alpha` is +not 1. + + .. _whats-new-1-2-2: new in matplotlib 1.2.2 @@ -166,11 +173,6 @@ Multiple images on same axes are correctly transparent When putting multiple images onto the same axes, the background color of the axes will now show through correctly. -Full control of the background color ------------------------------------- -Phil Elson fixed the Agg backend such that PNGs are now saved with the correct -background color when :meth:`fig.patch.get_alpha` is not 1. - .. _whats-new-1-2: new in matplotlib-1.2 diff --git a/lib/matplotlib/tests/baseline_images/test_figure/alpha_background.png b/lib/matplotlib/tests/baseline_images/test_figure/alpha_background.png index d09453f36b8e75d8f265f9928b9bfb9a9ede22f2..cc3ccda25dacff4537dcb945a9d6e2188e431c10 100644 GIT binary patch literal 1670 zcmdT_`BxGM5XK9&o>y%_l46&dXXXZ|<w7)!f<#m;;4&HLVbGr!FD-Xvbc*sJW&+yMXpR8S5` zCuwb!rmqrEip>wAcS?&q=`_kkNg8*Qd=sQxIoQFS1OTXc|45kvn|uNQ0Mthz?OY;W zEbz<%A^rOrmll`OSycUpxkhK4u7k-jZo3>_sn#<4)bq`{fzy2^h4JzQPY?lIB2U)W zv4?vtS=R3OUV_Y_M7e=B&3!rt+7x^m8T&hq6k4HIwJw~jSFJdS>@hb^H&PKtm$$4D z*2iADt8)bk2+?eDP;22v;zEnrOG1eBaQ&z_4|4a_v&RtZ+A0clRxWGsu;f8v1 zon{&u*_|-oZ%<8`v8K$W8#gOvn5B)cv`o^_rwpO4=-qr`=GnL~T?i9n4U5EPYG}BF zs3+-TZ>iJ;$KwU{mabWAN!O32z<;+q)$NaFPj+037CwU?gl3&H_q0KjPuo}Z_gJ2s znGGJ~a^0Mm%6Uj+E`v#?eo^)8E-{xvlpN1eG`8qPvg6dHam&;Sy1Lb*rj>n9$ne zDAaG3+ZN5+j!1~!-+}@f?$BvQrO3Ylar))hYu>$XIWGwTZZ) z6%r1|kB|zV@1D6lW>KuEcnRIsamX@469wK1Ay$V?fPkB$qic43wdqfadG4D$j9xDd zLD4SCthGol-VV#pcvco$8u@S+*dj2=%~<({<6FQy|m7&u%sMAHVM3?+x@66W|-GLiKnt28)$!4ugXm#F}TZ7_`rYXAgz(4Ky2m=i_Vjazlg>p zfUEcuJ7Qlq$vNL+j@bH@Ke}aBga@uAJF~!5rf(*Yeb9l|wjdGkcsP6QT@vI5Rq@0h zeAr;WL6$TAkzI~*C_YGs8Osk1T|JhRW2eHD^wY}(km=v9||H3P1Wdp=8Zo`lc`RIJ`ER+crTT=J|Ka}*A{WKXJsVOxA=WQm(c zo5gqI67u32pt2Tw_lO{{o$OP^_m|-WNNkhFJAatgQw_aV9WNiH{{Bj*e z@d9g!ty{@HxAW3Q#5m&@(=20tirF&fGJ;`A8OaQPNTxsmtBoIG*aE0cb?D|)eql?O zptoRv>~3GH>&_5#C|OTO*XKtzHe88h{2PM z4@bLc_8_r-^Z|NYMzL!Ceyz#7Ws#qQAazT=!f6~GQT~_&gVtNu7%lEK6nDmem@5ip zG$&3wi`>%!uj!y!VT$p)^eAGjXwSrTUmHxZFF{DaU-P^j{{k-AM;q^_a7Ezg)#Oooks#z_BH}PTtWw$Kz&I`TYLe z*Gp{dIbu?cp6AHv89$#lnPrYsQ1sUCwv3rmBs)Y51T+&Af(|$=X=wCdWS+>xro_tQ z!X+_OB;wN=UhIC_n*YCH_mTZePM-j-~rzU+wGodhi36uTsivM zkNLU2!&|c@=Nn~hc{VZs;Q7IIN$QH+p}->Z)3)a$Rt119|b>@?(3ej zV5gRTZ&z8PkC;K(o{CPBB@m?}31hnn?%U2-UKY2Pi@O_LHH*R@WraJV;%`%f&y z`kNXL?wMw+2{&joO<;!KNuK$&cfpc19#Mrg^Z!>0@34Ik?d5#=>`JxM zCd!ej^7k*lKdDywK>E|%W|`ybYxhkJ>C8KSG>Yxj{=-+!J2aWNeos$c%V^IK@06@r zud%x7Z`|TD;rt8R zjXyuUaz3lSXWIYjim$PstlHzBW<|d=pD^v3+0EuH|39<)pXl%3;j}I{^Mb>Y|E9no dhi2w~tRXi0&A9EDj{(aa22WQ%mvv4FO#qlh>-GQu diff --git a/lib/matplotlib/tests/baseline_images/test_figure/alpha_background.svg b/lib/matplotlib/tests/baseline_images/test_figure/alpha_background.svg index 85c177195dbb..f59b35d81258 100644 --- a/lib/matplotlib/tests/baseline_images/test_figure/alpha_background.svg +++ b/lib/matplotlib/tests/baseline_images/test_figure/alpha_background.svg @@ -16,7 +16,7 @@ L144 72 L144 0 L0 0 z -" style="opacity:0.3;"/> +" style="fill:#00ff66;opacity:0.4;"/> +" style="fill:#ff0000;opacity:0.6;stroke:#000000;"/> diff --git a/lib/matplotlib/tests/test_agg.py b/lib/matplotlib/tests/test_agg.py index 33699d397d39..1be2e7fb5292 100644 --- a/lib/matplotlib/tests/test_agg.py +++ b/lib/matplotlib/tests/test_agg.py @@ -15,12 +15,12 @@ @cleanup def test_repeated_save_with_alpha(): - # We want an image which has a background color of blue, with an + # We want an image which has a background color of bluish green, with an # alpha of 0.25. fig = Figure([1, 0.4]) canvas = FigureCanvas(fig) - fig.set_facecolor((0, 0, 0.4)) + fig.set_facecolor((0, 1, 0.4)) fig.patch.set_alpha(0.25) # The target color is fig.patch.get_facecolor() @@ -38,9 +38,9 @@ def test_repeated_save_with_alpha(): edgecolor='none') # Check the first pixel has the desired color & alpha - # (approx: 0, 0, 0.1, 0.25) + # (approx: 0, 1.0, 0.4, 0.25) assert_array_almost_equal(tuple(imread(img_fname)[0, 0]), - (0.0, 0.0, 0.098, 0.250), + (0.0, 1.0, 0.4, 0.250), decimal=3) finally: os.remove(img_fname) diff --git a/lib/matplotlib/tests/test_figure.py b/lib/matplotlib/tests/test_figure.py index 97d92172bbb1..6aefb95ca62f 100644 --- a/lib/matplotlib/tests/test_figure.py +++ b/lib/matplotlib/tests/test_figure.py @@ -75,18 +75,21 @@ def test_suptitle(): @image_comparison(baseline_images=['alpha_background'], - extensions=['png', 'svg'], # PDF is wrong - savefig_kwarg={'facecolor': 'black', 'edgecolor': 'none'}) + # only test png and svg. The PDF output appears correct, + # but Ghostscript does not preserve the background color. + extensions=['png', 'svg'], + savefig_kwarg={'facecolor': (0, 1, 0.4), 'edgecolor': 'none'}) def test_alpha(): - # We want an image which has a background color of black, with an - # alpha of 0.3. + # We want an image which has a background color and an + # alpha of 0.4. fig = plt.figure(figsize=[2, 1]) - fig.set_facecolor('black') - fig.patch.set_alpha(0.3) + fig.set_facecolor((0, 1, 0.4)) + fig.patch.set_alpha(0.4) import matplotlib.patches as mpatches fig.patches.append(mpatches.CirclePolygon([20, 20], radius=15, + alpha=0.6, facecolor='red')) diff --git a/src/_backend_agg.cpp b/src/_backend_agg.cpp index a50a68d152d2..96ddf920e3a4 100644 --- a/src/_backend_agg.cpp +++ b/src/_backend_agg.cpp @@ -1108,7 +1108,7 @@ RendererAgg::draw_image(const Py::Tuple& args) inv_mtx.invert(); typedef agg::span_allocator color_span_alloc_type; - typedef agg::image_accessor_clip + typedef agg::image_accessor_clip image_accessor_type; typedef agg::span_interpolator_linear<> interpolator_type; typedef agg::span_image_filter_rgba_nn renderer_base; typedef agg::renderer_scanline_aa_solid renderer_aa; typedef agg::renderer_scanline_bin_solid renderer_bin; diff --git a/src/_image.cpp b/src/_image.cpp index 520c8a82aa4b..13377eeed3e7 100644 --- a/src/_image.cpp +++ b/src/_image.cpp @@ -33,7 +33,7 @@ #include "mplutils.h" -typedef agg::pixfmt_rgba32 pixfmt; +typedef agg::pixfmt_rgba32_plain pixfmt; typedef agg::pixfmt_rgba32_pre pixfmt_pre; typedef agg::renderer_base renderer_base; typedef agg::span_interpolator_linear<> interpolator_type;