Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Jpeg quality 95 by default with rendering with PIL #1771

Merged
merged 10 commits into from

4 participants

@dhyams

This is just my opinion, but when rendering a plot to JPEG, the default quality should be a bit higher. Plots are usually (admittedly not always) full of starkly defined edges, that don't look very good once the jpeg format gets finished artifacting around all of them.

While it's true that a user shouldn't write a jpeg of something that doesn't consist of smoothly varying colors, if they do, let's try to make it look as good as is reasonable.

I've attached two images, one with the 75 quality and one with 95. The 75 quality one was 18K in size, and the 95 quality was 30K in size.

after
out

Daniel Hyams matplotlib#1710
When a font doesn't have a glyph name, we should synthesize.  This fixes the
Arial font problems under Windows 8.
05a9d9e
@mdboom
Owner

:+1: from me. I don't think disk space or bandwidth are at such a premium that this doesn't make sense as a default.

@pelson
Collaborator

Why not have a .matplotlibrc parameter for this, that way users can set it to whatever they like? I'd be happy enough for 95 to be the default.

@mdboom
Owner

@pelson: Good suggestion.

@dhyams

On the above commit; I'm not sure what the best practices are when dealing with rcParams (as far as whether to assume the entry is always there, and provide an initialization for it (savefig.jpeg_quality), or to use rcParams.get() as I have in the commit.

Also should the parameter be named savefig.pil_jpeg_quality or something like that, to indicate that it's only used for PIL?

@mdboom
Owner

The rcParam should be defined in rcsetup.py and described in matplotlibrc.template -- then you don't need to worry about it being defined or not -- it will always have a default value even if the user doesn't specify it.

As for the name -- it would be nice to fix up all of the backends that write jpegs to support this (it looks doable, just a matter of working through all the various APIs), so I'd be in favor of calling it savefig.jpeg_quality and then creating issues for all of the backends with JPEG support to start supporting this parameter.

@dhyams

