Skip to content

Commit d7bc528

Browse files
ssh4netlgritz
authored andcommitted
jxl: JPEG-XL improvements (AcademySoftwareFoundation#4252)
## 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 <libalias@gmail.com> Signed-off-by: Vlad (Kuzmin) Erium <libalias@gmail.com>
1 parent 1a571d9 commit d7bc528

File tree

4 files changed

+277
-48
lines changed

4 files changed

+277
-48
lines changed

src/doc/builtinplugins.rst

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1193,6 +1193,94 @@ special ``"oiio:ioproxy"`` attributes (see Sections
11931193
the `set_ioproxy()` methods.
11941194

11951195

1196+
|
1197+
1198+
.. _sec-bundledplugins-jpegxl:
1199+
1200+
JPEG XL
1201+
===============================================
1202+
1203+
JPEG XL is a new image format that is designed to be a successor to JPEG
1204+
and to provide better compression and quality. JPEG XL files use the file
1205+
extension :file:`.jxl`. The official JPEG XL format specification and other
1206+
helpful info may be found at: https://jpeg.org/jpegxl/
1207+
1208+
**Configuration settings for JPEG XL input**
1209+
1210+
When opening a JPEG XL ImageInput with a *configuration* (see
1211+
Section :ref:`sec-input with-config`), the following special configuration
1212+
attributes are supported:
1213+
1214+
.. list-table::
1215+
:widths: 30 10 65
1216+
:header-rows: 1
1217+
1218+
* - Input Configuration Attribute
1219+
- Type
1220+
- Meaning
1221+
* - ``oiio:ioproxy``
1222+
- ptr
1223+
- Pointer to a ``Filesystem::IOProxy`` that will handle the I/O, for
1224+
example by reading from memory rather than the file system.
1225+
1226+
**Configuration settings for JPEG XL output**
1227+
1228+
When opening a JPEG XL ImageOutput, the following special metadata tokens
1229+
control aspects of the writing itself:
1230+
1231+
.. list-table::
1232+
:widths: 30 10 65
1233+
:header-rows: 1
1234+
1235+
* - Output Configuration Attribute
1236+
- Type
1237+
- JPEG XL header data or explanation
1238+
* - ``oiio:dither``
1239+
- int
1240+
- If nonzero and outputting UINT8 values in the file from a source of
1241+
higher bit depth, will add a small amount of random dither to combat
1242+
the appearance of banding.
1243+
* - ``oiio:ioproxy``
1244+
- ptr
1245+
- Pointer to a ``Filesystem::IOProxy`` that will handle the I/O, for
1246+
example by writing to a memory buffer.
1247+
* - ``oiio:UnassociatedAlpha``
1248+
- int
1249+
- If nonzero, indicates that the data being passed is already in
1250+
unassociated form (non-premultiplied colors) and should stay that way
1251+
for output rather than being assumed to be associated and get automatic
1252+
un-association to store in the file.
1253+
* - ``compression``
1254+
- string
1255+
- If supplied, must be ``"jpegxl"``, but may optionally have a quality
1256+
value appended, like ``"jpegxl:90"``. Quality can be 0-100, with 100
1257+
meaning lossless.
1258+
* - ``jpegxl:distance``
1259+
- float
1260+
- Target visual distance in JND units, lower = higher quality.
1261+
0.0 = mathematically lossless. 1.0 = visually lossless.
1262+
Recommended range: 0.5 .. 3.0. Allowed range: 0.0 ... 25.0.
1263+
Mutually exclusive with ``*compression jpegxl:*```.
1264+
* - ``jpegxl:effort``
1265+
- int
1266+
- Encoder effort setting. Range: 1 .. 10.
1267+
Default: 7. Higher numbers allow more computation at the expense of time.
1268+
For lossless, generally it will produce smaller files.
1269+
For lossy, higher effort should more accurately reach the target quality.
1270+
* - ``jpegxl:speed``
1271+
- int
1272+
- Sets the encoding speed tier for the provided options. Minimum is 0
1273+
(slowest to encode, best quality/density), and maximum is 4 (fastest to
1274+
encode, at the cost of some quality/density). Default is 0.
1275+
(Note: in libjxl it named JXL_ENC_FRAME_SETTING_DECODING_SPEED. But it
1276+
is about encoding speed and compression quality, not decoding speed.)
1277+
* - ``jpegxl:photon_noise_iso``
1278+
- float
1279+
- (ISO_FILM_SPEED) Adds noise to the image emulating photographic film or
1280+
sensor noise. Higher number = grainier image, e.g. 100 gives a low
1281+
amount of noise, 3200 gives a lot of noise. Default is 0.
1282+
Encoded as metadata in the image.
1283+
11961284
|
11971285
11981286
.. _sec-bundledplugins-ffmpeg:

src/jpegxl.imageio/jxlinput.cpp

Lines changed: 42 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -57,15 +57,15 @@ class JxlInput final : public ImageInput {
5757
JxlResizableParallelRunnerPtr m_runner;
5858
std::unique_ptr<ImageSpec> m_config; // Saved copy of configuration spec
5959
std::vector<uint8_t> m_icc_profile;
60-
std::vector<float> m_pixels;
61-
// std::vector<uint8_t> m_pixels;
60+
std::unique_ptr<uint8_t[]> m_buffer;
6261

6362
void init()
6463
{
6564
ioproxy_clear();
6665
m_config.reset();
6766
m_decoder = nullptr;
6867
m_runner = nullptr;
68+
m_buffer = nullptr;
6969
}
7070

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

220220
JxlBasicInfo info;
221-
// JxlPixelFormat format = { channels, JXL_TYPE_UINT8, JXL_NATIVE_ENDIAN, 0 };
222-
JxlPixelFormat format = { m_channels, JXL_TYPE_FLOAT, JXL_NATIVE_ENDIAN,
223-
0 };
221+
JxlPixelFormat format;
222+
JxlDataType jxl_data_type;
223+
TypeDesc m_data_type;
224224

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

242+
// Get the basic information about the image
242243
if (JXL_DEC_SUCCESS
243244
!= JxlDecoderGetBasicInfo(m_decoder.get(), &info)) {
244245
errorfmt("JxlDecoderGetBasicInfo failed\n");
245246
return false;
246247
}
248+
249+
// Need to check how we can support bfloat16 if jpegxl supports it
250+
bool is_float = info.exponent_bits_per_sample > 0;
251+
252+
switch (info.bits_per_sample) {
253+
case 8:
254+
jxl_data_type = JXL_TYPE_UINT8;
255+
m_data_type = TypeDesc::UINT8;
256+
break;
257+
case 16:
258+
jxl_data_type = is_float ? JXL_TYPE_FLOAT16 : JXL_TYPE_UINT16;
259+
m_data_type = is_float ? TypeDesc::HALF : TypeDesc::UINT16;
260+
break;
261+
case 32:
262+
jxl_data_type = JXL_TYPE_FLOAT;
263+
m_data_type = TypeDesc::FLOAT;
264+
break;
265+
default: errorfmt("Unsupported bits per sample\n"); return false;
266+
}
267+
268+
format = { m_channels, jxl_data_type, JXL_NATIVE_ENDIAN, 0 };
269+
247270
format.num_channels = info.num_color_channels
248271
+ info.num_extra_channels;
249272
m_channels = info.num_color_channels + info.num_extra_channels;
@@ -284,19 +307,19 @@ JxlInput::open(const std::string& name, ImageSpec& newspec)
284307
return false;
285308
}
286309
if (buffer_size
287-
!= info.xsize * info.ysize * m_channels * sizeof(float)) {
310+
!= info.xsize * info.ysize * m_channels * info.bits_per_sample
311+
/ 8) {
288312
errorfmt("Invalid out buffer size {} {}\n", buffer_size,
289-
info.xsize * info.ysize * m_channels * sizeof(float));
313+
info.xsize * info.ysize * m_channels
314+
* info.bits_per_sample / 8);
290315
return false;
291316
}
292-
m_pixels.resize(info.xsize * info.ysize * m_channels);
293-
void* pixels_buffer = (void*)m_pixels.data();
294-
size_t pixels_buffer_size = m_pixels.size() * sizeof(float);
295-
// size_t pixels_buffer_size = m_pixels.size() * sizeof(uint8_t);
317+
318+
m_buffer.reset(new uint8_t[buffer_size]);
319+
296320
if (JXL_DEC_SUCCESS
297321
!= JxlDecoderSetImageOutBuffer(m_decoder.get(), &format,
298-
pixels_buffer,
299-
pixels_buffer_size)) {
322+
m_buffer.get(), buffer_size)) {
300323
errorfmt("JxlDecoderSetImageOutBuffer failed\n");
301324
return false;
302325
}
@@ -321,8 +344,8 @@ JxlInput::open(const std::string& name, ImageSpec& newspec)
321344
}
322345
}
323346

324-
m_spec = ImageSpec(info.xsize, info.ysize, m_channels, TypeDesc::FLOAT);
325-
// TypeDesc::UINT8);
347+
m_spec = ImageSpec(info.xsize, info.ysize, m_channels, m_data_type);
348+
326349
newspec = m_spec;
327350
return true;
328351
}
@@ -334,7 +357,7 @@ JxlInput::read_native_scanline(int subimage, int miplevel, int y, int /*z*/,
334357
void* data)
335358
{
336359
DBG std::cout << "JxlInput::read_native_scanline(, , " << y << ")\n";
337-
size_t scanline_size = m_spec.width * m_channels * sizeof(float);
360+
size_t scanline_size = m_spec.width * m_channels * m_spec.channel_bytes();
338361
// size_t scanline_size = m_spec.width * m_channels * sizeof(uint8_t);
339362

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

346-
memcpy(data, (void*)((uint8_t*)(m_pixels.data()) + y * scanline_size),
347-
scanline_size);
369+
memcpy(data, (void*)(m_buffer.get() + y * scanline_size), scanline_size);
348370

349371
return true;
350372
}
@@ -359,6 +381,8 @@ JxlInput::close()
359381
if (ioproxy_opened()) {
360382
close_file();
361383
}
384+
385+
m_buffer.reset();
362386
init(); // Reset to initial state
363387
return true;
364388
}

0 commit comments

Comments
 (0)