libjxl / libjxl

JPEG XL image format reference implementation
BSD 3-Clause "New" or "Revised" License
2.37k stars 235 forks source link

Failed to losslessly recompress and reconstruct some JPEG files #3131

Open URenko opened 6 months ago

URenko commented 6 months ago

Describe the bug Failed to losslessly recompress and reconstruct some JPEG files.

To Reproduce JPEG file sample: 181.zip

Afer v0.9.0

$ cjxl --lossless_jpeg=1 181.jpg 181.jxl
JPEG XL encoder v0.9.1 b8ceae3 [AVX2,SSE4,SSSE3,SSE2]
Encoding [JPEG, lossless transcode, effort: 7]
Compressed to 58662 bytes including container

$ djxl 181.jxl 181_output.jpg
JPEG XL decoder v0.9.1 b8ceae3 [AVX2,SSE4,SSSE3,SSE2]
Failed to decode image
Warning: could not decode losslessly to JPEG. Retrying with --pixels_to_jpeg...
Failed to decode image
DecompressJxlToPackedPixelFile failed

Before v0.9.0

$ cjxl --lossless_jpeg=1 181.jpg 181.jxl
JPEG XL encoder v0.8.2 954b460 [AVX2,SSE4,SSSE3,Unknown]
Read JPEG image with 88508 bytes.
Encoding [Container | JPEG, lossless transcode, effort: 7 | JPEG reconstruction data],
JxlEncoderAddJPEGFrame() failed.
EncodeImageJXL() failed.

The behavior after v0.9.0 is particularly dangerous, as one might think that files successfully processed by cjxl can always be reconstructed by djxl, thereby deleting the original JPEG file and losing it forever.

Environment

Traneptora commented 6 months ago

The problem in this case is that the ICC profile is invalid. lcms2 rejects it with:

Too many tags (4451)

libpng also refuses to write the profile to a PNG file:

convert: profile 'icc': C48h: length does not match profile

or with djpegli:

libpng warning: profile '1': C48h: length does not match profile

and discards it.

JPEG XL (reversibly) mangles the embedded ICC file in a way that makes it more compressible, but it appears something is getting very confused along the way by the invalid ICC profile. It's not clear what it should do, but what it definitely shouldn't do is report a success when the JXL file produced is not valid. (It doesn't decode with either libjxl, jxl-oxide, or jxlatte, for context.)