cgohlke / imagecodecs

Image transformation, compression, and decompression codecs
https://pypi.org/project/imagecodecs
BSD 3-Clause "New" or "Revised" License
111 stars 21 forks source link

JxlDecoderProcessInput returned JXL_DEC_ERROR on certain images #54

Closed cheng-chi closed 1 year ago

cheng-chi commented 1 year ago

Hi Christoph, Thank you so much for maintaining this package! It's super handy for my current research project. I'm currently countering a bug with JpegXL on Ubuntu 20.04 with this image (attached as png). Minimal reproducible example:

import imagecodecs
img = imagecodecs.png_decode(open('test.png', 'rb').read())
imagecodecs.jpegxl_decode(imagecodecs.jpegxl_encode(img, level=4, lossless=False))

test

cgohlke commented 1 year ago

Looks like the jxl library encoder produces an invalid JPEG XL stream with level > 1 in lossy mode.

The level parameter maps to the "decoding speed tier", which is probably not what you want. I better change level to something like the "quality" parameter of the cjxl tool, although that is not part of the public jxl API...

For now, consider the distance or effort parameters:

/**
 * Sets the distance level for lossy compression: target max butteraugli
 * distance, lower = higher quality. Range: 0 .. 15.
 * 0.0 = mathematically lossless (however, use JxlEncoderSetFrameLossless
 * instead to use true lossless, as setting distance to 0 alone is not the only
 * requirement). 1.0 = visually lossless. Recommended range: 0.5 .. 3.0. Default
 * value: 1.0.
  /** Sets encoder effort/speed level without affecting decoding speed. Valid
   * values are, from faster to slower speed: 1:lightning 2:thunder 3:falcon
   * 4:cheetah 5:hare 6:wombat 7:squirrel 8:kitten 9:tortoise.
cgohlke commented 1 year ago

Going to use the formula from the GIMP plugin to map level/quality to distance in the next version of imagecodecs: https://github.com/libjxl/libjxl/blob/f95da131cf7c7ccd4da256356fde2fec1fa23bb5/plugins/gimp/file-jxl-save.cc#L541-L555

Unfortunately that formula is going to change in the next version of libjxl...

cheng-chi commented 1 year ago

Thanks for the fast response! I can confirm that the following code works. It seems like libjxl is still not stable enough for general usage. I will use Jpeg2k for now!

import imagecodecs
img = imagecodecs.png_decode(open('test.png', 'rb').read())
imagecodecs.jpegxl_decode(imagecodecs.jpegxl_encode(img, effort=3, distance=1.0, lossless=False, decodingspeed=1))
cheng-chi commented 1 year ago

Just to confirm, is "jxl library encoder produces an invalid JPEG XL stream with level > 1 in lossy mode" a bug or expected behavior? Is it documented in libjxl? Or should I submit an issue to libjxl as well?

cgohlke commented 1 year ago

Looks like a bug. To reproduce the issue, effort has to be below 5 too. I don't see this documented and would expect an error during writing if this combination of parameters is not valid.

In order to report the issue to libjxl, this needs to be reproduced outside of imagecodecs with the current libjxl development code. Unfortunately the decoding speed parameter cannot be set with the cjxl tool. Let's take that as a hint not to change the default decoding speed...

cgohlke commented 1 year ago

In v2022.12.22, the level parameter maps to distance in a way that it resembles the libjpeg quality parameter.

cheng-chi commented 1 year ago

Thanks for making the change!