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 ff469d15c31..56b6c793cac 100644 --- a/Tests/test_file_jpeg.py +++ b/Tests/test_file_jpeg.py @@ -446,7 +446,7 @@ def _n_qtables_helper(n, test_file): 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 @@ def _n_qtables_helper(n, test_file): # 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 @@ def test_save_low_quality_baseline_qtables(self): 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 29bc61aa8b3..3ff22b0f47e 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 @@ def _getmp(self): 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 validate_qtables(qtables): 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 79d10ebb2c6..e5a5d178a16 100644 --- a/src/PIL/JpegPresets.py +++ b/src/PIL/JpegPresets.py @@ -52,19 +52,11 @@ 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 3ff22b0f47e..82b4954d108 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 validate_qtables(qtables): 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 56b6c793cac..e04933a161e 100644 --- a/Tests/test_file_jpeg.py +++ b/Tests/test_file_jpeg.py @@ -459,7 +459,7 @@ def _n_qtables_helper(n, test_file): # 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 82b4954d108..a14c0228347 100644 --- a/src/PIL/JpegImagePlugin.py +++ b/src/PIL/JpegImagePlugin.py @@ -586,8 +586,8 @@ def _getmp(self): 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 ef88afa237d..71c6dec7fa9 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 71c6dec7fa9..262ba79e0cc 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 fed8c1ecaff..0929d75b209 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 ===========