Skip to content

Commit

Permalink
Deprecate JPEG-specific kwargs and rcParams to savefig.
Browse files Browse the repository at this point in the history
Saving Matplotlib figures to jpeg is generally not a great idea to start
with (even at the default quality of 95 there are visible (faint)
artefacts around sharp lines, and at quality=95 the files produced are
bigger than the corresponding pngs anyways).

We don't need to completely get rid of jpeg support, but we can at least
simplify the code path (which otherwise also needs to be duplicated in
mplcairo) and not have to document these jpeg-specific kwargs (in two
places!).  Note that users can still set them via `pil_kwargs`.

The changes to _delete_parameter are so that we can write
```
@_delete_parameter(..., "foo")
def f(**kwargs): ...
```
where `foo` actually only shows up in `kwargs`.
  • Loading branch information
anntzer committed Mar 11, 2020
1 parent 50242cf commit e12f16a
Show file tree
Hide file tree
Showing 8 changed files with 71 additions and 24 deletions.
10 changes: 9 additions & 1 deletion doc/api/next_api_changes/deprecations.rst
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,6 @@ The following validators, defined in `.rcsetup`, are deprecated:
``validate_movie_frame_fmt``, ``validate_axis_locator``,
``validate_movie_html_fmt``, ``validate_grid_axis``,
``validate_axes_titlelocation``, ``validate_toolbar``,
<<<<<<< HEAD
``validate_ps_papersize``, ``validate_legend_loc``,
``validate_bool_maybe_none``, ``validate_hinting``,
``validate_movie_writers``.
Expand Down Expand Up @@ -299,3 +298,12 @@ is deprecated, set the offset to 0 instead.
``autofmt_xdate(which=None)``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This is deprecated, use its more explicit synonym, ``which="major"``, instead.

JPEG options
~~~~~~~~~~~~
The *quality*, *optimize*, and *progressive* keyword arguments to
`~.Figure.savefig`, which were only used when saving to JPEG, are deprecated.
:rc:`savefig.jpeg_quality` is likewise deprecated.

Such options should now be directly passed to Pillow using
``savefig(..., pil_kwargs={"quality": ..., "optimize": ..., "progressive": ...})``.
1 change: 1 addition & 0 deletions lib/matplotlib/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -604,6 +604,7 @@ def gen_candidates():
'animation.avconv_args': ('3.3',),
'mathtext.fallback_to_cm': ('3.3',),
'keymap.all_axes': ('3.3',),
'savefig.jpeg_quality': ('3.3',),
}


Expand Down
24 changes: 21 additions & 3 deletions lib/matplotlib/backends/backend_agg.py
Original file line number Diff line number Diff line change
Expand Up @@ -512,6 +512,12 @@ def print_to_buffer(self):
# matches the dpi kwarg (if any).

