Skip to content

Commit

Permalink
jxl: JPEG-XL improvements (AcademySoftwareFoundation#4252)
Browse files Browse the repository at this point in the history
## JpegXL Output plugin
* support for uint8/uint16/half/float
* "compression jpegxl:##" config hint sets compression type
* "jpegxl:distance"
* "jpegxl:effort"
* "jpegxl:photon_noise_iso"

## JpegXL Input plugin
* support for uint8/uint16/half/float

**Encode:**
```
oiiotools -v ^
-i DISP_16bit.tif -o DISP_16bit_tif.jxl ^
-i  DISP_8bit.tif -o DISP_8bit_tif.jxl ^
-i DISP_float.exr -o DISP_float_exr.jxl ^
-i DISP_half.exr -o DISP_half_exr.jxl
```

jxlinfo.exe -v output
```
JPEG XL image, 1440x1440, (possibly) lossless, 16-bit RGB
JPEG XL image, 1440x1440, (possibly) lossless, 8-bit RGB
JPEG XL image, 1440x1440, (possibly) lossless, 32-bit float (8 exponent bits) RGB
JPEG XL image, 1440x1440, (possibly) lossless, 16-bit float (5 exponent bits) RGB
```

**Decode**
```
oiiotool.exe -v ^
-i w:\DISP_16bit_tif.jxl -o w:\DISP_16bit_tif_jxl.tif ^
-i w:\DISP_8bit_tif.jxl -o w:\DISP_8bit_tif_jxl.tif ^
-i w:\DISP_float_exr.jxl -o w:\DISP_float_exr_jxl.exr ^
-i w:\DISP_half_exr.jxl -o w:\DISP_half_exr_jxl.exr
```

iinfo.exe output
```
DISP_16bit_tif_jxl.tif : 1440 x 1440, 3 channel, uint16 tiff
DISP_8bit_tif_jxl.tif : 1440 x 1440, 3 channel, uint8 tiff
DISP_float_exr_jxl.exr : 1440 x 1440, 3 channel, float openexr
DISP_half_exr_jxl.exr : 1440 x 1440, 3 channel, half openexr
```

TODO:
- [ ] EXIF and metadata
- [ ] Color spaces support
- [x] Encoding settings
- [ ] Decoding settings

## OIIO Tests
not yet


---------

Signed-off-by: ssh4net <[email protected]>
Signed-off-by: Vlad (Kuzmin) Erium <[email protected]>
  • Loading branch information
ssh4net authored May 5, 2024
1 parent 6ba496f commit feb28fd
Show file tree
Hide file tree
Showing 4 changed files with 277 additions and 48 deletions.
88 changes: 88 additions & 0 deletions src/doc/builtinplugins.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1193,6 +1193,94 @@ special ``"oiio:ioproxy"`` attributes (see Sections
the `set_ioproxy()` methods.


|
.. _sec-bundledplugins-jpegxl:

JPEG XL
===============================================

JPEG XL is a new image format that is designed to be a successor to JPEG
and to provide better compression and quality. JPEG XL files use the file
extension :file:`.jxl`. The official JPEG XL format specification and other
helpful info may be found at: https://jpeg.org/jpegxl/

**Configuration settings for JPEG XL input**

When opening a JPEG XL ImageInput with a *configuration* (see
Section :ref:`sec-input with-config`), the following special configuration
attributes are supported:

.. list-table::
:widths: 30 10 65
:header-rows: 1

* - Input Configuration Attribute
- Type
- Meaning
* - ``oiio:ioproxy``
- ptr
- Pointer to a ``Filesystem::IOProxy`` that will handle the I/O, for
example by reading from memory rather than the file system.

**Configuration settings for JPEG XL output**

When opening a JPEG XL ImageOutput, the following special metadata tokens
control aspects of the writing itself:

.. list-table::
:widths: 30 10 65
:header-rows: 1

* - Output Configuration Attribute
- Type
- JPEG XL header data or explanation
* - ``oiio:dither``
- int
- If nonzero and outputting UINT8 values in the file from a source of
higher bit depth, will add a small amount of random dither to combat
the appearance of banding.
* - ``oiio:ioproxy``
- ptr
- Pointer to a ``Filesystem::IOProxy`` that will handle the I/O, for
example by writing to a memory buffer.
* - ``oiio:UnassociatedAlpha``
- int
- If nonzero, indicates that the data being passed is already in
unassociated form (non-premultiplied colors) and should stay that way
for output rather than being assumed to be associated and get automatic
un-association to store in the file.
* - ``compression``
- string
- If supplied, must be ``"jpegxl"``, but may optionally have a quality
value appended, like ``"jpegxl:90"``. Quality can be 0-100, with 100
meaning lossless.
* - ``jpegxl:distance``
- float
- Target visual distance in JND units, lower = higher quality.
0.0 = mathematically lossless. 1.0 = visually lossless.
Recommended range: 0.5 .. 3.0. Allowed range: 0.0 ... 25.0.
Mutually exclusive with ``*compression jpegxl:*```.
* - ``jpegxl:effort``
- int
- Encoder effort setting. Range: 1 .. 10.
Default: 7. Higher numbers allow more computation at the expense of time.
For lossless, generally it will produce smaller files.
For lossy, higher effort should more accurately reach the target quality.
* - ``jpegxl:speed``
- int
- Sets the encoding speed tier for the provided options. Minimum is 0
(slowest to encode, best quality/density), and maximum is 4 (fastest to
encode, at the cost of some quality/density). Default is 0.
(Note: in libjxl it named JXL_ENC_FRAME_SETTING_DECODING_SPEED. But it
is about encoding speed and compression quality, not decoding speed.)
* - ``jpegxl:photon_noise_iso``
- float
- (ISO_FILM_SPEED) Adds noise to the image emulating photographic film or
sensor noise. Higher number = grainier image, e.g. 100 gives a low
amount of noise, 3200 gives a lot of noise. Default is 0.
Encoded as metadata in the image.

|
.. _sec-bundledplugins-ffmpeg:
Expand Down
60 changes: 42 additions & 18 deletions src/jpegxl.imageio/jxlinput.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,15 +57,15 @@ class JxlInput final : public ImageInput {
JxlResizableParallelRunnerPtr m_runner;
std::unique_ptr<ImageSpec> m_config; // Saved copy of configuration spec
std::vector<uint8_t> m_icc_profile;
std::vector<float> m_pixels;
// std::vector<uint8_t> m_pixels;
std::unique_ptr<uint8_t[]> m_buffer;

void init()
{
ioproxy_clear();
m_config.reset();
m_decoder = nullptr;
m_runner = nullptr;
m_buffer = nullptr;
}

void close_file() { init(); }
Expand Down Expand Up @@ -218,9 +218,9 @@ JxlInput::open(const std::string& name, ImageSpec& newspec)
}

JxlBasicInfo info;
// JxlPixelFormat format = { channels, JXL_TYPE_UINT8, JXL_NATIVE_ENDIAN, 0 };
JxlPixelFormat format = { m_channels, JXL_TYPE_FLOAT, JXL_NATIVE_ENDIAN,
0 };
JxlPixelFormat format;
JxlDataType jxl_data_type;
TypeDesc m_data_type;

for (;;) {
JxlDecoderStatus status = JxlDecoderProcessInput(m_decoder.get());
Expand All @@ -239,11 +239,34 @@ JxlInput::open(const std::string& name, ImageSpec& newspec)
} else if (status == JXL_DEC_BASIC_INFO) {
DBG std::cout << "JXL_DEC_BASIC_INFO\n";

// Get the basic information about the image
if (JXL_DEC_SUCCESS
!= JxlDecoderGetBasicInfo(m_decoder.get(), &info)) {
errorfmt("JxlDecoderGetBasicInfo failed\n");
return false;
}

// Need to check how we can support bfloat16 if jpegxl supports it
bool is_float = info.exponent_bits_per_sample > 0;

switch (info.bits_per_sample) {
case 8:
jxl_data_type = JXL_TYPE_UINT8;
m_data_type = TypeDesc::UINT8;
break;
case 16:
jxl_data_type = is_float ? JXL_TYPE_FLOAT16 : JXL_TYPE_UINT16;
m_data_type = is_float ? TypeDesc::HALF : TypeDesc::UINT16;
break;
case 32:
jxl_data_type = JXL_TYPE_FLOAT;
m_data_type = TypeDesc::FLOAT;
break;
default: errorfmt("Unsupported bits per sample\n"); return false;
}

format = { m_channels, jxl_data_type, JXL_NATIVE_ENDIAN, 0 };

format.num_channels = info.num_color_channels
+ info.num_extra_channels;
m_channels = info.num_color_channels + info.num_extra_channels;
Expand Down Expand Up @@ -284,19 +307,19 @@ JxlInput::open(const std::string& name, ImageSpec& newspec)
return false;
}
if (buffer_size
!= info.xsize * info.ysize * m_channels * sizeof(float)) {
!= info.xsize * info.ysize * m_channels * info.bits_per_sample
/ 8) {
errorfmt("Invalid out buffer size {} {}\n", buffer_size,
info.xsize * info.ysize * m_channels * sizeof(float));
info.xsize * info.ysize * m_channels
* info.bits_per_sample / 8);
return false;
}
m_pixels.resize(info.xsize * info.ysize * m_channels);
void* pixels_buffer = (void*)m_pixels.data();
size_t pixels_buffer_size = m_pixels.size() * sizeof(float);
// size_t pixels_buffer_size = m_pixels.size() * sizeof(uint8_t);

m_buffer.reset(new uint8_t[buffer_size]);

if (JXL_DEC_SUCCESS
!= JxlDecoderSetImageOutBuffer(m_decoder.get(), &format,
pixels_buffer,
pixels_buffer_size)) {
m_buffer.get(), buffer_size)) {
errorfmt("JxlDecoderSetImageOutBuffer failed\n");
return false;
}
Expand All @@ -321,8 +344,8 @@ JxlInput::open(const std::string& name, ImageSpec& newspec)
}
}

m_spec = ImageSpec(info.xsize, info.ysize, m_channels, TypeDesc::FLOAT);
// TypeDesc::UINT8);
m_spec = ImageSpec(info.xsize, info.ysize, m_channels, m_data_type);

newspec = m_spec;
return true;
}
Expand All @@ -334,7 +357,7 @@ JxlInput::read_native_scanline(int subimage, int miplevel, int y, int /*z*/,
void* data)
{
DBG std::cout << "JxlInput::read_native_scanline(, , " << y << ")\n";
size_t scanline_size = m_spec.width * m_channels * sizeof(float);
size_t scanline_size = m_spec.width * m_channels * m_spec.channel_bytes();
// size_t scanline_size = m_spec.width * m_channels * sizeof(uint8_t);

lock_guard lock(*this);
Expand All @@ -343,8 +366,7 @@ JxlInput::read_native_scanline(int subimage, int miplevel, int y, int /*z*/,
if (y < 0 || y >= m_spec.height) // out of range scanline
return false;

memcpy(data, (void*)((uint8_t*)(m_pixels.data()) + y * scanline_size),
scanline_size);
memcpy(data, (void*)(m_buffer.get() + y * scanline_size), scanline_size);

return true;
}
Expand All @@ -359,6 +381,8 @@ JxlInput::close()
if (ioproxy_opened()) {
close_file();
}

m_buffer.reset();
init(); // Reset to initial state
return true;
}
Expand Down
Loading

0 comments on commit feb28fd

Please sign in to comment.