From 70a50907c293f68ee9787b7663d78cb92291aa1c Mon Sep 17 00:00:00 2001 From: Chris Sinchok Date: Wed, 28 May 2014 17:21:58 -0500 Subject: [PATCH 1/4] This patch allows a JPEG image to be saved with a specific qtables value (in dictionary format). Previously, this would throw a TypeError when checking if the qtables value was actually a preset. By adding an isStringType check, we can avoid this error. --- PIL/JpegImagePlugin.py | 2 +- Tests/test_file_jpeg.py | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/PIL/JpegImagePlugin.py b/PIL/JpegImagePlugin.py index 7b40d5d4f..fbf4248d9 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 4871c3fbf..c9ee4eec2 100644 --- a/Tests/test_file_jpeg.py +++ b/Tests/test_file_jpeg.py @@ -230,6 +230,13 @@ def test_quality_keep(): assert_no_exception(lambda: im.save(f, quality='keep')) +def test_qtables(): + im = Image.open("Images/lena.jpg") + qtables = im.quantization + f = tempfile('temp.jpg') + assert_no_exception(lambda: im.save(f, qtables=qtables, subsampling=0)) + + def test_junk_jpeg_header(): # https://github.com/python-imaging/Pillow/issues/630 filename = "Tests/images/junk_jpeg_header.jpg" From fcd4c662bfb21a320406c8ea0a5af365113a0597 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Fri, 20 Jun 2014 00:40:18 -0700 Subject: [PATCH 2/4] Fixed JPEG qtables test --- Tests/test_file_jpeg.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/test_file_jpeg.py b/Tests/test_file_jpeg.py index 74ee8a1c3..d584eef1c 100644 --- a/Tests/test_file_jpeg.py +++ b/Tests/test_file_jpeg.py @@ -224,7 +224,7 @@ class TestFileJpeg(PillowTestCase): filename = "Tests/images/junk_jpeg_header.jpg" Image.open(filename) - def test_qtables(): + def test_qtables(self): im = Image.open("Tests/images/lena.jpg") qtables = im.quantization reloaded = self.roundtrip(im, qtables=qtables, subsampling=0) From dfe7ff515f648204a8632738f605488b825c72da Mon Sep 17 00:00:00 2001 From: wiredfool Date: Fri, 20 Jun 2014 01:09:59 -0700 Subject: [PATCH 3/4] Additional jpeg qtables tests --- Tests/test_file_jpeg.py | 44 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/Tests/test_file_jpeg.py b/Tests/test_file_jpeg.py index d584eef1c..dae1d0019 100644 --- a/Tests/test_file_jpeg.py +++ b/Tests/test_file_jpeg.py @@ -229,8 +229,52 @@ class TestFileJpeg(PillowTestCase): 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() From 06ad0b092abf27482aea467ed8ae0c76d0e2e880 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Fri, 20 Jun 2014 01:39:42 -0700 Subject: [PATCH 4/4] Added docs for JPEG qtables and subsampling --- docs/handbook/image-file-formats.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) 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