@cbook._delete_parameter("3.2", "dryrun")
@cbook._delete_parameter("3.3", "quality",
alternative="pil_kwargs={'quality': ...}")
@cbook._delete_parameter("3.3", "optimize",
alternative="pil_kwargs={'optimize': ...}")
@cbook._delete_parameter("3.3", "progressive",
alternative="pil_kwargs={'progressive': ...}")
def print_jpg(self, filename_or_obj, *args, dryrun=False, pil_kwargs=None,
**kwargs):
"""
Expand All @@ -528,14 +534,17 @@ def print_jpg(self, filename_or_obj, *args, dryrun=False, pil_kwargs=None,
The image quality, on a scale from 1 (worst) to 95 (best).
Values above 95 should be avoided; 100 disables portions of
the JPEG compression algorithm, and results in large files
with hardly any gain in image quality.
with hardly any gain in image quality. This parameter is
deprecated.
optimize : bool, default: False
Whether the encoder should make an extra pass over the image
in order to select optimal encoder settings.
in order to select optimal encoder settings. This parameter is
deprecated.
progressive : bool, default: False
Whether the image should be stored as a progressive JPEG file.
This parameter is deprecated.
pil_kwargs : dict, optional
Additional keyword arguments that are passed to
Expand All @@ -555,7 +564,16 @@ def print_jpg(self, filename_or_obj, *args, dryrun=False, pil_kwargs=None,
for k in ["quality", "optimize", "progressive"]:
if k in kwargs:
pil_kwargs.setdefault(k, kwargs[k])
pil_kwargs.setdefault("quality", mpl.rcParams["savefig.jpeg_quality"])
if "quality" not in pil_kwargs:
quality = pil_kwargs["quality"] = \
dict.__getitem__(mpl.rcParams, "savefig.jpeg_quality")
if quality not in [0, 75, 95]: # default qualities.
cbook.warn_deprecated(
"3.3", name="savefig.jpeg_quality", obj_type="rcParam",
addendum="Set the quality using "
"`pil_kwargs={'quality': ...}`; the future default "
"quality will be 75, matching the default of Pillow and "
"libjpeg.")
pil_kwargs.setdefault("dpi", (self.figure.dpi, self.figure.dpi))
return background.save(
filename_or_obj, format='jpeg', **pil_kwargs)
Expand Down
5 changes: 3 additions & 2 deletions lib/matplotlib/backends/backend_wx.py
Original file line number Diff line number Diff line change
Expand Up @@ -885,8 +885,9 @@ def _print_image(self, filename, filetype, *args, **kwargs):
# are saving a JPEG, convert the wx.Bitmap to a wx.Image,
# and set the quality.
if filetype == wx.BITMAP_TYPE_JPEG:
jpeg_quality = kwargs.get('quality',
mpl.rcParams['savefig.jpeg_quality'])
jpeg_quality = kwargs.get(
'quality',
dict.__getitem__(mpl.rcParams, 'savefig.jpeg_quality'))
image = self.bitmap.ConvertToImage()
image.SetOption(wx.IMAGE_OPTION_QUALITY, str(jpeg_quality))

Expand Down
47 changes: 31 additions & 16 deletions lib/matplotlib/cbook/deprecation.py
Original file line number Diff line number Diff line change
Expand Up @@ -312,15 +312,17 @@ def _delete_parameter(since, name, func=None, **kwargs):
Decorator indicating that parameter *name* of *func* is being deprecated.
The actual implementation of *func* should keep the *name* parameter in its
signature.
signature, or accept a ``**kwargs`` argument (through which *name* would be
passed).
Parameters that come after the deprecated parameter effectively become
keyword-only (as they cannot be passed positionally without triggering the
DeprecationWarning on the deprecated parameter), and should be marked as
such after the deprecation period has passed and the deprecated parameter
is removed.
Additional keyword arguments are passed to `.warn_deprecated`.
Parameters other than *since*, *name*, and *func* are keyword-only and
forwarded to `.warn_deprecated`.
Examples
--------
Expand All @@ -334,17 +336,25 @@ def func(used_arg, other_arg, unused, more_args): ...
return functools.partial(_delete_parameter, since, name, **kwargs)

signature = inspect.signature(func)
assert name in signature.parameters, (
f"Matplotlib internal error: {name!r} must be a parameter for "
f"{func.__name__}()")
kind = signature.parameters[name].kind
is_varargs = kind is inspect.Parameter.VAR_POSITIONAL
is_varkwargs = kind is inspect.Parameter.VAR_KEYWORD
if not is_varargs and not is_varkwargs:
func.__signature__ = signature = signature.replace(parameters=[
param.replace(default=_deprecated_parameter) if param.name == name
else param
for param in signature.parameters.values()])
# Name of `**kwargs` parameter of the decorated function, typically
# "kwargs" if such a parameter exists, or None if the decorated function
# doesn't accept `**kwargs`.
kwargs_name = next((param.name for param in signature.parameters.values()
if param.kind == inspect.Parameter.VAR_KEYWORD), None)
if name in signature.parameters:
kind = signature.parameters[name].kind
is_varargs = kind is inspect.Parameter.VAR_POSITIONAL
is_varkwargs = kind is inspect.Parameter.VAR_KEYWORD
if not is_varargs and not is_varkwargs:
func.__signature__ = signature = signature.replace(parameters=[
param.replace(default=_deprecated_parameter)
if param.name == name else param
for param in signature.parameters.values()])
else:
is_varargs = is_varkwargs = False
assert kwargs_name, (
f"Matplotlib internal error: {name!r} must be a parameter for "
f"{func.__name__}()")

@functools.wraps(func)
def wrapper(*inner_args, **inner_kwargs):
Expand All @@ -361,13 +371,18 @@ def wrapper(*inner_args, **inner_kwargs):
f"support for them will be removed %(removal)s.")
# We cannot just check `name not in arguments` because the pyplot
# wrappers always pass all arguments explicitly.
elif name in arguments and arguments[name] != _deprecated_parameter:
elif any(name in d and d[name] != _deprecated_parameter
for d in [arguments, arguments.get(kwargs_name, {})]):
addendum = (f"If any parameter follows {name!r}, they should be "
f"passed as keyword, not positionally.")
if kwargs.get("addendum"):
kwargs["addendum"] += " " + addendum
else:
kwargs["addendum"] = addendum
warn_deprecated(
since,
name=repr(name),
obj_type=f"parameter of {func.__name__}()",
addendum=(f"If any parameter follows {name!r}, they should be "
f"passed as keyword, not positionally."),
**kwargs)
return func(*inner_args, **inner_kwargs)

Expand Down
6 changes: 6 additions & 0 deletions lib/matplotlib/figure.py
Original file line number Diff line number Diff line change
Expand Up @@ -2065,17 +2065,23 @@ def savefig(self, fname, *, transparent=None, **kwargs):
the JPEG compression algorithm, and results in large files
with hardly any gain in image quality.
This parameter is deprecated.
optimize : bool, default: False
Applicable only if *format* is 'jpg' or 'jpeg', ignored otherwise.
Whether the encoder should make an extra pass over the image
in order to select optimal encoder settings.
This parameter is deprecated.
progressive : bool, default: False
Applicable only if *format* is 'jpg' or 'jpeg', ignored otherwise.
Whether the image should be stored as a progressive JPEG file.
This parameter is deprecated.
facecolor : color, default: :rc:`savefig.facecolor`
The facecolor of the figure.
Expand Down
1 change: 0 additions & 1 deletion lib/matplotlib/mpl-data/stylelib/classic.mplstyle
Original file line number Diff line number Diff line change
Expand Up @@ -420,7 +420,6 @@ savefig.bbox : standard # 'tight' or 'standard'.
# e.g. setting animation.writer to ffmpeg will not work,
# use ffmpeg_file instead
savefig.pad_inches : 0.1 # Padding to be used when bbox is set to 'tight'
savefig.jpeg_quality: 95 # when a jpeg is saved, the default quality parameter.
savefig.transparent : False # setting that controls whether figures are saved with a
# transparent background by default
savefig.orientation : portrait
Expand Down
1 change: 0 additions & 1 deletion matplotlibrc.template
Original file line number Diff line number Diff line change
Expand Up @@ -651,7 +651,6 @@
# e.g. setting animation.writer to ffmpeg will not work,
# use ffmpeg_file instead
#savefig.pad_inches: 0.1 # Padding to be used when bbox is set to 'tight'
#savefig.jpeg_quality: 95 # when a jpeg is saved, the default quality parameter.
#savefig.directory: ~ # default directory in savefig dialog box,
# leave empty to always use current working directory
#savefig.transparent: False # setting that controls whether figures are saved with a
Expand Down

0 comments on commit e12f16a

Please sign in to comment.