diff --git a/PIL/JpegImagePlugin.py b/PIL/JpegImagePlugin.py index aaef663a7..1d300fc04 100644 --- a/PIL/JpegImagePlugin.py +++ b/PIL/JpegImagePlugin.py @@ -498,7 +498,7 @@ def _save(im, fp, filename): else: if subsampling in presets: subsampling = presets[subsampling].get('subsampling', -1) - if qtables in presets: + if isStringType(qtables) and qtables in presets: qtables = presets[qtables].get('quantization') if subsampling == "4:4:4": diff --git a/Tests/test_file_jpeg.py b/Tests/test_file_jpeg.py index 158299c36..dae1d0019 100644 --- a/Tests/test_file_jpeg.py +++ b/Tests/test_file_jpeg.py @@ -224,7 +224,57 @@ class TestFileJpeg(PillowTestCase): filename = "Tests/images/junk_jpeg_header.jpg" Image.open(filename) + def test_qtables(self): + im = Image.open("Tests/images/lena.jpg") + qtables = im.quantization + reloaded = self.roundtrip(im, qtables=qtables, subsampling=0) + self.assertEqual(im.quantization, reloaded.quantization) + self.assert_image_similar(im, self.roundtrip(im, qtables='web_low'), 30) + self.assert_image_similar(im, self.roundtrip(im, qtables='web_high'), 30) + self.assert_image_similar(im, self.roundtrip(im, qtables='keep'), 30) + #values from wizard.txt in jpeg9-a src package. + standard_l_qtable = [int(s) for s in """ + 16 11 10 16 24 40 51 61 + 12 12 14 19 26 58 60 55 + 14 13 16 24 40 57 69 56 + 14 17 22 29 51 87 80 62 + 18 22 37 56 68 109 103 77 + 24 35 55 64 81 104 113 92 + 49 64 78 87 103 121 120 101 + 72 92 95 98 112 100 103 99 + """.split(None)] + + standard_chrominance_qtable= [int(s) for s in """ + 17 18 24 47 99 99 99 99 + 18 21 26 66 99 99 99 99 + 24 26 56 99 99 99 99 99 + 47 66 99 99 99 99 99 99 + 99 99 99 99 99 99 99 99 + 99 99 99 99 99 99 99 99 + 99 99 99 99 99 99 99 99 + 99 99 99 99 99 99 99 99 + """.split(None)] + # list of qtable lists + self.assert_image_similar(im, + self.roundtrip(im, + qtables=[standard_l_qtable, + standard_chrominance_qtable]), + 30) + # tuple of qtable lists + self.assert_image_similar(im, + self.roundtrip(im, + qtables=(standard_l_qtable, + standard_chrominance_qtable)), + 30) + # dict of qtable lists + self.assert_image_similar(im, + self.roundtrip(im, + qtables={0:standard_l_qtable, + 1:standard_chrominance_qtable}), + 30) + + if __name__ == '__main__': unittest.main() diff --git a/docs/handbook/image-file-formats.rst b/docs/handbook/image-file-formats.rst index fddc134e6..03e55f35a 100644 --- a/docs/handbook/image-file-formats.rst +++ b/docs/handbook/image-file-formats.rst @@ -147,6 +147,29 @@ The :py:meth:`~PIL.Image.Image.save` method supports the following options: If present, indicates that this image should be stored as a progressive JPEG file. +**subsampling** + If present, sets the subsampling for the encoder. + + * ``keep``: Only valid for JPEG files, will retain the original image setting. + * ``4:4:4``, ``4:2:2``, ``4:1:1``: Specific sampling values + * ``-1``: equivalent to ``keep`` + * ``0``: equivalent to ``4:4:4`` + * ``1``: equivalent to ``4:2:2`` + * ``2``: equivalent to ``4:1:1`` + +**qtables** + If present, sets the qtables for the encoder. This is listed as an + advanced option for wizards in the JPEG documentation. Use with + caution. ``qtables`` can be one of several types of values: + + * a string, naming a preset, e.g. ``keep``, ``web_low``, or ``web_high`` + * a list, tuple, or dictionary (with integer keys = + range(len(keys))) of lists of 64 integers. There must be + between 2 and 4 tables. + + .. versionadded:: 2.5.0 + + .. note:: To enable JPEG support, you need to build and install the IJG JPEG library