You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Transparency always on for PNG in version 3.1.0 #14339
While saving a figure using savefig, the flag for transparency is ignored, and the resulting PNG has a transparent background, even though transparency is set to False.
Note: This affect matplotlib 3.1.0. The error did not occur in 3.0.3 on the same machine as described bellow.
Code for reproduction
# Paste your code herefrommatplotlibimportpeplosaspltfig=plt.figure(figsize=[20, 10], frameon=False)
fig.savefig('./test.png', transparent=False)
plt.close()
I would say that this is actually closer to the indended behavior: you requested that the figure has no frame, and then the transparent kwarg is only documented to make the figure transparent if it is set to True (but not to undo transparency if it is set to False).
Again, this points to the issue of having multiple kwargs that control the same thing: what happens when they are set in a conflicting manner?
I guess, in the meantime, we could make transparent=False undo the transparency, if any (the default, transparent=None, would do nothing). Obviously this could also break some other users that were explicitly passing transparent=False and hoping that nothing happens, but at least they can pass transparent=None instead to achieve that...
transparent=None currently means use rcParams['savefig.transparent'] (default False). That will have an explicit effect on the axes patches. Meaning "do not change" for the figure patch at the same time would be even more confusing.
There are two options:
Make transparent always enables/disables visibility of the figure patch (None only meaning to use the rcParams).
Let transparent only hide visible figure patches. This assumes that the user has intentionally hidden the figure patch and if he wants to draw it, he will explicitly enable it.
2 is the current behavior and I'm leaning towards it. 1) doesn't make it possible to have axes patches but not figure patches visible.
The main issue, for me, is that the new behavior breaks a test in a project of mine (comparing a generated image with a reference). Is there an, preferably easy, or simple mitigation, or migration path?
The transparent kwarg is basically a red herring here, previously even
from matplotlib import pyplot as plt
fig = plt.figure(frameon=False)
fig.subplots()
fig.savefig('./test.png')
plt.close()
would result in a plot without a transparent background because background transparency would be read from rcParams["figure.frameon"].
As argued elsewhere I think frameon is basically a bad idea...
You can work around this (I think) with
from matplotlib import pyplot as plt
fig = plt.figure(facecolor="none") # Sets the figure patch to transparent.
fig.savefig('./test.png', facecolor="w") # Sets the figure patch to solid white for saving.
The source of the problem with tostring_rgb is #5336 which is because it just truncates the alpha channel! Because of the way anti-aliasing is implemented in Agg, the edges of the letters are blended into what ever color is below it including the alpha. If you start with a (0, 0, 0, 0) as the background, the alpha survives all the way to the end, hence the RGB channels of the text are really chunky. If you grab the alpha channel and blend in into white "by hand" (or have a non-transparent figure patch), then it looks OK:
I got bitten by this behavior pretty hard today, when I tried to make a GIF. My strategy of writing individual frames with savefig and then writing a GIF with imageio had worked just fine a few months back.
But today the resulting GIFs were super ugly with the artifacts from above. For 2 hours I suspected compression when the GIF was created and tried PIL/imageio/moviepy - eventually figuring out that they all use PIL under the hood.
All the frames I saved as intermediates looked fine!
Only when I found my files from January and compared the intermediate frames (good thing I saved them to disk), I noticed that even though they were all PNG, the new ones were transparent.
After finding this issue rather quickly, because transparent=False didn't work as advertised, it took me another hour to make it work with facecolor='w' and cutting away the alpha channel manually, because the PNGs were still saved with alpha.
It is also not clear to me in what set of conditions you would want a warning?
savefig(transparent=False) should not silently ignore the kwarg.
Generally something like fig.to_rgba_array() and fig.to_rgb_array() would be great.
transparent : bool
If *True*, the Axes patches will all be transparent; the
figure patch will also be transparent unless facecolor
and/or edgecolor are specified via kwargs.
This is useful, for example, for displaying
a plot on top of a colored background on a web page. The
transparency of these patches will be restored to their
original values upon exit of this function.
which says setting this to True will make the axes and figure patches transparent if it is True, it makes no claims as to what it will do you you set the value False. On one hand I see the motivation "False means the patch is forced to be not transparent", but "False means do nothing" is also reasonable (and is what it actually does!
One key problem with trying to implement anything for False is that we have to pick some color to blend into (yes, white a a reasonable option, but fig.savefig already gives you a way to set the patch color!),
The default value of transparent= is None in which case we fallback to the value in rcparams (which defaults to False which means "do nothing"). I guess we could warn when the user passes False explicitly to say "this probably is not doing what you think it is doing". However, the default default is also False, we do not want to warn on every savefig for a kwarg/rcParam that many users do not know exists so it seems a bit weird to warn in one case and not the other. Further, the default behavior of "please do not change my Figure and Axes patch color" seems like a reasonable thing for someone ask for defensively (in case they are a library author and do not want to risk their users rcparams messing with them).
I'm going to open a PR adding "If False this will do nothing" to the docstring as I think that is the best course forward and will hopefully prevent future confusion / frustration.
Just another +1 to the visibility of this issue, because upgrading matplotlib broke all my graphs. I tried for several hours to fix it before I found this bug, and the solution wasn't any of the things I expected.
Activity
[-]Transparancy always on for PNG in version 3.1[/-][+]Transparency always on for PNG in version 3.1.0[/+]QuLogic commentedon May 27, 2019
This bisects to 38e3896, backport of #11692, and possibly the interaction between
frameonandtransparentwas not realized at the time.cc @anntzer
anntzer commentedon May 27, 2019
I would say that this is actually closer to the indended behavior: you requested that the figure has no frame, and then the
transparentkwarg is only documented to make the figure transparent if it is set to True (but not to undo transparency if it is set to False).Again, this points to the issue of having multiple kwargs that control the same thing: what happens when they are set in a conflicting manner?
I guess, in the meantime, we could make
transparent=Falseundo the transparency, if any (the default,transparent=None, would do nothing). Obviously this could also break some other users that were explicitly passingtransparent=Falseand hoping that nothing happens, but at least they can passtransparent=Noneinstead to achieve that...timhoffm commentedon May 30, 2019
transparent=Nonecurrently means usercParams['savefig.transparent'](default False). That will have an explicit effect on the axes patches. Meaning "do not change" for the figure patch at the same time would be even more confusing.There are two options:
transparentalways enables/disables visibility of the figure patch (None only meaning to use the rcParams).2 is the current behavior and I'm leaning towards it. 1) doesn't make it possible to have axes patches but not figure patches visible.
cLupus commentedon May 30, 2019
The main issue, for me, is that the new behavior breaks a test in a project of mine (comparing a generated image with a reference). Is there an, preferably easy, or simple mitigation, or migration path?
anntzer commentedon May 30, 2019
The
transparentkwarg is basically a red herring here, previously evenwould result in a plot without a transparent background because background transparency would be read from
rcParams["figure.frameon"].As argued elsewhere I think
frameonis basically a bad idea...You can work around this (I think) with
efiring commentedon Jun 17, 2019
@tacaswell I think you were going to write an explanation and close this, with the idea that the present behavior is intended. Correct?
anntzer commentedon Sep 29, 2019
I'm going to claim that this is the intended behavior (see discussion above) and close this, especially as a workaround is available.
michaelosthege commentedon May 13, 2021
Would it be possible to print a warning when
transparent=Falseis used?Also it looks like
fig.canvas.tostring_rgb()is affected - at least it results in crappy quality:tacaswell commentedon May 13, 2021
The source of the problem with
tostring_rgbis #5336 which is because it just truncates the alpha channel! Because of the way anti-aliasing is implemented in Agg, the edges of the letters are blended into what ever color is below it including the alpha. If you start with a (0, 0, 0, 0) as the background, the alpha survives all the way to the end, hence the RGB channels of the text are really chunky. If you grab the alpha channel and blend in into white "by hand" (or have a non-transparent figure patch), then it looks OK:@michaelosthege It is also not clear to me in what set of conditions you would want a warning?
michaelosthege commentedon May 13, 2021
@tacaswell thanks for the explanation!
I got bitten by this behavior pretty hard today, when I tried to make a GIF. My strategy of writing individual frames with
savefigand then writing a GIF withimageiohad worked just fine a few months back.But today the resulting GIFs were super ugly with the artifacts from above. For 2 hours I suspected compression when the GIF was created and tried PIL/imageio/moviepy - eventually figuring out that they all use PIL under the hood.
All the frames I saved as intermediates looked fine!
Only when I found my files from January and compared the intermediate frames (good thing I saved them to disk), I noticed that even though they were all PNG, the new ones were transparent.
After finding this issue rather quickly, because
transparent=Falsedidn't work as advertised, it took me another hour to make it work withfacecolor='w'and cutting away the alpha channel manually, because the PNGs were still saved with alpha.savefig(transparent=False)should not silently ignore the kwarg.Generally something like
fig.to_rgba_array()andfig.to_rgb_array()would be great.All the solutions one finds when searching for "pyplot figure to numpy array" are really verbose code with buffers and reshaping. And they all suffer from #5336.
tacaswell commentedon May 13, 2021
As of 3.1.0 you can do:
which gets you the full images as an array.
The docs on this are:
which says setting this to True will make the axes and figure patches transparent if it is True, it makes no claims as to what it will do you you set the value False. On one hand I see the motivation "False means the patch is forced to be not transparent", but "False means do nothing" is also reasonable (and is what it actually does!
One key problem with trying to implement anything for
Falseis that we have to pick some color to blend into (yes, white a a reasonable option, butfig.savefigalready gives you a way to set the patch color!),The default value of
transparent=isNonein which case we fallback to the value in rcparams (which defaults toFalsewhich means "do nothing"). I guess we could warn when the user passesFalseexplicitly to say "this probably is not doing what you think it is doing". However, the default default is also False, we do not want to warn on every savefig for a kwarg/rcParam that many users do not know exists so it seems a bit weird to warn in one case and not the other. Further, the default behavior of "please do not change my Figure and Axes patch color" seems like a reasonable thing for someone ask for defensively (in case they are a library author and do not want to risk their users rcparams messing with them).I'm going to open a PR adding "If False this will do nothing" to the docstring as I think that is the best course forward and will hopefully prevent future confusion / frustration.
DOC: clarify that savefig(..., transparent=False) has no effect
DOC: clarify that savefig(..., transparent=False) has no effect
ToyKeeper commentedon Feb 6, 2023
Just another +1 to the visibility of this issue, because upgrading matplotlib broke all my graphs. I tried for several hours to fix it before I found this bug, and the solution wasn't any of the things I expected.
To fix it, I added
fig.set_frameon(True).Simple 1-line change, but definitely not obvious.