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".
This commit is contained in:
gofr 2020-10-17 20:20:59 +02:00
parent 7b80e69ed2
commit 9980981c2e
3 changed files with 19 additions and 18 deletions

View File

@ -446,7 +446,7 @@ class TestFileJpeg:
assert len(im.quantization) == n assert len(im.quantization) == n
reloaded = self.roundtrip(im, qtables="keep") reloaded = self.roundtrip(im, qtables="keep")
assert im.quantization == reloaded.quantization 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: with Image.open("Tests/images/hopper.jpg") as im:
qtables = im.quantization qtables = im.quantization
@ -458,7 +458,8 @@ class TestFileJpeg:
# valid bounds for baseline qtable # valid bounds for baseline qtable
bounds_qtable = [int(s) for s in ("255 1 " * 32).split(None)] 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. # values from wizard.txt in jpeg9-a src package.
standard_l_qtable = [ standard_l_qtable = [
@ -569,6 +570,12 @@ class TestFileJpeg:
assert max(im2.quantization[0]) <= 255 assert max(im2.quantization[0]) <= 255
assert max(im2.quantization[1]) <= 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") @pytest.mark.skipif(not djpeg_available(), reason="djpeg not available")
def test_load_djpeg(self): def test_load_djpeg(self):
with Image.open(TEST_FILE) as img: with Image.open(TEST_FILE) as img:

View File

@ -252,7 +252,7 @@ def DQT(self, marker):
data = array.array("B" if precision == 1 else "H", s[1:qt_length]) data = array.array("B" if precision == 1 else "H", s[1:qt_length])
if sys.byteorder == "little" and precision > 1: if sys.byteorder == "little" and precision > 1:
data.byteswap() # the values are always big-endian 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:] s = s[qt_length:]
@ -585,9 +585,10 @@ samplings = {
def convert_dict_qtables(qtables): def convert_dict_qtables(qtables):
qtables = [qtables[key] for key in range(len(qtables)) if key in qtables] warnings.warn(
for idx, table in enumerate(qtables): "convert_dict_qtables is deprecated and will be removed in a future"
qtables[idx] = [table[i] for i in zigzag_index] " release. Conversion is no longer needed.",
DeprecationWarning)
return qtables return qtables
@ -668,7 +669,8 @@ def _save(im, fp, filename):
qtables = [lines[s : s + 64] for s in range(0, len(lines), 64)] qtables = [lines[s : s + 64] for s in range(0, len(lines), 64)]
if isinstance(qtables, (tuple, list, dict)): if isinstance(qtables, (tuple, list, dict)):
if isinstance(qtables, 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): elif isinstance(qtables, tuple):
qtables = list(qtables) qtables = list(qtables)
if not (0 < len(qtables) < 5): if not (0 < len(qtables) < 5):

View File

@ -52,19 +52,11 @@ You can get the quantization tables of a JPEG with::
im.quantization 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. directly as the qtables argument when saving a JPEG.
The tables format between im.quantization and quantization in presets differ in The quantization table format in presets is a list with sublists. These formats
3 ways: are interchangeable.
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.
Libjpeg ref.: Libjpeg ref.:
https://web.archive.org/web/20120328125543/http://www.jpegcameras.com/libjpeg/libjpeg-3.html https://web.archive.org/web/20120328125543/http://www.jpegcameras.com/libjpeg/libjpeg-3.html