Skip to content
Merged

Jpegxl #4252

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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