From 9980981c2e2063df3e86a14ebd4c109a2bf92122 Mon Sep 17 00:00:00 2001 From: gofr <32750931+gofr@users.noreply.github.com> Date: Sat, 17 Oct 2020 20:20:59 +0200 Subject: [PATCH 1/6] De-zigzag JPEG's DQT when loading; deprecate convert_dict_qtables Re-order the JPEG quantization tables to normal order when loading. This wastes a few CPU cycles if you don't need them. But it has the advantage of hiding the zigzag order JPEG implementation detail of these tables completely from Pillow users. This difference has led to cases where: * arrays in zigzag order were taken from a dict and passed directly as a qtables parameter, causing them to be "zigzagged" again by libjpeg. * dicts with lists in normal order being passed to JpegImagePlugin.convert_dict_qtables, causing them to be unnecessarily "de-zigzagged". --- Tests/test_file_jpeg.py | 11 +++++++++-- src/PIL/JpegImagePlugin.py | 12 +++++++----- src/PIL/JpegPresets.py | 14 +++----------- 3 files changed, 19 insertions(+), 18 deletions(-) diff --git a/Tests/test_file_jpeg.py b/Tests/test_file_jpeg.py index ff469d15c..56b6c793c 100644 --- a/Tests/test_file_jpeg.py +++ b/Tests/test_file_jpeg.py @@ -446,7 +446,7 @@ class TestFileJpeg: assert len(im.quantization) == n reloaded = self.roundtrip(im, qtables="keep") assert im.quantization == reloaded.quantization - assert reloaded.quantization[0].typecode == "B" + assert max(reloaded.quantization[0]) <= 255 with Image.open("Tests/images/hopper.jpg") as im: qtables = im.quantization @@ -458,7 +458,8 @@ class TestFileJpeg: # valid bounds for baseline qtable bounds_qtable = [int(s) for s in ("255 1 " * 32).split(None)] - self.roundtrip(im, qtables=[bounds_qtable]) + im2 = self.roundtrip(im, qtables=[bounds_qtable]) + assert im2.quantization[0] == bounds_qtable # values from wizard.txt in jpeg9-a src package. standard_l_qtable = [ @@ -569,6 +570,12 @@ class TestFileJpeg: assert max(im2.quantization[0]) <= 255 assert max(im2.quantization[1]) <= 255 + def test_convert_dict_qtables_deprecation(self): + with pytest.warns(DeprecationWarning): + qtable = {0: [1, 2, 3, 4]} + qtable2 = JpegImagePlugin.convert_dict_qtables(qtable) + assert qtable == qtable2 + @pytest.mark.skipif(not djpeg_available(), reason="djpeg not available") def test_load_djpeg(self): with Image.open(TEST_FILE) as img: diff --git a/src/PIL/JpegImagePlugin.py b/src/PIL/JpegImagePlugin.py index 29bc61aa8..3ff22b0f4 100644 --- a/src/PIL/JpegImagePlugin.py +++ b/src/PIL/JpegImagePlugin.py @@ -252,7 +252,7 @@ def DQT(self, marker): data = array.array("B" if precision == 1 else "H", s[1:qt_length]) if sys.byteorder == "little" and precision > 1: data.byteswap() # the values are always big-endian - self.quantization[v & 15] = data + self.quantization[v & 15] = [data[i] for i in zigzag_index] s = s[qt_length:] @@ -585,9 +585,10 @@ samplings = { def convert_dict_qtables(qtables): - qtables = [qtables[key] for key in range(len(qtables)) if key in qtables] - for idx, table in enumerate(qtables): - qtables[idx] = [table[i] for i in zigzag_index] + warnings.warn( + "convert_dict_qtables is deprecated and will be removed in a future" + " release. Conversion is no longer needed.", + DeprecationWarning) return qtables @@ -668,7 +669,8 @@ def _save(im, fp, filename): qtables = [lines[s : s + 64] for s in range(0, len(lines), 64)] if isinstance(qtables, (tuple, list, dict)): if isinstance(qtables, dict): - qtables = convert_dict_qtables(qtables) + qtables = [ + qtables[key] for key in range(len(qtables)) if key in qtables] elif isinstance(qtables, tuple): qtables = list(qtables) if not (0 < len(qtables) < 5): diff --git a/src/PIL/JpegPresets.py b/src/PIL/JpegPresets.py index 79d10ebb2..e5a5d178a 100644 --- a/src/PIL/JpegPresets.py +++ b/src/PIL/JpegPresets.py @@ -52,19 +52,11 @@ You can get the quantization tables of a JPEG with:: im.quantization -This will return a dict with a number of arrays. You can pass this dict +This will return a dict with a number of lists. You can pass this dict directly as the qtables argument when saving a JPEG. -The tables format between im.quantization and quantization in presets differ in -3 ways: - -1. The base container of the preset is a list with sublists instead of dict. - dict[0] -> list[0], dict[1] -> list[1], ... -2. Each table in a preset is a list instead of an array. -3. The zigzag order is remove in the preset (needed by libjpeg >= 6a). - -You can convert the dict format to the preset format with the -:func:`.JpegImagePlugin.convert_dict_qtables()` function. +The quantization table format in presets is a list with sublists. These formats +are interchangeable. Libjpeg ref.: https://web.archive.org/web/20120328125543/http://www.jpegcameras.com/libjpeg/libjpeg-3.html From 4dc195333e537515817b40c65b3ad643acda7d1e Mon Sep 17 00:00:00 2001 From: gofr <32750931+gofr@users.noreply.github.com> Date: Sat, 17 Oct 2020 20:45:54 +0200 Subject: [PATCH 2/6] fixup! De-zigzag JPEG's DQT when loading; deprecate convert_dict_qtables --- src/PIL/JpegImagePlugin.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/PIL/JpegImagePlugin.py b/src/PIL/JpegImagePlugin.py index 3ff22b0f4..82b4954d1 100644 --- a/src/PIL/JpegImagePlugin.py +++ b/src/PIL/JpegImagePlugin.py @@ -588,7 +588,8 @@ def convert_dict_qtables(qtables): warnings.warn( "convert_dict_qtables is deprecated and will be removed in a future" " release. Conversion is no longer needed.", - DeprecationWarning) + DeprecationWarning, + ) return qtables @@ -670,7 +671,8 @@ def _save(im, fp, filename): if isinstance(qtables, (tuple, list, dict)): if isinstance(qtables, dict): qtables = [ - qtables[key] for key in range(len(qtables)) if key in qtables] + qtables[key] for key in range(len(qtables)) if key in qtables + ] elif isinstance(qtables, tuple): qtables = list(qtables) if not (0 < len(qtables) < 5): From a1d8d638bfe34bb6124c6a1940e5845e946dc350 Mon Sep 17 00:00:00 2001 From: Andrew Murray <3112309+radarhere@users.noreply.github.com> Date: Tue, 29 Jun 2021 19:41:00 +1000 Subject: [PATCH 3/6] Checked complete length of value --- 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 56b6c793c..e04933a16 100644 --- a/Tests/test_file_jpeg.py +++ b/Tests/test_file_jpeg.py @@ -459,7 +459,7 @@ class TestFileJpeg: # valid bounds for baseline qtable bounds_qtable = [int(s) for s in ("255 1 " * 32).split(None)] im2 = self.roundtrip(im, qtables=[bounds_qtable]) - assert im2.quantization[0] == bounds_qtable + assert im2.quantization == {0: bounds_qtable} # values from wizard.txt in jpeg9-a src package. standard_l_qtable = [ From 70c7514a4abcf54f694be2a3f12577dba7daa5c2 Mon Sep 17 00:00:00 2001 From: Andrew Murray <3112309+radarhere@users.noreply.github.com> Date: Wed, 30 Jun 2021 19:29:52 +1000 Subject: [PATCH 4/6] Added specific removal details --- src/PIL/JpegImagePlugin.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PIL/JpegImagePlugin.py b/src/PIL/JpegImagePlugin.py index 82b4954d1..a14c02283 100644 --- a/src/PIL/JpegImagePlugin.py +++ b/src/PIL/JpegImagePlugin.py @@ -586,8 +586,8 @@ samplings = { def convert_dict_qtables(qtables): warnings.warn( - "convert_dict_qtables is deprecated and will be removed in a future" - " release. Conversion is no longer needed.", + "convert_dict_qtables is deprecated and will be removed in Pillow 10" + "(2023-01-02). Conversion is no longer needed.", DeprecationWarning, ) return qtables From afb6ad27d7b26017540183cacc4e1b9b046d0ac7 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 30 Jun 2021 20:57:21 +1000 Subject: [PATCH 5/6] Sorted deprecations by removal date --- docs/deprecations.rst | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/docs/deprecations.rst b/docs/deprecations.rst index ef88afa23..71c6dec7f 100644 --- a/docs/deprecations.rst +++ b/docs/deprecations.rst @@ -25,26 +25,6 @@ vulnerability introduced in FreeType 2.6 (:cve:`CVE-2020-15999`). .. _2.10.4: https://sourceforge.net/projects/freetype/files/freetype2/2.10.4/ -Tk/Tcl 8.4 -~~~~~~~~~~ - -.. deprecated:: 8.2.0 - -Support for Tk/Tcl 8.4 is deprecated and will be removed in Pillow 10.0.0 (2023-01-02), -when Tk/Tcl 8.5 will be the minimum supported. - -Categories -~~~~~~~~~~ - -.. deprecated:: 8.2.0 - -``im.category`` is deprecated and will be removed in Pillow 10.0.0 (2023-01-02), -along with the related ``Image.NORMAL``, ``Image.SEQUENCE`` and -``Image.CONTAINER`` attributes. - -To determine if an image has multiple frames or not, -``getattr(im, "is_animated", False)`` can be used instead. - Image.show command parameter ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -82,6 +62,26 @@ Use ``__version__`` instead. It was initially removed in Pillow 7.0.0, but brought back in 7.1.0 to give projects more time to upgrade. +Tk/Tcl 8.4 +~~~~~~~~~~ + +.. deprecated:: 8.2.0 + +Support for Tk/Tcl 8.4 is deprecated and will be removed in Pillow 10.0.0 (2023-01-02), +when Tk/Tcl 8.5 will be the minimum supported. + +Categories +~~~~~~~~~~ + +.. deprecated:: 8.2.0 + +``im.category`` is deprecated and will be removed in Pillow 10.0.0 (2023-01-02), +along with the related ``Image.NORMAL``, ``Image.SEQUENCE`` and +``Image.CONTAINER`` attributes. + +To determine if an image has multiple frames or not, +``getattr(im, "is_animated", False)`` can be used instead. + Removed features ---------------- From dfeb49c107e3ee546ac4328e32f9cba2a830e1c4 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 30 Jun 2021 21:47:41 +1000 Subject: [PATCH 6/6] Documented deprecation --- docs/deprecations.rst | 10 ++++++++++ docs/releasenotes/8.3.0.rst | 9 ++++++--- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/docs/deprecations.rst b/docs/deprecations.rst index 71c6dec7f..262ba79e0 100644 --- a/docs/deprecations.rst +++ b/docs/deprecations.rst @@ -82,6 +82,16 @@ along with the related ``Image.NORMAL``, ``Image.SEQUENCE`` and To determine if an image has multiple frames or not, ``getattr(im, "is_animated", False)`` can be used instead. +JpegImagePlugin.convert_dict_qtables +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. deprecated:: 8.3.0 + +JPEG ``quantization`` is now automatically converted, but still returned as a +dictionary. The :py:attr:`~PIL.JpegImagePlugin.convert_dict_qtables` method no longer +performs any operations on the data given to it, has been deprecated and will be +removed in Pillow 10.0.0 (2023-01-02). + Removed features ---------------- diff --git a/docs/releasenotes/8.3.0.rst b/docs/releasenotes/8.3.0.rst index fed8c1eca..0929d75b2 100644 --- a/docs/releasenotes/8.3.0.rst +++ b/docs/releasenotes/8.3.0.rst @@ -4,10 +4,13 @@ Deprecations ============ -TODO -^^^^ +JpegImagePlugin.convert_dict_qtables +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -TODO +JPEG ``quantization`` is now automatically converted, but still returned as a +dictionary. The :py:attr:`~PIL.JpegImagePlugin.convert_dict_qtables` method no longer +performs any operations on the data given to it, has been deprecated and will be +removed in Pillow 10.0.0 (2023-01-02). API Changes ===========