jxl: encoding improvements

Plug-in can save in 8bit depth now,
previously only 16bit was supported.
Memory and dimension limits were adjusted.
This commit is contained in:
Daniel Novomeský 2022-02-15 19:41:42 +01:00
parent 9911c9c2ea
commit f04084e175

View File

@ -12,6 +12,7 @@
#include "jxl_p.h" #include "jxl_p.h"
#include <jxl/encode.h> #include <jxl/encode.h>
#include <jxl/thread_parallel_runner.h> #include <jxl/thread_parallel_runner.h>
#include <string.h>
QJpegXLHandler::QJpegXLHandler() QJpegXLHandler::QJpegXLHandler()
: m_parseState(ParseJpegXLNotParsed) : m_parseState(ParseJpegXLNotParsed)
@ -174,19 +175,30 @@ bool QJpegXLHandler::ensureDecoder()
return false; return false;
} }
if (m_basicinfo.xsize > 32768 || m_basicinfo.ysize > 32768) { if (m_basicinfo.xsize > 65535 || m_basicinfo.ysize > 65535) {
qWarning("JXL image (%dx%d) is too large", m_basicinfo.xsize, m_basicinfo.ysize); qWarning("JXL image (%dx%d) is too large", m_basicinfo.xsize, m_basicinfo.ysize);
m_parseState = ParseJpegXLError; m_parseState = ParseJpegXLError;
return false; return false;
} else if (sizeof(void *) <= 4) { }
if (sizeof(void *) <= 4) {
/* On 32bit systems, there is limited address space. /* On 32bit systems, there is limited address space.
* We skip imagess bigger than 8192 x 8192 pixels. * We skip imagess bigger than 8192 x 8192 pixels.
* If we don't do it, abort() in libjxl may close whole application */ * If we don't do it, abort() in libjxl may close whole application */
if ((m_basicinfo.xsize * m_basicinfo.ysize) > 67108864) { if (m_basicinfo.xsize > ((8192 * 8192) / m_basicinfo.ysize)) {
qWarning("JXL image (%dx%d) is too large for 32bit build of the plug-in", m_basicinfo.xsize, m_basicinfo.ysize); qWarning("JXL image (%dx%d) is too large for 32bit build of the plug-in", m_basicinfo.xsize, m_basicinfo.ysize);
m_parseState = ParseJpegXLError; m_parseState = ParseJpegXLError;
return false; return false;
} }
} else {
/* On 64bit systems
* We skip images bigger than 16384 x 16384 pixels.
* It is an artificial limit not to use extreme amount of memory */
if (m_basicinfo.xsize > ((16384 * 16384) / m_basicinfo.ysize)) {
qWarning("JXL image (%dx%d) is bigger than security limit 256 megapixels", m_basicinfo.xsize, m_basicinfo.ysize);
m_parseState = ParseJpegXLError;
return false;
}
} }
m_parseState = ParseJpegXLBasicInfoParsed; m_parseState = ParseJpegXLBasicInfoParsed;
@ -411,11 +423,59 @@ bool QJpegXLHandler::write(const QImage &image)
return false; return false;
} }
if ((image.width() > 32768) || (image.height() > 32768)) { if ((image.width() > 0) && (image.height() > 0)) {
qWarning("Image is too large"); if ((image.width() > 65535) || (image.height() > 65535)) {
qWarning("Image (%dx%d) is too large to save!", image.width(), image.height());
return false;
}
if (sizeof(void *) <= 4) {
if (image.width() > ((8192 * 8192) / image.height())) {
qWarning("Image (%dx%d) is too large save via 32bit build of JXL plug-in", image.width(), image.height());
return false;
}
} else {
if (image.width() > ((16384 * 16384) / image.height())) {
qWarning("Image (%dx%d) will not be saved because it has more than 256 megapixels", image.width(), image.height());
return false;
}
}
} else {
qWarning("Image has zero dimension!");
return false; return false;
} }
int save_depth = 8; // 8 or 16
// depth detection
switch (image.format()) {
case QImage::Format_BGR30:
case QImage::Format_A2BGR30_Premultiplied:
case QImage::Format_RGB30:
case QImage::Format_A2RGB30_Premultiplied:
case QImage::Format_Grayscale16:
case QImage::Format_RGBX64:
case QImage::Format_RGBA64:
case QImage::Format_RGBA64_Premultiplied:
save_depth = 16;
break;
case QImage::Format_RGB32:
case QImage::Format_ARGB32:
case QImage::Format_ARGB32_Premultiplied:
case QImage::Format_RGB888:
case QImage::Format_RGBX8888:
case QImage::Format_RGBA8888:
case QImage::Format_RGBA8888_Premultiplied:
save_depth = 8;
break;
default:
if (image.depth() > 32) {
save_depth = 16;
} else {
save_depth = 8;
}
break;
}
JxlEncoder *encoder = JxlEncoderCreate(nullptr); JxlEncoder *encoder = JxlEncoderCreate(nullptr);
if (!encoder) { if (!encoder) {
qWarning("Failed to create Jxl encoder"); qWarning("Failed to create Jxl encoder");
@ -456,16 +516,16 @@ bool QJpegXLHandler::write(const QImage &image)
bool convert_color_profile; bool convert_color_profile;
QByteArray iccprofile; QByteArray iccprofile;
if (image.colorSpace().isValid()) { if (image.colorSpace().isValid() && (m_quality < 100)) {
if (image.colorSpace().primaries() != QColorSpace::Primaries::SRgb || image.colorSpace().transferFunction() != QColorSpace::TransferFunction::SRgb) { if (image.colorSpace().primaries() != QColorSpace::Primaries::SRgb || image.colorSpace().transferFunction() != QColorSpace::TransferFunction::SRgb) {
convert_color_profile = true; convert_color_profile = true;
} else { } else {
convert_color_profile = false; convert_color_profile = false;
} }
} else { // no profile or Qt-unsupported ICC profile } else { // lossless or no profile or Qt-unsupported ICC profile
convert_color_profile = false; convert_color_profile = false;
iccprofile = image.colorSpace().iccProfile(); iccprofile = image.colorSpace().iccProfile();
if (iccprofile.size() > 0) { if (iccprofile.size() > 0 || m_quality == 100) {
output_info.uses_original_profile = 1; output_info.uses_original_profile = 1;
} }
} }
@ -474,19 +534,45 @@ bool QJpegXLHandler::write(const QImage &image)
QImage::Format tmpformat; QImage::Format tmpformat;
JxlEncoderStatus status; JxlEncoderStatus status;
pixel_format.data_type = JXL_TYPE_UINT16;
pixel_format.endianness = JXL_NATIVE_ENDIAN; pixel_format.endianness = JXL_NATIVE_ENDIAN;
pixel_format.align = 0; pixel_format.align = 0;
if (image.hasAlphaChannel()) { output_info.intensity_target = 255.0f;
tmpformat = QImage::Format_RGBA64; output_info.orientation = JXL_ORIENT_IDENTITY;
pixel_format.num_channels = 4; output_info.num_color_channels = 3;
output_info.alpha_bits = 16; output_info.animation.tps_numerator = 10;
output_info.num_extra_channels = 1; output_info.animation.tps_denominator = 1;
} else {
tmpformat = QImage::Format_RGBX64; if (save_depth > 8) { // 16bit depth
pixel_format.num_channels = 3; pixel_format.data_type = JXL_TYPE_UINT16;
output_info.alpha_bits = 0;
output_info.bits_per_sample = 16;
if (image.hasAlphaChannel()) {
tmpformat = QImage::Format_RGBA64;
pixel_format.num_channels = 4;
output_info.alpha_bits = 16;
output_info.num_extra_channels = 1;
} else {
tmpformat = QImage::Format_RGBX64;
pixel_format.num_channels = 3;
output_info.alpha_bits = 0;
}
} else { // 8bit depth
pixel_format.data_type = JXL_TYPE_UINT8;
output_info.bits_per_sample = 8;
if (image.hasAlphaChannel()) {
tmpformat = QImage::Format_RGBA8888;
pixel_format.num_channels = 4;
output_info.alpha_bits = 8;
output_info.num_extra_channels = 1;
} else {
tmpformat = QImage::Format_RGB888;
pixel_format.num_channels = 3;
output_info.alpha_bits = 0;
}
} }
const QImage tmpimage = const QImage tmpimage =
@ -494,7 +580,7 @@ bool QJpegXLHandler::write(const QImage &image)
const size_t xsize = tmpimage.width(); const size_t xsize = tmpimage.width();
const size_t ysize = tmpimage.height(); const size_t ysize = tmpimage.height();
const size_t buffer_size = 2 * pixel_format.num_channels * xsize * ysize; const size_t buffer_size = (save_depth > 8) ? (2 * pixel_format.num_channels * xsize * ysize) : (pixel_format.num_channels * xsize * ysize);
if (xsize == 0 || ysize == 0 || tmpimage.isNull()) { if (xsize == 0 || ysize == 0 || tmpimage.isNull()) {
qWarning("Unable to allocate memory for output image"); qWarning("Unable to allocate memory for output image");
@ -507,12 +593,6 @@ bool QJpegXLHandler::write(const QImage &image)
output_info.xsize = tmpimage.width(); output_info.xsize = tmpimage.width();
output_info.ysize = tmpimage.height(); output_info.ysize = tmpimage.height();
output_info.bits_per_sample = 16;
output_info.intensity_target = 255.0f;
output_info.orientation = JXL_ORIENT_IDENTITY;
output_info.num_color_channels = 3;
output_info.animation.tps_numerator = 10;
output_info.animation.tps_denominator = 1;
status = JxlEncoderSetBasicInfo(encoder, &output_info); status = JxlEncoderSetBasicInfo(encoder, &output_info);
if (status != JXL_ENC_SUCCESS) { if (status != JXL_ENC_SUCCESS) {
@ -546,39 +626,60 @@ bool QJpegXLHandler::write(const QImage &image)
} }
} }
if (image.hasAlphaChannel()) { if (image.hasAlphaChannel() || ((save_depth == 8) && (xsize % 4 == 0))) {
status = JxlEncoderAddImageFrame(encoder_options, &pixel_format, (void *)tmpimage.constBits(), buffer_size); status = JxlEncoderAddImageFrame(encoder_options, &pixel_format, (void *)tmpimage.constBits(), buffer_size);
} else { } else {
uint16_t *tmp_buffer = new (std::nothrow) uint16_t[3 * xsize * ysize]; if (save_depth > 8) { // 16bit depth without alpha channel
if (!tmp_buffer) { uint16_t *tmp_buffer = new (std::nothrow) uint16_t[3 * xsize * ysize];
qWarning("Memory allocation error"); if (!tmp_buffer) {
if (runner) { qWarning("Memory allocation error");
JxlThreadParallelRunnerDestroy(runner); if (runner) {
JxlThreadParallelRunnerDestroy(runner);
}
JxlEncoderDestroy(encoder);
return false;
} }
JxlEncoderDestroy(encoder);
return false;
}
uint16_t *dest_pixels = tmp_buffer; uint16_t *dest_pixels = tmp_buffer;
for (int y = 0; y < tmpimage.height(); y++) { for (int y = 0; y < tmpimage.height(); y++) {
const uint16_t *src_pixels = reinterpret_cast<const uint16_t *>(tmpimage.constScanLine(y)); const uint16_t *src_pixels = reinterpret_cast<const uint16_t *>(tmpimage.constScanLine(y));
for (int x = 0; x < tmpimage.width(); x++) { for (int x = 0; x < tmpimage.width(); x++) {
// R // R
*dest_pixels = *src_pixels; *dest_pixels = *src_pixels;
dest_pixels++; dest_pixels++;
src_pixels++; src_pixels++;
// G // G
*dest_pixels = *src_pixels; *dest_pixels = *src_pixels;
dest_pixels++; dest_pixels++;
src_pixels++; src_pixels++;
// B // B
*dest_pixels = *src_pixels; *dest_pixels = *src_pixels;
dest_pixels++; dest_pixels++;
src_pixels += 2; // skipalpha src_pixels += 2; // skipalpha
}
} }
status = JxlEncoderAddImageFrame(encoder_options, &pixel_format, (void *)tmp_buffer, buffer_size);
delete[] tmp_buffer;
} else { // 8bit depth without alpha channel
uchar *tmp_buffer8 = new (std::nothrow) uchar[3 * xsize * ysize];
if (!tmp_buffer8) {
qWarning("Memory allocation error");
if (runner) {
JxlThreadParallelRunnerDestroy(runner);
}
JxlEncoderDestroy(encoder);
return false;
}
uchar *dest_pixels8 = tmp_buffer8;
const size_t rowbytes = 3 * xsize;
for (int y = 0; y < tmpimage.height(); y++) {
memcpy(dest_pixels8, tmpimage.constScanLine(y), rowbytes);
dest_pixels8 += rowbytes;
}
status = JxlEncoderAddImageFrame(encoder_options, &pixel_format, (void *)tmp_buffer8, buffer_size);
delete[] tmp_buffer8;
} }
status = JxlEncoderAddImageFrame(encoder_options, &pixel_format, (void *)tmp_buffer, buffer_size);
delete[] tmp_buffer;
} }
if (status == JXL_ENC_ERROR) { if (status == JXL_ENC_ERROR) {