On the above commits:
1) I was unable to test gdk and gtk on my current setup; so that's still pending.
2) quality parameter not supported in the mac backend. I think it shouldn't be too hard, but I'm unable to test it at the present time, and it takes some mucking around with some objective C code to make it work.
3) otherwise, works fine on the wx and wxagg backends (which are really the same, in this branch anyway; see #1770 for more info on this)

lib/matplotlib/backends/backend_gtk.py
@@ -469,9 +469,18 @@ def _print_image(self, filename, format):
pixbuf.get_from_drawable(pixmap, pixmap.get_colormap(),
0, 0, 0, 0, width, height)
+ # set the default quality, if we are writing a JPEG.
+ # http://www.pygtk.org/docs/pygtk/class-gdkpixbuf.html#method-gdkpixbuf--save
+ options = cbook.restrict_dict(kwargs, ['quality'])
+ if format in ['jpg','jpeg']:
+ if 'quality' not in options:
+ options['quality'] = rcParams['savefig.jpeg_quality']
+ options['quality'] = str(options['quality'])
+
@pelson Collaborator
pelson added a note

PEP8: Please use just one newline between blocks of code.

@dhyams
dhyams added a note

do you mean between lines 477 and 478?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@pelson pelson commented on the diff
lib/matplotlib/backends/backend_wx.py
@@ -1162,15 +1162,29 @@ def _print_image(self, filename, filetype, *args, **kwargs):
gc = renderer.new_gc()
self.figure.draw(renderer)
+
+ # image is the object that we call SaveFile on.
+ image = self.bitmap
+
+ # set the JPEG quality appropriately. Unfortunately, it is only possible
+ # to set the quality on a wx.Image object. So if we 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',rcParams['savefig.jpeg_quality'])
+ image = self.bitmap.ConvertToImage()
+ image.SetOption(wx.IMAGE_OPTION_QUALITY,str(jpeg_quality))
+
@pelson Collaborator
pelson added a note

Please remove newline.

@dhyams
dhyams added a note

Do you mean line 1168?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@pelson
Collaborator

@dhyams - looks like you've done most of the hard work. The PR wont merge automatically, so would you mind re-basing. I'd also be interested to hear what pieces of work you think are critical to be done before this PR should be merged.

Thanks,

@pelson
Collaborator

Bump. I'm keen to get this into v1.3 (~ a months time)

@dhyams

Sorry; I have been a little bogged down lately and let this one slip. I'll make the requested changes as soon as I can, and rebase.

@dhyams

Re: pelson on what else needs to be done before this PR is merged: maybe can someone test on gtk and gdk? My current setup precludes the use of these two backends.

@dhyams

git clone git@github.com:dhyams/matplotlib.git
git remote add original git://github.com/matplotlib/matplotlib.git
git checkout jpeg_quality_95_by_default
git rebase origin/master
conflict happens, so I edit to resolve the conflict
git add matplotlibrc.template
git rebase --continue

At this point, everything looks OK.

git push

To git@github.com:dhyams/matplotlib.git
! [rejected] jpeg_quality_95_by_default -> jpeg_quality_95_by_default (non-fast-forward)
error: failed to push some refs to 'git@github.com:dhyams/matplotlib.git'
To prevent you from losing history, non-fast-forward updates were rejected
Merge the remote changes (e.g. 'git pull') before pushing again. See the
'Note about fast-forwards' section of 'git push --help' for details.

Is it correct to do a

git push -f

@mdboom
Owner

I usually run the test suite again once the conflicts have been resolved to make sure, but after that, yes, git push -f should be fine.

@WeatherGod
Collaborator
@dhyams

I just pushed the rebasing on this one.

@dhyams

Bump, so that the rebasing doesn't get out of date. Is this one OK to merge?

@mdboom mdboom merged commit 9bb698a into matplotlib:master

1 check passed

Details default The Travis build passed
@pelson
Collaborator

Thanks for merging @mdboom - I think the code changes were ready to go & @dhyams was right to encourage a merge before the PR went stale.

@dhyams: Given there is a change to the default JPEG quality, and there is a new rcParam, would you mind creating a new PR with the necessary changes to the doc/api/whats_new.rst (for the fact that the value has changed from 75 to 95) and users/whats_new.rst (for the fact that there is a new rcParam for JPEG quality).

While you're at it, this PR has introduced a couple of double newlines (one in lib/matplotlib/backends/backend_gtk.py and one in lib/matplotlib/backends/backend_wx.py). Whilst these are cosmetic, we are trying to standardise towards PEP8 (specifically http://www.python.org/dev/peps/pep-0008/#blank-lines); would you mind tidying these up? (this is the epitome of nitpicking, for which I apologise :smile:)

@dhyams

Ok this has been done here:

#1940

@pelson
Collaborator

Brilliant. Thanks @dhyams!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Feb 17, 2013
  1. matplotlib#1710

    Daniel Hyams authored
    When a font doesn't have a glyph name, we should synthesize.  This fixes the
    Arial font problems under Windows 8.
Commits on Apr 19, 2013
  1. Merge remote branch 'original/master'

    Daniel Hyams authored
    Conflicts:
    	src/ft2font.cpp
  2. Set default quality for rendering jpegs with PIL to 95.

    Daniel Hyams authored
  3. Straw man commit for making the jpeg quality (when saved by PIL) conf…

    Daniel Hyams authored
    …igurable
    
    via rcParams.
  4. Added savefig.jpeg_quality to the rcParams correctly

    Daniel Hyams authored
    With a short one-line doc in matplotlibrc.template.
  5. added jpeg quality support for gdk backend

    Daniel Hyams authored
  6. Added jpeg quality support for gtk backend

    Daniel Hyams authored
  7. Added jpeg quality support for wx backend

    Daniel Hyams authored
  8. Two newline fixes to comply with PEP8

    Daniel Hyams authored
This page is out of date. Refresh to see the latest.
View
11 lib/matplotlib/backend_bases.py
@@ -1930,9 +1930,10 @@ def print_jpg(self, filename_or_obj, *args, **kwargs):
Supported kwargs:
*quality*: The image quality, on a scale from 1 (worst) to
- 95 (best). The default is 75. Values above 95 should
- be avoided; 100 completely disables the JPEG
- quantization stage.
+ 95 (best). The default is 95, if not given in the
+ matplotlibrc file in the savefig.jpeg_quality parameter.
+ Values above 95 should be avoided; 100 completely
+ disables the JPEG quantization stage.
*optimize*: If present, indicates that the encoder should
make an extra pass over the image in order to select
@@ -1949,6 +1950,10 @@ def print_jpg(self, filename_or_obj, *args, **kwargs):
image = Image.frombuffer('RGBA', size, buf, 'raw', 'RGBA', 0, 1)
options = cbook.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)
print_jpeg = print_jpg
View
12 lib/matplotlib/backends/backend_gdk.py
@@ -19,6 +19,7 @@ def fn_name(): return sys._getframe(1).f_code.co_name
import numpy as np
import matplotlib
+from matplotlib import rcParams
from matplotlib._pylab_helpers import Gcf
from matplotlib.backend_bases import RendererBase, GraphicsContextBase, \
FigureManagerBase, FigureCanvasBase
@@ -471,4 +472,13 @@ def _print_image(self, filename, format, *args, **kwargs):
pixbuf.get_from_drawable(pixmap, pixmap.get_colormap(),
0, 0, 0, 0, width, height)
- pixbuf.save(filename, format)
+ # set the default quality, if we are writing a JPEG.
+ # http://www.pygtk.org/docs/pygtk/class-gdkpixbuf.html#method-gdkpixbuf--save
+ options = cbook.restrict_dict(kwargs, ['quality'])
+ if format in ['jpg','jpeg']:
+ if 'quality' not in options:
+ options['quality'] = rcParams['savefig.jpeg_quality']
+ options['quality'] = str(options['quality'])
+
+ pixbuf.save(filename, format, options=options)
+
View
16 lib/matplotlib/backends/backend_gtk.py
@@ -452,7 +452,7 @@ def print_jpeg(self, filename, *args, **kwargs):
def print_png(self, filename, *args, **kwargs):
return self._print_image(filename, 'png')
- def _print_image(self, filename, format):
+ def _print_image(self, filename, format, *args, **kwargs):
if self.flags() & gtk.REALIZED == 0:
# for self.window(for pixmap) and has a side effect of altering
# figure width,height (via configure-event?)
@@ -469,9 +469,19 @@ def _print_image(self, filename, format):
pixbuf.get_from_drawable(pixmap, pixmap.get_colormap(),
0, 0, 0, 0, width, height)
+ # set the default quality, if we are writing a JPEG.
+ # http://www.pygtk.org/docs/pygtk/class-gdkpixbuf.html#method-gdkpixbuf--save
+ options = cbook.restrict_dict(kwargs, ['quality'])
+ if format in ['jpg','jpeg']:
+ if 'quality' not in options:
+ options['quality'] = rcParams['savefig.jpeg_quality']
+
+ options['quality'] = str(options['quality'])
+
+
if is_string_like(filename):
try:
- pixbuf.save(filename, format)
+ pixbuf.save(filename, format, options=options)
except gobject.GError as exc:
error_msg_gtk('Save figure failure:\n%s' % (exc,), parent=self)
elif is_writable_file_like(filename):
@@ -479,7 +489,7 @@ def _print_image(self, filename, format):
def save_callback(buf, data=None):
data.write(buf)
try:
- pixbuf.save_to_callback(save_callback, format, user_data=filename)
+ pixbuf.save_to_callback(save_callback, format, user_data=filename, options=options)
except gobject.GError as exc:
error_msg_gtk('Save figure failure:\n%s' % (exc,), parent=self)
else:
View
17 lib/matplotlib/backends/backend_wx.py
@@ -1162,15 +1162,28 @@ def _print_image(self, filename, filetype, *args, **kwargs):
gc = renderer.new_gc()
self.figure.draw(renderer)
+
+ # image is the object that we call SaveFile on.
+ image = self.bitmap
+ # set the JPEG quality appropriately. Unfortunately, it is only possible
+ # to set the quality on a wx.Image object. So if we 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',rcParams['savefig.jpeg_quality'])
+ image = self.bitmap.ConvertToImage()
+ image.SetOption(wx.IMAGE_OPTION_QUALITY,str(jpeg_quality))
+
@pelson Collaborator
pelson added a note

