Skip to content

Copying a JPEG quantization table as a list breaks the value order #4962

@gofr

Description

@gofr

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:

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions