15#include <jxl/encode.h>
16#include <jxl/thread_parallel_runner.h>
18#if JPEGXL_NUMERIC_VERSION >= JPEGXL_COMPUTE_NUMERIC_VERSION(0, 9, 0)
25#if (QT_VERSION >= QT_VERSION_CHECK(6, 5, 7) && QT_VERSION < QT_VERSION_CHECK(6, 6, 0)) || (QT_VERSION >= QT_VERSION_CHECK(6, 7, 3))
26#ifndef JXL_QT_AUTOTRANSFORM
27#define JXL_QT_AUTOTRANSFORM
31#if JPEGXL_NUMERIC_VERSION < JPEGXL_COMPUTE_NUMERIC_VERSION(0, 9, 0)
32#ifndef JXL_HDR_PRESERVATION_DISABLED
35#define JXL_HDR_PRESERVATION_DISABLED
39#ifndef JXL_DECODE_BOXES_DISABLED
45#define FEATURE_LEVEL_5_WIDTH 262144
46#define FEATURE_LEVEL_5_HEIGHT 262144
47#define FEATURE_LEVEL_5_PIXELS 268435456
49#if QT_POINTER_SIZE < 8
50#define MAX_IMAGE_WIDTH 32767
51#define MAX_IMAGE_HEIGHT 32767
52#define MAX_IMAGE_PIXELS FEATURE_LEVEL_5_PIXELS
54#define MAX_IMAGE_WIDTH FEATURE_LEVEL_5_WIDTH
55#define MAX_IMAGE_HEIGHT FEATURE_LEVEL_5_HEIGHT
56#define MAX_IMAGE_PIXELS FEATURE_LEVEL_5_PIXELS
59QJpegXLHandler::QJpegXLHandler()
60 : m_parseState(ParseJpegXLNotParsed)
62 , m_currentimage_index(0)
63 , m_previousimage_index(-1)
67 , m_next_image_delay(0)
68 , m_input_image_format(
QImage::Format_Invalid)
69 , m_target_image_format(
QImage::Format_Invalid)
74QJpegXLHandler::~QJpegXLHandler()
77 JxlThreadParallelRunnerDestroy(m_runner);
80 JxlDecoderDestroy(m_decoder);
84bool QJpegXLHandler::canRead()
const
86 if (m_parseState == ParseJpegXLNotParsed && !canRead(device())) {
90 if (m_parseState != ParseJpegXLError) {
93 if (m_parseState == ParseJpegXLFinished) {
102bool QJpegXLHandler::canRead(
QIODevice *device)
108 if (header.
size() < 12) {
112 JxlSignature signature = JxlSignatureCheck(
reinterpret_cast<const uint8_t *
>(header.
constData()), header.
size());
113 if (signature == JXL_SIG_CODESTREAM || signature == JXL_SIG_CONTAINER) {
119bool QJpegXLHandler::ensureParsed()
const
121 if (m_parseState == ParseJpegXLSuccess || m_parseState == ParseJpegXLBasicInfoParsed || m_parseState == ParseJpegXLFinished) {
124 if (m_parseState == ParseJpegXLError) {
128 QJpegXLHandler *that =
const_cast<QJpegXLHandler *
>(
this);
130 return that->ensureDecoder();
133bool QJpegXLHandler::ensureALLCounted()
const
135 if (!ensureParsed()) {
139 if (m_parseState == ParseJpegXLSuccess || m_parseState == ParseJpegXLFinished) {
143 QJpegXLHandler *that =
const_cast<QJpegXLHandler *
>(
this);
145 return that->countALLFrames();
148bool QJpegXLHandler::ensureDecoder()
154 m_rawData = device()->
readAll();
156 if (m_rawData.isEmpty()) {
160 JxlSignature signature = JxlSignatureCheck(
reinterpret_cast<const uint8_t *
>(m_rawData.constData()), m_rawData.size());
161 if (signature != JXL_SIG_CODESTREAM && signature != JXL_SIG_CONTAINER) {
162 m_parseState = ParseJpegXLError;
166 m_decoder = JxlDecoderCreate(
nullptr);
168 qWarning(
"ERROR: JxlDecoderCreate failed");
169 m_parseState = ParseJpegXLError;
173#ifdef JXL_QT_AUTOTRANSFORM
175 JxlDecoderSetKeepOrientation(m_decoder,
true);
179 if (!m_runner && num_worker_threads >= 4) {
182 num_worker_threads = num_worker_threads / 2;
183 num_worker_threads = qBound(2, num_worker_threads, 64);
184 m_runner = JxlThreadParallelRunnerCreate(
nullptr, num_worker_threads);
186 if (JxlDecoderSetParallelRunner(m_decoder, JxlThreadParallelRunner, m_runner) != JXL_DEC_SUCCESS) {
187 qWarning(
"ERROR: JxlDecoderSetParallelRunner failed");
188 m_parseState = ParseJpegXLError;
193 if (JxlDecoderSetInput(m_decoder,
reinterpret_cast<const uint8_t *
>(m_rawData.constData()), m_rawData.size()) != JXL_DEC_SUCCESS) {
194 qWarning(
"ERROR: JxlDecoderSetInput failed");
195 m_parseState = ParseJpegXLError;
199 JxlDecoderCloseInput(m_decoder);
200#ifndef JXL_DECODE_BOXES_DISABLED
201 JxlDecoderStatus
status = JxlDecoderSubscribeEvents(m_decoder, JXL_DEC_BASIC_INFO | JXL_DEC_COLOR_ENCODING | JXL_DEC_FRAME | JXL_DEC_BOX);
203 JxlDecoderStatus
status = JxlDecoderSubscribeEvents(m_decoder, JXL_DEC_BASIC_INFO | JXL_DEC_COLOR_ENCODING | JXL_DEC_FRAME);
205 if (
status == JXL_DEC_ERROR) {
206 qWarning(
"ERROR: JxlDecoderSubscribeEvents failed");
207 m_parseState = ParseJpegXLError;
211 if (!decodeBoxes(
status)) {
215 status = JxlDecoderGetBasicInfo(m_decoder, &m_basicinfo);
216 if (
status != JXL_DEC_SUCCESS) {
217 qWarning(
"ERROR: JXL basic info not available");
218 m_parseState = ParseJpegXLError;
222 if (m_basicinfo.xsize == 0 || m_basicinfo.ysize == 0) {
223 qWarning(
"ERROR: JXL image has zero dimensions");
224 m_parseState = ParseJpegXLError;
228 if (m_basicinfo.xsize > MAX_IMAGE_WIDTH || m_basicinfo.ysize > MAX_IMAGE_HEIGHT) {
229 qWarning(
"JXL image (%dx%d) is too large", m_basicinfo.xsize, m_basicinfo.ysize);
230 m_parseState = ParseJpegXLError;
234 m_parseState = ParseJpegXLBasicInfoParsed;
238bool QJpegXLHandler::countALLFrames()
240 if (m_parseState != ParseJpegXLBasicInfoParsed) {
245 if (!decodeBoxes(
status)) {
249 if (
status != JXL_DEC_COLOR_ENCODING) {
250 qWarning(
"Unexpected event %d instead of JXL_DEC_COLOR_ENCODING",
status);
251 m_parseState = ParseJpegXLError;
255 bool is_gray = m_basicinfo.num_color_channels == 1 && m_basicinfo.alpha_bits == 0;
256 JxlColorEncoding color_encoding;
257 if (m_basicinfo.uses_original_profile == JXL_FALSE && m_basicinfo.have_animation == JXL_FALSE) {
258#if JPEGXL_NUMERIC_VERSION >= JPEGXL_COMPUTE_NUMERIC_VERSION(0, 9, 0)
259 const JxlCmsInterface *jxlcms = JxlGetDefaultCms();
261 status = JxlDecoderSetCms(m_decoder, *jxlcms);
262 if (
status != JXL_DEC_SUCCESS) {
263 qWarning(
"JxlDecoderSetCms ERROR");
266 qWarning(
"No JPEG XL CMS Interface");
269 JxlColorEncodingSetToSRGB(&color_encoding, is_gray ? JXL_TRUE : JXL_FALSE);
270 JxlDecoderSetPreferredColorProfile(m_decoder, &color_encoding);
273 bool loadalpha =
false;
274 if (m_basicinfo.alpha_bits > 0) {
278 m_input_pixel_format.endianness = JXL_NATIVE_ENDIAN;
279 m_input_pixel_format.align = 4;
280 m_input_pixel_format.num_channels = is_gray ? 1 : 4;
282 if (m_basicinfo.bits_per_sample > 8) {
283#ifdef JXL_HDR_PRESERVATION_DISABLED
286 bool is_fp = m_basicinfo.exponent_bits_per_sample > 0 && m_basicinfo.num_color_channels == 3;
290 m_input_pixel_format.data_type = JXL_TYPE_UINT16;
292 m_buffer_size = ((size_t)m_basicinfo.ysize - 1) * (((((
size_t)m_basicinfo.xsize) * 2 + 3) >> 2) << 2) + (size_t)m_basicinfo.xsize * 2;
293 }
else if (m_basicinfo.bits_per_sample > 16 && is_fp) {
294 m_input_pixel_format.data_type = JXL_TYPE_FLOAT;
296 m_buffer_size = (size_t)m_basicinfo.xsize * (
size_t)m_basicinfo.ysize * m_input_pixel_format.num_channels * 4;
302 m_input_pixel_format.data_type = is_fp ? JXL_TYPE_FLOAT16 : JXL_TYPE_UINT16;
303 m_buffer_size = (size_t)m_basicinfo.xsize * (
size_t)m_basicinfo.ysize * m_input_pixel_format.num_channels * 2;
311 m_input_pixel_format.data_type = JXL_TYPE_UINT8;
315 m_buffer_size = ((size_t)m_basicinfo.ysize - 1) * (((((
size_t)m_basicinfo.xsize) + 3) >> 2) << 2) + (size_t)m_basicinfo.xsize;
318 m_buffer_size = (size_t)m_basicinfo.xsize * (
size_t)m_basicinfo.ysize * m_input_pixel_format.num_channels;
327 status = JxlDecoderGetColorAsEncodedProfile(m_decoder,
328#
if JPEGXL_NUMERIC_VERSION < JPEGXL_COMPUTE_NUMERIC_VERSION(0, 9, 0)
329 &m_input_pixel_format,
331 JXL_COLOR_PROFILE_TARGET_DATA,
334 if (
status == JXL_DEC_SUCCESS && color_encoding.color_space == JXL_COLOR_SPACE_RGB && color_encoding.white_point == JXL_WHITE_POINT_D65
335 && color_encoding.primaries == JXL_PRIMARIES_SRGB && color_encoding.transfer_function == JXL_TRANSFER_FUNCTION_SRGB) {
339 if (JxlDecoderGetICCProfileSize(m_decoder,
340#
if JPEGXL_NUMERIC_VERSION < JPEGXL_COMPUTE_NUMERIC_VERSION(0, 9, 0)
341 &m_input_pixel_format,
343 JXL_COLOR_PROFILE_TARGET_DATA,
345 == JXL_DEC_SUCCESS) {
348 if (JxlDecoderGetColorAsICCProfile(m_decoder,
349#
if JPEGXL_NUMERIC_VERSION < JPEGXL_COMPUTE_NUMERIC_VERSION(0, 9, 0)
350 &m_input_pixel_format,
352 JXL_COLOR_PROFILE_TARGET_DATA,
353 reinterpret_cast<uint8_t *
>(icc_data.data()),
355 == JXL_DEC_SUCCESS) {
358 if (!m_colorspace.isValid()) {
359 qWarning(
"JXL image has Qt-unsupported or invalid ICC profile!");
362 qWarning(
"Failed to obtain data from JPEG XL decoder");
365 qWarning(
"Empty ICC data");
368 qWarning(
"no ICC, other color profile");
372 if (m_basicinfo.have_animation) {
373 JxlFrameHeader frame_header;
376 for (
status = JxlDecoderProcessInput(m_decoder);
status != JXL_DEC_SUCCESS;
status = JxlDecoderProcessInput(m_decoder)) {
377 if (
status != JXL_DEC_FRAME) {
380 qWarning(
"ERROR: JXL decoding failed");
382 case JXL_DEC_NEED_MORE_INPUT:
383 qWarning(
"ERROR: JXL data incomplete");
386 qWarning(
"Unexpected event %d instead of JXL_DEC_FRAME",
status);
389 m_parseState = ParseJpegXLError;
393 if (JxlDecoderGetFrameHeader(m_decoder, &frame_header) != JXL_DEC_SUCCESS) {
394 qWarning(
"ERROR: JxlDecoderGetFrameHeader failed");
395 m_parseState = ParseJpegXLError;
399 if (m_basicinfo.animation.tps_denominator > 0 && m_basicinfo.animation.tps_numerator > 0) {
400 delay = (int)(0.5 + 1000.0 * frame_header.duration * m_basicinfo.animation.tps_denominator / m_basicinfo.animation.tps_numerator);
405 m_framedelays.append(delay);
408 if (m_framedelays.isEmpty()) {
409 qWarning(
"no frames loaded by the JXL plug-in");
410 m_parseState = ParseJpegXLError;
414 if (m_framedelays.count() == 1) {
415 qWarning(
"JXL file was marked as animation but it has only one frame.");
416 m_basicinfo.have_animation = JXL_FALSE;
419 m_framedelays.resize(1);
420 m_framedelays[0] = 0;
423#ifndef JXL_DECODE_BOXES_DISABLED
424 if (!decodeBoxes(
status)) {
433 m_next_image_delay = m_framedelays[0];
434 m_parseState = ParseJpegXLSuccess;
438bool QJpegXLHandler::decode_one_frame()
440 JxlDecoderStatus
status = JxlDecoderProcessInput(m_decoder);
441 if (
status != JXL_DEC_NEED_IMAGE_OUT_BUFFER) {
442 qWarning(
"Unexpected event %d instead of JXL_DEC_NEED_IMAGE_OUT_BUFFER",
status);
443 m_parseState = ParseJpegXLError;
447 m_current_image = imageAlloc(m_basicinfo.xsize, m_basicinfo.ysize, m_input_image_format);
448 if (m_current_image.isNull()) {
449 qWarning(
"Memory cannot be allocated");
450 m_parseState = ParseJpegXLError;
454 m_current_image.setColorSpace(m_colorspace);
455 if (!m_xmp.isEmpty()) {
456 m_current_image.setText(QStringLiteral(META_KEY_XMP_ADOBE),
QString::fromUtf8(m_xmp));
459 if (JxlDecoderSetImageOutBuffer(m_decoder, &m_input_pixel_format, m_current_image.bits(), m_buffer_size) != JXL_DEC_SUCCESS) {
460 qWarning(
"ERROR: JxlDecoderSetImageOutBuffer failed");
461 m_parseState = ParseJpegXLError;
465 status = JxlDecoderProcessInput(m_decoder);
466 if (
status != JXL_DEC_FULL_IMAGE) {
467 qWarning(
"Unexpected event %d instead of JXL_DEC_FULL_IMAGE",
status);
468 m_parseState = ParseJpegXLError;
472 if (m_target_image_format != m_input_image_format) {
473 m_current_image.convertTo(m_target_image_format);
476 m_next_image_delay = m_framedelays[m_currentimage_index];
477 m_previousimage_index = m_currentimage_index;
479 if (m_framedelays.count() > 1) {
480 m_currentimage_index++;
482 if (m_currentimage_index >= m_framedelays.count()) {
488 m_parseState = ParseJpegXLFinished;
490 m_parseState = ParseJpegXLSuccess;
494 m_parseState = ParseJpegXLFinished;
500bool QJpegXLHandler::read(
QImage *image)
502 if (!ensureALLCounted()) {
506 if (m_currentimage_index == m_previousimage_index) {
507 *image = m_current_image;
508 return jumpToNextImage();
511 if (decode_one_frame()) {
512 *image = m_current_image;
520void packRGBPixels(
QImage &img)
523 auto dest_pixels =
reinterpret_cast<T *
>(img.
bits());
524 for (qint32 y = 0; y < img.
height(); y++) {
525 auto src_pixels =
reinterpret_cast<const T *
>(img.
constScanLine(y));
526 for (qint32 x = 0; x < img.
width(); x++) {
528 *dest_pixels = *src_pixels;
532 *dest_pixels = *src_pixels;
536 *dest_pixels = *src_pixels;
543bool QJpegXLHandler::write(
const QImage &image)
546 qWarning(
"No image data to save");
550 if ((image.
width() == 0) || (image.
height() == 0)) {
551 qWarning(
"Image has zero dimension!");
555 if ((image.
width() > MAX_IMAGE_WIDTH) || (image.
height() > MAX_IMAGE_HEIGHT)) {
556 qWarning(
"Image (%dx%d) is too large to save!", image.
width(), image.
height());
560 size_t pixel_count = size_t(image.
width()) * image.
height();
561 if (MAX_IMAGE_PIXELS && pixel_count > MAX_IMAGE_PIXELS) {
562 qWarning(
"Image (%dx%d) will not be saved because it has more than %d megapixels!", image.
width(), image.
height(), MAX_IMAGE_PIXELS / 1024 / 1024);
567 bool save_fp =
false;
568 bool is_gray =
false;
574#ifndef JXL_HDR_PRESERVATION_DISABLED
582#ifndef JXL_HDR_PRESERVATION_DISABLED
603#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
604 case QImage::Format_CMYK8888:
624 if (image.
depth() > 32) {
632 JxlEncoder *encoder = JxlEncoderCreate(
nullptr);
634 qWarning(
"Failed to create Jxl encoder");
637 JxlEncoderUseBoxes(encoder);
639 if (m_quality > 100) {
641 }
else if (m_quality < 0) {
645 JxlBasicInfo output_info;
646 JxlEncoderInitBasicInfo(&output_info);
651 || m_quality == 100) {
655 if (iccprofile.
size() > 0 || m_quality == 100 || is_gray) {
656 output_info.uses_original_profile = JXL_TRUE;
661 if ( (save_depth > 8 && (image.
hasAlphaChannel() || output_info.uses_original_profile))
663 || (pixel_count > FEATURE_LEVEL_5_PIXELS)
664 || (image.
width() > FEATURE_LEVEL_5_WIDTH)
665 || (image.
height() > FEATURE_LEVEL_5_HEIGHT)) {
666 output_info.have_container = JXL_TRUE;
667 JxlEncoderUseContainer(encoder, JXL_TRUE);
668 JxlEncoderSetCodestreamLevel(encoder, 10);
672 void *runner =
nullptr;
675 if (num_worker_threads > 1) {
676 runner = JxlThreadParallelRunnerCreate(
nullptr, num_worker_threads);
677 if (JxlEncoderSetParallelRunner(encoder, JxlThreadParallelRunner, runner) != JXL_ENC_SUCCESS) {
678 qWarning(
"JxlEncoderSetParallelRunner failed");
679 JxlThreadParallelRunnerDestroy(runner);
680 JxlEncoderDestroy(encoder);
685 JxlPixelFormat pixel_format;
689 pixel_format.endianness = JXL_NATIVE_ENDIAN;
690 pixel_format.align = 0;
692 output_info.animation.tps_numerator = 10;
693 output_info.animation.tps_denominator = 1;
694 output_info.orientation = JXL_ORIENT_IDENTITY;
696 output_info.orientation = JXL_ORIENT_FLIP_HORIZONTAL;
698 output_info.orientation = JXL_ORIENT_ROTATE_180;
700 output_info.orientation = JXL_ORIENT_FLIP_VERTICAL;
702 output_info.orientation = JXL_ORIENT_TRANSPOSE;
704 output_info.orientation = JXL_ORIENT_ROTATE_90_CW;
706 output_info.orientation = JXL_ORIENT_ANTI_TRANSPOSE;
708 output_info.orientation = JXL_ORIENT_ROTATE_90_CCW;
711 if (save_depth > 8 && is_gray) {
712 pixel_format.data_type = JXL_TYPE_UINT16;
713 pixel_format.align = 4;
714 output_info.num_color_channels = 1;
715 output_info.bits_per_sample = 16;
717 pixel_format.num_channels = 1;
718 }
else if (is_gray) {
719 pixel_format.data_type = JXL_TYPE_UINT8;
720 pixel_format.align = 4;
721 output_info.num_color_channels = 1;
722 output_info.bits_per_sample = 8;
724 pixel_format.num_channels = 1;
725 }
else if (save_depth > 16) {
726 pixel_format.data_type = JXL_TYPE_FLOAT;
727 output_info.exponent_bits_per_sample = 8;
728 output_info.num_color_channels = 3;
729 output_info.bits_per_sample = 32;
733 pixel_format.num_channels = 4;
734 output_info.alpha_bits = 32;
735 output_info.alpha_exponent_bits = 8;
736 output_info.num_extra_channels = 1;
739 pixel_format.num_channels = 3;
740 output_info.alpha_bits = 0;
741 output_info.num_extra_channels = 0;
743 }
else if (save_depth > 8) {
744 pixel_format.data_type = save_fp ? JXL_TYPE_FLOAT16 : JXL_TYPE_UINT16;
745 output_info.exponent_bits_per_sample = save_fp ? 5 : 0;
746 output_info.num_color_channels = 3;
747 output_info.bits_per_sample = 16;
751 pixel_format.num_channels = 4;
752 output_info.alpha_bits = 16;
753 output_info.alpha_exponent_bits = save_fp ? 5 : 0;
754 output_info.num_extra_channels = 1;
757 pixel_format.num_channels = 3;
758 output_info.alpha_bits = 0;
759 output_info.num_extra_channels = 0;
762 pixel_format.data_type = JXL_TYPE_UINT8;
763 pixel_format.align = 4;
764 output_info.num_color_channels = 3;
765 output_info.bits_per_sample = 8;
769 pixel_format.num_channels = 4;
770 output_info.alpha_bits = 8;
771 output_info.num_extra_channels = 1;
774 pixel_format.num_channels = 3;
775 output_info.alpha_bits = 0;
776 output_info.num_extra_channels = 0;
780#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
784 if (cs.isValid() && cs.colorModel() == QColorSpace::ColorModel::Cmyk && image.
format() == QImage::Format_CMYK8888) {
793 const size_t xsize = tmpimage.
width();
794 const size_t ysize = tmpimage.
height();
796 if (xsize == 0 || ysize == 0 || tmpimage.
isNull()) {
797 qWarning(
"Unable to allocate memory for output image");
799 JxlThreadParallelRunnerDestroy(runner);
801 JxlEncoderDestroy(encoder);
805 output_info.xsize = tmpimage.
width();
806 output_info.ysize = tmpimage.
height();
808 status = JxlEncoderSetBasicInfo(encoder, &output_info);
809 if (
status != JXL_ENC_SUCCESS) {
810 qWarning(
"JxlEncoderSetBasicInfo failed!");
812 JxlThreadParallelRunnerDestroy(runner);
814 JxlEncoderDestroy(encoder);
818 auto xmp_data = image.
text(QStringLiteral(META_KEY_XMP_ADOBE)).
toUtf8();
819 if (!xmp_data.isEmpty()) {
820 const char *box_type =
"xml ";
821 status = JxlEncoderAddBox(encoder, box_type,
reinterpret_cast<const uint8_t *
>(xmp_data.constData()), xmp_data.size(), JXL_FALSE);
822 if (
status != JXL_ENC_SUCCESS) {
823 qWarning(
"JxlEncoderAddBox failed!");
825 JxlThreadParallelRunnerDestroy(runner);
827 JxlEncoderDestroy(encoder);
831 JxlEncoderCloseBoxes(encoder);
833 if (iccprofile.
size() > 0) {
834 status = JxlEncoderSetICCProfile(encoder,
reinterpret_cast<const uint8_t *
>(iccprofile.
constData()), iccprofile.
size());
835 if (
status != JXL_ENC_SUCCESS) {
836 qWarning(
"JxlEncoderSetICCProfile failed!");
838 JxlThreadParallelRunnerDestroy(runner);
840 JxlEncoderDestroy(encoder);
844 JxlColorEncoding color_profile;
845 JxlColorEncodingSetToSRGB(&color_profile, is_gray ? JXL_TRUE : JXL_FALSE);
847 status = JxlEncoderSetColorEncoding(encoder, &color_profile);
848 if (
status != JXL_ENC_SUCCESS) {
849 qWarning(
"JxlEncoderSetColorEncoding failed!");
851 JxlThreadParallelRunnerDestroy(runner);
853 JxlEncoderDestroy(encoder);
858 JxlEncoderFrameSettings *encoder_options = JxlEncoderFrameSettingsCreate(encoder,
nullptr);
860 JxlEncoderSetFrameDistance(encoder_options, (100.0f - m_quality) / 10.0f);
862 JxlEncoderSetFrameLossless(encoder_options, (m_quality == 100) ? JXL_TRUE : JXL_FALSE);
866 buffer_size = (size_t(save_depth / 8) * pixel_format.num_channels * xsize * ysize);
871 qWarning(
"Memory allocation error");
873 JxlThreadParallelRunnerDestroy(runner);
875 JxlEncoderDestroy(encoder);
880 if (save_depth > 16 && save_fp)
881 packRGBPixels<float>(tmpimage);
883 packRGBPixels<qfloat16>(tmpimage);
885 packRGBPixels<quint16>(tmpimage);
887 status = JxlEncoderAddImageFrame(encoder_options, &pixel_format,
static_cast<const void *
>(tmpimage.
constBits()), buffer_size);
889 if (
status == JXL_ENC_ERROR) {
890 qWarning(
"JxlEncoderAddImageFrame failed!");
892 JxlThreadParallelRunnerDestroy(runner);
894 JxlEncoderDestroy(encoder);
898 JxlEncoderCloseInput(encoder);
900 std::vector<uint8_t> compressed;
901 compressed.resize(4096);
906 next_out = compressed.data() + offset;
907 avail_out = compressed.size() - offset;
908 status = JxlEncoderProcessOutput(encoder, &next_out, &avail_out);
910 if (
status == JXL_ENC_NEED_MORE_OUTPUT) {
911 offset = next_out - compressed.data();
912 compressed.resize(compressed.size() * 2);
913 }
else if (
status == JXL_ENC_ERROR) {
914 qWarning(
"JxlEncoderProcessOutput failed!");
916 JxlThreadParallelRunnerDestroy(runner);
918 JxlEncoderDestroy(encoder);
921 }
while (
status != JXL_ENC_SUCCESS);
924 JxlThreadParallelRunnerDestroy(runner);
926 JxlEncoderDestroy(encoder);
928 compressed.resize(next_out - compressed.data());
930 if (compressed.size() > 0) {
931 qint64 write_status = device()->
write(
reinterpret_cast<const char *
>(compressed.data()), compressed.size());
933 if (write_status > 0) {
935 }
else if (write_status == -1) {
936 qWarning(
"Write error: %s\n", qUtf8Printable(device()->errorString()));
943QVariant QJpegXLHandler::option(ImageOption option)
const
945 if (!supportsOption(option)) {
949 if (option == Quality) {
953 if (!ensureParsed()) {
954#ifdef JXL_QT_AUTOTRANSFORM
955 if (option == ImageTransformation) {
956 return int(m_transformations);
964 return QSize(m_basicinfo.xsize, m_basicinfo.ysize);
966 if (m_basicinfo.have_animation) {
971#ifdef JXL_QT_AUTOTRANSFORM
972 case ImageTransformation:
973 if (m_basicinfo.orientation == JXL_ORIENT_IDENTITY) {
975 }
else if (m_basicinfo.orientation == JXL_ORIENT_FLIP_HORIZONTAL) {
977 }
else if (m_basicinfo.orientation == JXL_ORIENT_ROTATE_180) {
979 }
else if (m_basicinfo.orientation == JXL_ORIENT_FLIP_VERTICAL) {
981 }
else if (m_basicinfo.orientation == JXL_ORIENT_TRANSPOSE) {
983 }
else if (m_basicinfo.orientation == JXL_ORIENT_ROTATE_90_CW) {
985 }
else if (m_basicinfo.orientation == JXL_ORIENT_ANTI_TRANSPOSE) {
987 }
else if (m_basicinfo.orientation == JXL_ORIENT_ROTATE_90_CCW) {
999void QJpegXLHandler::setOption(ImageOption option,
const QVariant &value)
1003 m_quality = value.
toInt();
1004 if (m_quality > 100) {
1006 }
else if (m_quality < 0) {
1010#ifdef JXL_QT_AUTOTRANSFORM
1011 case ImageTransformation:
1012 if (
auto t = value.
toInt()) {
1024bool QJpegXLHandler::supportsOption(ImageOption option)
const
1026 auto supported = option == Quality || option == Size || option ==
Animation;
1027#ifdef JXL_QT_AUTOTRANSFORM
1028 supported = supported || option == ImageTransformation;
1033int QJpegXLHandler::imageCount()
const
1035 if (!ensureParsed()) {
1039 if (m_parseState == ParseJpegXLBasicInfoParsed) {
1040 if (!m_basicinfo.have_animation) {
1044 if (!ensureALLCounted()) {
1049 if (!m_framedelays.isEmpty()) {
1050 return m_framedelays.count();
1055int QJpegXLHandler::currentImageNumber()
const
1057 if (m_parseState == ParseJpegXLNotParsed) {
1061 if (m_parseState == ParseJpegXLError || m_parseState == ParseJpegXLBasicInfoParsed || !m_decoder) {
1065 return m_currentimage_index;
1068bool QJpegXLHandler::jumpToNextImage()
1070 if (!ensureALLCounted()) {
1074 if (m_framedelays.count() > 1) {
1075 m_currentimage_index++;
1077 if (m_currentimage_index >= m_framedelays.count()) {
1082 JxlDecoderSkipFrames(m_decoder, 1);
1086 m_parseState = ParseJpegXLSuccess;
1090bool QJpegXLHandler::jumpToImage(
int imageNumber)
1092 if (!ensureALLCounted()) {
1096 if (imageNumber < 0 || imageNumber >= m_framedelays.count()) {
1100 if (imageNumber == m_currentimage_index) {
1101 m_parseState = ParseJpegXLSuccess;
1105 if (imageNumber > m_currentimage_index) {
1106 JxlDecoderSkipFrames(m_decoder, imageNumber - m_currentimage_index);
1107 m_currentimage_index = imageNumber;
1108 m_parseState = ParseJpegXLSuccess;
1116 if (imageNumber > 0) {
1117 JxlDecoderSkipFrames(m_decoder, imageNumber);
1119 m_currentimage_index = imageNumber;
1120 m_parseState = ParseJpegXLSuccess;
1124int QJpegXLHandler::nextImageDelay()
const
1126 if (!ensureALLCounted()) {
1130 if (m_framedelays.count() < 2) {
1134 return m_next_image_delay;
1137int QJpegXLHandler::loopCount()
const
1139 if (!ensureParsed()) {
1143 if (m_basicinfo.have_animation) {
1144 return (m_basicinfo.animation.num_loops > 0) ? m_basicinfo.animation.num_loops - 1 : -1;
1150bool QJpegXLHandler::rewind()
1152 m_currentimage_index = 0;
1154 JxlDecoderReleaseInput(m_decoder);
1155 JxlDecoderRewind(m_decoder);
1157 if (JxlDecoderSetParallelRunner(m_decoder, JxlThreadParallelRunner, m_runner) != JXL_DEC_SUCCESS) {
1158 qWarning(
"ERROR: JxlDecoderSetParallelRunner failed");
1159 m_parseState = ParseJpegXLError;
1164 if (JxlDecoderSetInput(m_decoder,
reinterpret_cast<const uint8_t *
>(m_rawData.constData()), m_rawData.size()) != JXL_DEC_SUCCESS) {
1165 qWarning(
"ERROR: JxlDecoderSetInput failed");
1166 m_parseState = ParseJpegXLError;
1170 JxlDecoderCloseInput(m_decoder);
1172 if (m_basicinfo.uses_original_profile == JXL_FALSE && m_basicinfo.have_animation == JXL_FALSE) {
1173 if (JxlDecoderSubscribeEvents(m_decoder, JXL_DEC_COLOR_ENCODING | JXL_DEC_FULL_IMAGE) != JXL_DEC_SUCCESS) {
1174 qWarning(
"ERROR: JxlDecoderSubscribeEvents failed");
1175 m_parseState = ParseJpegXLError;
1179 JxlDecoderStatus
status = JxlDecoderProcessInput(m_decoder);
1180 if (
status != JXL_DEC_COLOR_ENCODING) {
1181 qWarning(
"Unexpected event %d instead of JXL_DEC_COLOR_ENCODING",
status);
1182 m_parseState = ParseJpegXLError;
1186#if JPEGXL_NUMERIC_VERSION >= JPEGXL_COMPUTE_NUMERIC_VERSION(0, 9, 0)
1187 const JxlCmsInterface *jxlcms = JxlGetDefaultCms();
1189 status = JxlDecoderSetCms(m_decoder, *jxlcms);
1190 if (
status != JXL_DEC_SUCCESS) {
1191 qWarning(
"JxlDecoderSetCms ERROR");
1194 qWarning(
"No JPEG XL CMS Interface");
1198 bool is_gray = m_basicinfo.num_color_channels == 1 && m_basicinfo.alpha_bits == 0;
1199 JxlColorEncoding color_encoding;
1200 JxlColorEncodingSetToSRGB(&color_encoding, is_gray ? JXL_TRUE : JXL_FALSE);
1201 JxlDecoderSetPreferredColorProfile(m_decoder, &color_encoding);
1203 if (JxlDecoderSubscribeEvents(m_decoder, JXL_DEC_FULL_IMAGE) != JXL_DEC_SUCCESS) {
1204 qWarning(
"ERROR: JxlDecoderSubscribeEvents failed");
1205 m_parseState = ParseJpegXLError;
1213bool QJpegXLHandler::decodeBoxes(JxlDecoderStatus &
status)
1216 status = JxlDecoderProcessInput(m_decoder);
1217 if (
status == JXL_DEC_BOX) {
1219 JxlDecoderGetBoxType(m_decoder, type, JXL_FALSE);
1220 if (memcmp(type,
"xml ", 4) == 0) {
1222 if (JxlDecoderGetBoxSizeRaw(m_decoder, &size) == JXL_DEC_SUCCESS && size < uint64_t(kMaxQVectorSize)) {
1224 JxlDecoderSetBoxBuffer(m_decoder,
reinterpret_cast<uint8_t *
>(m_xmp.data()), m_xmp.size());
1228 }
while (
status == JXL_DEC_BOX);
1230 if (
status == JXL_DEC_ERROR) {
1231 qWarning(
"ERROR: JXL decoding failed");
1232 m_parseState = ParseJpegXLError;
1235 if (
status == JXL_DEC_NEED_MORE_INPUT) {
1236 qWarning(
"ERROR: JXL data incomplete");
1237 m_parseState = ParseJpegXLError;
1245 if (format ==
"jxl") {
1257 if (device->
isReadable() && QJpegXLHandler::canRead(device)) {
1276#include "moc_jxl_p.cpp"
Q_SCRIPTABLE CaptureState status()
Type type(const QSqlDatabase &db)
QFlags< Capability > Capabilities
const char * constData() const const
bool isEmpty() const const
qsizetype size() const const
QColorSpace fromIccProfile(const QByteArray &iccProfile)
QByteArray iccProfile() const const
bool isValid() const const
Primaries primaries() const const
TransferFunction transferFunction() const const
qsizetype bytesPerLine() const const
QColorSpace colorSpace() const const
const uchar * constBits() const const
const uchar * constScanLine(int i) const const
QImage convertedToColorSpace(const QColorSpace &colorSpace) const const
bool hasAlphaChannel() const const
bool isGrayscale() const const
bool isNull() const const
QString text(const QString &key) const const
void setDevice(QIODevice *device)
virtual void setOption(ImageOption option, const QVariant &value)
bool isOpen() const const
bool isReadable() const const
bool isWritable() const const
QByteArray peek(qint64 maxSize)
qint64 write(const QByteArray &data)
QString fromUtf8(QByteArrayView str)
QByteArray toUtf8() const const
int toInt(bool *ok) const const