Please remove newline.

@dhyams
dhyams added a note

Do you mean line 1168?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
# Now that we have rendered into the bitmap, save it
# to the appropriate file type and clean up
if is_string_like(filename):
- if not self.bitmap.SaveFile(filename, filetype):
+ if not image.SaveFile(filename, filetype):
DEBUG_MSG('print_figure() file save error', 4, self)
raise RuntimeError('Could not save figure to %s\n' % (filename))
elif is_writable_file_like(filename):
- if not self.bitmap.ConvertToImage().SaveStream(filename, filetype):
+ if not isinstance(image,wx.Image):
+ image = image.ConvertToImage()
+ if not image.SaveStream(filename, filetype):
DEBUG_MSG('print_figure() file save error', 4, self)
raise RuntimeError('Could not save figure to %s\n' % (filename))
View
1  lib/matplotlib/rcsetup.py
@@ -687,6 +687,7 @@ def __call__(self, s):
'savefig.frameon': [True, validate_bool],
'savefig.orientation': ['portrait', validate_orientation], # edgecolor;
#white
+ 'savefig.jpeg_quality': [95, validate_int],
# what to add to extensionless filenames
'savefig.extension': ['png', deprecate_savefig_extension],
# value checked by backend at runtime
View
17 matplotlibrc.template
@@ -362,14 +362,15 @@ text.hinting_factor : 8 # Specifies the amount of softness for hinting in the
# the default savefig params can be different from the display params
# e.g., you may want a higher resolution, or to make the figure
# background white
-#savefig.dpi : 100 # figure dots per inch
-#savefig.facecolor : white # figure facecolor when saving
-#savefig.edgecolor : white # figure edgecolor when saving
-#savefig.format : png # png, ps, pdf, svg
-#savefig.bbox : standard # 'tight' or 'standard'.
-#savefig.pad_inches : 0.1 # Padding to be used when bbox is set to 'tight'
-#savefig.directory : ~ # default directory in savefig dialog box,
- # leave empty to always use current working directory
+#savefig.dpi : 100 # figure dots per inch
+#savefig.facecolor : white # figure facecolor when saving
+#savefig.edgecolor : white # figure edgecolor when saving
+#savefig.format : png # png, ps, pdf, svg
+#savefig.bbox : standard # 'tight' or 'standard'.
+#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
# tk backend params
#tk.window_focus : False # Maintain shell focus for TkAgg
View
3  src/ft2font.cpp
@@ -1627,7 +1627,8 @@ FT2Font::get_glyph_name(const Py::Tuple & args)
{
throw Py::RuntimeError("Could not get glyph names.");
}
- }
+ }
+
return Py::String(buffer);
}
PYCXX_VARARGS_METHOD_DECL(FT2Font, get_glyph_name)
Something went wrong with that request. Please try again.