-
-
Notifications
You must be signed in to change notification settings - Fork 2.4k
Description
What did you do?
from PIL import Image
im = Image.open('any_existing.jpg')
im.save('output.jpg', qtables=[im.quantization[0]])
im2 = Image.open('output.jpg')
assert im2.quantization[0] == im.quantization[0]What did you expect to happen?
The assert passes.
What actually happened?
It fails.
If you replace the second line by one of the following, it does pass:
im.save('output.jpg', qtables=im.quantization)
im.save('output.jpg', qtables={0: im.quantization[0]})The reason is that JPEGs store their quantization table in zigzag order. When reading JPEGs, Pillow loads the table into the quantization property unchanged which means the values are in zigzag order.
But when writing, Pillow passes the qtables value to libjpeg's jpeg_add_quant_table, which takes values in normal order (since libjpeg version 6a).
Pillow has the built-in convert_dict_qtables to convert this dict of zigzagged arrays stored in quantization back into normal-order arrays. It uses that internally when saving using a dict value, so if you use qtables='keep', or qtables=im.quantization things work as expected.
But if you write the quantization data from an existing JPEG as a list, jpeg_add_quant_table ends up re-zigzagging the already zigzagged data.
I think it would be less confusing if Pillow already de-zigzagged the values when it reads them in the DQT function.
Note also that the existing test_qtables test is already affected by this:
Pillow/Tests/test_file_jpeg.py
Line 460 in 804c76f
| self.roundtrip(im, qtables=[bounds_qtable]) |
But the only thing that line really does is check that save doesn't raise any exceptions. It does not check that the input and output images are the same.