Skip to content

JPEG2000 irreversible option misleading #5485

@scaramallion

Description

@scaramallion

The documentation for the J2K plugin says:

irreversible
If True, use the lossy Irreversible Color Transformation followed by DWT 9-7. Defaults to False, which means to use the Reversible Color Transformation with DWT 5-3.

I think the "color transformation" here is referring to the multiple component transformation (MCT), however looking at the source code, MCT isn't being applied, so no color transform is occurring (as I understand it irreversible in openjpeg only controls which DWT is used). This is visible in the output codestream:

from io import BytesIO
import numpy as np
from PIL import Image
from struct import unpack

# https://github.com/python-pillow/Pillow/blob/master/Tests/images/xmp_test.jpg
im = Image.open("xmp_test.jpg")
assert im.mode == "RGB"
ref = np.asarray(im)
b = BytesIO()
b.name = "foo.j2k"  # workaround so no JP2
im.save(b, format="JPEG2000", irreversible=False)

# Parse J2K codestream
codestream = b.getvalue()
print(f"len: {len(codestream)}")
if codestream[0:2] == b'\xff\x4f':
    if codestream[2:4] == b'\xff\x51':
        lsiz = unpack('>H', codestream[4:6])[0]
        ssiz = codestream[42]
        if ssiz & 0x80:
            print(f"precision: {(ssiz & 0x7F) + 1}")
            print("signed: true")
        else:
            print(f"precision: {ssiz + 1}")
            print("signed: false")

        cod_offset = lsiz + 4
        if codestream[cod_offset:cod_offset + 2] == b'\xff\x52':
            # 0 for none, 1 for applied to components 0, 1, 2
            print(f"MCT: {codestream[cod_offset + 8]}")
            # 0 for DWT 9-7 (irreversible), 1 for DWT 5-3 (reversible)
            print(f"DWT: {codestream[cod_offset + 13]}")

im = Image.open(b)
assert np.array_equal(ref, np.asarray(im))

Produces:

len: 3506536
precision: 8
signed: false
MCT: 0
DWT: 1

For comparison, with Jpeg2KEncode.c modified to use MCT:

len: 2682646
precision: 8
signed: false
MCT: 1
DWT: 1

With the transform into YCbCr the compression ratio is higher, as expected, while still being reversible.

What are your OS, Python and Pillow versions?

  • OS: Ubuntu 20.04
  • Python: 3.9
  • Pillow: current master d393cfb

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions