13#include "microexif_p.h"
17#include <jxl/encode.h>
18#include <jxl/thread_parallel_runner.h>
23#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))
24#ifndef JXL_QT_AUTOTRANSFORM
25#define JXL_QT_AUTOTRANSFORM
29#ifndef JXL_HDR_PRESERVATION_DISABLED
35#ifndef JXL_DECODE_BOXES_DISABLED
41#define FEATURE_LEVEL_5_WIDTH 262144
42#define FEATURE_LEVEL_5_HEIGHT 262144
43#define FEATURE_LEVEL_5_PIXELS 268435456
45#if QT_POINTER_SIZE < 8
46#define MAX_IMAGE_WIDTH 32767
47#define MAX_IMAGE_HEIGHT 32767
48#define MAX_IMAGE_PIXELS FEATURE_LEVEL_5_PIXELS
50#define MAX_IMAGE_WIDTH FEATURE_LEVEL_5_WIDTH
51#define MAX_IMAGE_HEIGHT FEATURE_LEVEL_5_HEIGHT
52#define MAX_IMAGE_PIXELS FEATURE_LEVEL_5_PIXELS
55QJpegXLHandler::QJpegXLHandler()
56 : m_parseState(ParseJpegXLNotParsed)
58 , m_currentimage_index(0)
59 , m_previousimage_index(-1)
63 , m_next_image_delay(0)
65 , m_cmyk_channel_id(0)
66 , m_alpha_channel_id(0)
67 , m_input_image_format(
QImage::Format_Invalid)
68 , m_target_image_format(
QImage::Format_Invalid)
72QJpegXLHandler::~QJpegXLHandler()
75 JxlThreadParallelRunnerDestroy(m_runner);
78 JxlDecoderDestroy(m_decoder);
82bool QJpegXLHandler::canRead()
const
84 if (m_parseState == ParseJpegXLNotParsed && !canRead(device())) {
88 if (m_parseState != ParseJpegXLError) {
91 if (m_parseState == ParseJpegXLFinished) {
100bool QJpegXLHandler::canRead(
QIODevice *device)
106 if (header.
size() < 12) {
110 JxlSignature signature = JxlSignatureCheck(
reinterpret_cast<const uint8_t *
>(header.
constData()), header.
size());
111 if (signature == JXL_SIG_CODESTREAM || signature == JXL_SIG_CONTAINER) {
117bool QJpegXLHandler::ensureParsed()
const
119 if (m_parseState == ParseJpegXLSuccess || m_parseState == ParseJpegXLBasicInfoParsed || m_parseState == ParseJpegXLFinished) {
122 if (m_parseState == ParseJpegXLError) {
126 QJpegXLHandler *that =
const_cast<QJpegXLHandler *
>(
this);
128 return that->ensureDecoder();
131bool QJpegXLHandler::ensureALLCounted()
const
133 if (!ensureParsed()) {
137 if (m_parseState == ParseJpegXLSuccess || m_parseState == ParseJpegXLFinished) {
141 QJpegXLHandler *that =
const_cast<QJpegXLHandler *
>(
this);
143 return that->countALLFrames();
146bool QJpegXLHandler::ensureDecoder()
152 m_rawData = device()->
readAll();
154 if (m_rawData.isEmpty()) {
158 JxlSignature signature = JxlSignatureCheck(
reinterpret_cast<const uint8_t *
>(m_rawData.constData()), m_rawData.size());
159 if (signature != JXL_SIG_CODESTREAM && signature != JXL_SIG_CONTAINER) {
160 m_parseState = ParseJpegXLError;
164 m_decoder = JxlDecoderCreate(
nullptr);
166 qWarning(
"ERROR: JxlDecoderCreate failed");
167 m_parseState = ParseJpegXLError;
171#ifdef JXL_QT_AUTOTRANSFORM
173 JxlDecoderSetKeepOrientation(m_decoder,
true);
177 if (!m_runner && num_worker_threads >= 4) {
180 num_worker_threads = num_worker_threads / 2;
181 num_worker_threads = qBound(2, num_worker_threads, 64);
182 m_runner = JxlThreadParallelRunnerCreate(
nullptr, num_worker_threads);
184 if (JxlDecoderSetParallelRunner(m_decoder, JxlThreadParallelRunner, m_runner) != JXL_DEC_SUCCESS) {
185 qWarning(
"ERROR: JxlDecoderSetParallelRunner failed");
186 m_parseState = ParseJpegXLError;
191 if (JxlDecoderSetInput(m_decoder,
reinterpret_cast<const uint8_t *
>(m_rawData.constData()), m_rawData.size()) != JXL_DEC_SUCCESS) {
192 qWarning(
"ERROR: JxlDecoderSetInput failed");
193 m_parseState = ParseJpegXLError;
197 JxlDecoderCloseInput(m_decoder);
199 JxlDecoderStatus
status = JxlDecoderSubscribeEvents(m_decoder, JXL_DEC_BASIC_INFO | JXL_DEC_COLOR_ENCODING | JXL_DEC_FRAME);
200 if (
status == JXL_DEC_ERROR) {
201 qWarning(
"ERROR: JxlDecoderSubscribeEvents failed");
202 m_parseState = ParseJpegXLError;
206 status = JxlDecoderProcessInput(m_decoder);
207 if (
status == JXL_DEC_ERROR) {
208 qWarning(
"ERROR: JXL decoding failed");
209 m_parseState = ParseJpegXLError;
212 if (
status == JXL_DEC_NEED_MORE_INPUT) {
213 qWarning(
"ERROR: JXL data incomplete");
214 m_parseState = ParseJpegXLError;
218 status = JxlDecoderGetBasicInfo(m_decoder, &m_basicinfo);
219 if (
status != JXL_DEC_SUCCESS) {
220 qWarning(
"ERROR: JXL basic info not available");
221 m_parseState = ParseJpegXLError;
225 if (m_basicinfo.xsize == 0 || m_basicinfo.ysize == 0) {
226 qWarning(
"ERROR: JXL image has zero dimensions");
227 m_parseState = ParseJpegXLError;
231 if (m_basicinfo.xsize > MAX_IMAGE_WIDTH || m_basicinfo.ysize > MAX_IMAGE_HEIGHT) {
232 qWarning(
"JXL image (%dx%d) is too large", m_basicinfo.xsize, m_basicinfo.ysize);
233 m_parseState = ParseJpegXLError;
237 m_parseState = ParseJpegXLBasicInfoParsed;
241bool QJpegXLHandler::countALLFrames()
243 if (m_parseState != ParseJpegXLBasicInfoParsed) {
247 JxlDecoderStatus
status = JxlDecoderProcessInput(m_decoder);
248 if (
status != JXL_DEC_COLOR_ENCODING) {
249 qWarning(
"Unexpected event %d instead of JXL_DEC_COLOR_ENCODING",
status);
250 m_parseState = ParseJpegXLError;
254 bool is_gray = m_basicinfo.num_color_channels == 1 && m_basicinfo.alpha_bits == 0;
255 JxlColorEncoding color_encoding;
256 if (m_basicinfo.uses_original_profile == JXL_FALSE && m_basicinfo.have_animation == JXL_FALSE) {
257 const JxlCmsInterface *jxlcms = JxlGetDefaultCms();
259 status = JxlDecoderSetCms(m_decoder, *jxlcms);
260 if (
status != JXL_DEC_SUCCESS) {
261 qWarning(
"JxlDecoderSetCms ERROR");
264 qWarning(
"No JPEG XL CMS Interface");
267 JxlColorEncodingSetToSRGB(&color_encoding, is_gray ? JXL_TRUE : JXL_FALSE);
268 JxlDecoderSetPreferredColorProfile(m_decoder, &color_encoding);
271 bool loadalpha =
false;
272 if (m_basicinfo.alpha_bits > 0) {
276 m_input_pixel_format.endianness = JXL_NATIVE_ENDIAN;
277 m_input_pixel_format.align = 4;
279 if (m_basicinfo.bits_per_sample > 8) {
280#ifdef JXL_HDR_PRESERVATION_DISABLED
283 bool is_fp = m_basicinfo.exponent_bits_per_sample > 0 && m_basicinfo.num_color_channels == 3;
286 m_input_pixel_format.num_channels = 4;
289 m_input_pixel_format.num_channels = 1;
290 m_input_pixel_format.data_type = JXL_TYPE_UINT16;
292 }
else if (m_basicinfo.bits_per_sample > 16 && is_fp) {
293 m_input_pixel_format.data_type = JXL_TYPE_FLOAT;
300 m_input_pixel_format.data_type = is_fp ? JXL_TYPE_FLOAT16 : JXL_TYPE_UINT16;
308 m_input_pixel_format.data_type = JXL_TYPE_UINT8;
311 m_input_pixel_format.num_channels = 1;
315 m_input_pixel_format.num_channels = 4;
319 m_input_pixel_format.num_channels = 3;
326 status = JxlDecoderGetColorAsEncodedProfile(m_decoder, JXL_COLOR_PROFILE_TARGET_DATA, &color_encoding);
328 if (
status == JXL_DEC_SUCCESS && color_encoding.color_space == JXL_COLOR_SPACE_RGB && color_encoding.white_point == JXL_WHITE_POINT_D65
329 && color_encoding.primaries == JXL_PRIMARIES_SRGB && color_encoding.transfer_function == JXL_TRANSFER_FUNCTION_SRGB) {
333 if (JxlDecoderGetICCProfileSize(m_decoder, JXL_COLOR_PROFILE_TARGET_DATA, &icc_size) == JXL_DEC_SUCCESS) {
336 if (JxlDecoderGetColorAsICCProfile(m_decoder, JXL_COLOR_PROFILE_TARGET_DATA,
reinterpret_cast<uint8_t *
>(icc_data.data()), icc_data.size())
337 == JXL_DEC_SUCCESS) {
340 if (!m_colorspace.isValid()) {
341 qWarning(
"JXL image has Qt-unsupported or invalid ICC profile!");
344 qWarning(
"Failed to obtain data from JPEG XL decoder");
347 qWarning(
"Empty ICC data");
350 qWarning(
"no ICC, other color profile");
354 if (m_basicinfo.have_animation) {
355 JxlFrameHeader frame_header;
358 for (
status = JxlDecoderProcessInput(m_decoder);
status != JXL_DEC_SUCCESS;
status = JxlDecoderProcessInput(m_decoder)) {
359 if (
status != JXL_DEC_FRAME) {
362 qWarning(
"ERROR: JXL decoding failed");
364 case JXL_DEC_NEED_MORE_INPUT:
365 qWarning(
"ERROR: JXL data incomplete");
368 qWarning(
"Unexpected event %d instead of JXL_DEC_FRAME",
status);
371 m_parseState = ParseJpegXLError;
375 if (JxlDecoderGetFrameHeader(m_decoder, &frame_header) != JXL_DEC_SUCCESS) {
376 qWarning(
"ERROR: JxlDecoderGetFrameHeader failed");
377 m_parseState = ParseJpegXLError;
381 if (m_basicinfo.animation.tps_denominator > 0 && m_basicinfo.animation.tps_numerator > 0) {
382 delay = (int)(0.5 + 1000.0 * frame_header.duration * m_basicinfo.animation.tps_denominator / m_basicinfo.animation.tps_numerator);
387 m_framedelays.append(delay);
389 if (frame_header.is_last == JXL_TRUE) {
394 if (m_framedelays.isEmpty()) {
395 qWarning(
"no frames loaded by the JXL plug-in");
396 m_parseState = ParseJpegXLError;
400 if (m_framedelays.count() == 1) {
401 qWarning(
"JXL file was marked as animation but it has only one frame.");
402 m_basicinfo.have_animation = JXL_FALSE;
405 m_framedelays.resize(1);
406 m_framedelays[0] = 0;
409#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
411 if ((m_basicinfo.uses_original_profile == JXL_TRUE) && (m_basicinfo.num_color_channels == 3) && (m_colorspace.isValid())) {
412 bool alpha_found =
false;
413 JxlExtraChannelInfo channel_info;
414 for (uint32_t index = 0; index < m_basicinfo.num_extra_channels; index++) {
415 status = JxlDecoderGetExtraChannelInfo(m_decoder, index, &channel_info);
416 if (
status != JXL_DEC_SUCCESS) {
417 qWarning(
"JxlDecoderGetExtraChannelInfo for channel %d returned %d", index,
status);
418 m_parseState = ParseJpegXLError;
422 if (channel_info.type == JXL_CHANNEL_BLACK) {
423 if (m_colorspace.colorModel() == QColorSpace::ColorModel::Cmyk) {
425 m_cmyk_channel_id = index;
427 if (m_basicinfo.alpha_bits > 0) {
430 for (uint32_t alpha_index = index + 1; alpha_index < m_basicinfo.num_extra_channels; alpha_index++) {
431 status = JxlDecoderGetExtraChannelInfo(m_decoder, alpha_index, &channel_info);
432 if (
status != JXL_DEC_SUCCESS) {
433 qWarning(
"JxlDecoderGetExtraChannelInfo for channel %d returned %d", alpha_index,
status);
434 m_parseState = ParseJpegXLError;
438 if (channel_info.type == JXL_CHANNEL_ALPHA) {
440 m_alpha_channel_id = alpha_index;
446 qWarning(
"JXL BasicInfo indicate Alpha channel but it was not found");
447 m_parseState = ParseJpegXLError;
453 qWarning(
"JXL has BLACK channel but colorspace is not CMYK!");
456 }
else if ((channel_info.type == JXL_CHANNEL_ALPHA) && !alpha_found) {
458 m_alpha_channel_id = index;
462 if (!m_isCMYK && (m_colorspace.colorModel() == QColorSpace::ColorModel::Cmyk)) {
463 qWarning(
"JXL has CMYK colorspace but BLACK channel was not found!");
468#ifndef JXL_DECODE_BOXES_DISABLED
469 if (!decodeContainer()) {
478 m_next_image_delay = m_framedelays[0];
479 m_parseState = ParseJpegXLSuccess;
483bool QJpegXLHandler::decode_one_frame()
485 JxlDecoderStatus
status = JxlDecoderProcessInput(m_decoder);
486 if (
status != JXL_DEC_NEED_IMAGE_OUT_BUFFER) {
487 qWarning(
"Unexpected event %d instead of JXL_DEC_NEED_IMAGE_OUT_BUFFER",
status);
488 m_parseState = ParseJpegXLError;
493#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
494 uchar *pixels_cmy =
nullptr;
495 uchar *pixels_black =
nullptr;
497 JxlPixelFormat format_extra;
499 m_input_pixel_format.num_channels = 3;
500 m_input_pixel_format.data_type = JXL_TYPE_UINT8;
501 m_input_pixel_format.endianness = JXL_NATIVE_ENDIAN;
502 m_input_pixel_format.align = 0;
504 format_extra.num_channels = 1;
505 format_extra.data_type = JXL_TYPE_UINT8;
506 format_extra.endianness = JXL_NATIVE_ENDIAN;
507 format_extra.align = 0;
509 const size_t extra_buffer_size = size_t(m_basicinfo.xsize) * size_t(m_basicinfo.ysize);
510 const size_t cmy_buffer_size = extra_buffer_size * 3;
512 if (m_basicinfo.alpha_bits > 0) {
513 QImage tmp_cmyk_image = imageAlloc(m_basicinfo.xsize, m_basicinfo.ysize, QImage::Format_CMYK8888);
514 if (tmp_cmyk_image.
isNull()) {
515 qWarning(
"Memory cannot be allocated");
516 m_parseState = ParseJpegXLError;
522 uchar *pixels_alpha =
reinterpret_cast<uchar *
>(malloc(extra_buffer_size));
524 qWarning(
"Memory cannot be allocated for ALPHA channel");
525 m_parseState = ParseJpegXLError;
529 pixels_cmy =
reinterpret_cast<uchar *
>(malloc(cmy_buffer_size));
532 pixels_alpha =
nullptr;
533 qWarning(
"Memory cannot be allocated for CMY buffer");
534 m_parseState = ParseJpegXLError;
538 pixels_black =
reinterpret_cast<uchar *
>(malloc(extra_buffer_size));
541 pixels_cmy =
nullptr;
543 pixels_alpha =
nullptr;
544 qWarning(
"Memory cannot be allocated for BLACK buffer");
545 m_parseState = ParseJpegXLError;
549 if (JxlDecoderSetImageOutBuffer(m_decoder, &m_input_pixel_format, pixels_cmy, cmy_buffer_size) != JXL_DEC_SUCCESS) {
551 pixels_black =
nullptr;
553 pixels_cmy =
nullptr;
555 pixels_alpha =
nullptr;
556 qWarning(
"ERROR: JxlDecoderSetImageOutBuffer failed");
557 m_parseState = ParseJpegXLError;
561 if (JxlDecoderSetExtraChannelBuffer(m_decoder, &format_extra, pixels_black, extra_buffer_size, m_cmyk_channel_id) != JXL_DEC_SUCCESS) {
563 pixels_black =
nullptr;
565 pixels_cmy =
nullptr;
567 pixels_alpha =
nullptr;
568 qWarning(
"ERROR: JxlDecoderSetExtraChannelBuffer failed");
569 m_parseState = ParseJpegXLError;
573 if (JxlDecoderSetExtraChannelBuffer(m_decoder, &format_extra, pixels_alpha, extra_buffer_size, m_alpha_channel_id) != JXL_DEC_SUCCESS) {
575 pixels_black =
nullptr;
577 pixels_cmy =
nullptr;
579 pixels_alpha =
nullptr;
580 qWarning(
"ERROR: JxlDecoderSetExtraChannelBuffer failed");
581 m_parseState = ParseJpegXLError;
585 status = JxlDecoderProcessInput(m_decoder);
586 if (
status != JXL_DEC_FULL_IMAGE) {
588 pixels_black =
nullptr;
590 pixels_cmy =
nullptr;
592 pixels_alpha =
nullptr;
593 qWarning(
"Unexpected event %d instead of JXL_DEC_FULL_IMAGE",
status);
594 m_parseState = ParseJpegXLError;
598 const uchar *src_CMY = pixels_cmy;
599 const uchar *src_K = pixels_black;
600 for (
int y = 0; y < tmp_cmyk_image.
height(); y++) {
601 uchar *write_pointer = tmp_cmyk_image.
scanLine(y);
602 for (
int x = 0; x < tmp_cmyk_image.
width(); x++) {
603 *write_pointer = 255 - *src_CMY;
606 *write_pointer = 255 - *src_CMY;
609 *write_pointer = 255 - *src_CMY;
612 *write_pointer = 255 - *src_K;
619 pixels_black =
nullptr;
621 pixels_cmy =
nullptr;
624 if (m_current_image.isNull()) {
626 pixels_alpha =
nullptr;
627 qWarning(
"ERROR: convertedToColorSpace returned empty image");
628 m_parseState = ParseJpegXLError;
633 const uchar *src_alpha = pixels_alpha;
634 for (
int y = 0; y < m_current_image.height(); y++) {
635 uchar *write_pointer = m_current_image.scanLine(y);
636 for (
int x = 0; x < m_current_image.width(); x++) {
637#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
639 *write_pointer = *src_alpha;
643 *write_pointer = *src_alpha;
651 pixels_alpha =
nullptr;
653 m_current_image = imageAlloc(m_basicinfo.xsize, m_basicinfo.ysize, QImage::Format_CMYK8888);
654 if (m_current_image.isNull()) {
655 qWarning(
"Memory cannot be allocated");
656 m_parseState = ParseJpegXLError;
660 m_current_image.setColorSpace(m_colorspace);
662 pixels_cmy =
reinterpret_cast<uchar *
>(malloc(cmy_buffer_size));
664 qWarning(
"Memory cannot be allocated for CMY buffer");
665 m_parseState = ParseJpegXLError;
669 pixels_black =
reinterpret_cast<uchar *
>(malloc(extra_buffer_size));
672 pixels_cmy =
nullptr;
673 qWarning(
"Memory cannot be allocated for BLACK buffer");
674 m_parseState = ParseJpegXLError;
678 if (JxlDecoderSetImageOutBuffer(m_decoder, &m_input_pixel_format, pixels_cmy, cmy_buffer_size) != JXL_DEC_SUCCESS) {
680 pixels_black =
nullptr;
682 pixels_cmy =
nullptr;
683 qWarning(
"ERROR: JxlDecoderSetImageOutBuffer failed");
684 m_parseState = ParseJpegXLError;
688 if (JxlDecoderSetExtraChannelBuffer(m_decoder, &format_extra, pixels_black, extra_buffer_size, m_cmyk_channel_id) != JXL_DEC_SUCCESS) {
690 pixels_black =
nullptr;
692 pixels_cmy =
nullptr;
693 qWarning(
"ERROR: JxlDecoderSetExtraChannelBuffer failed");
694 m_parseState = ParseJpegXLError;
698 status = JxlDecoderProcessInput(m_decoder);
699 if (
status != JXL_DEC_FULL_IMAGE) {
701 pixels_black =
nullptr;
703 pixels_cmy =
nullptr;
704 qWarning(
"Unexpected event %d instead of JXL_DEC_FULL_IMAGE",
status);
705 m_parseState = ParseJpegXLError;
709 const uchar *src_CMY = pixels_cmy;
710 const uchar *src_K = pixels_black;
711 for (
int y = 0; y < m_current_image.height(); y++) {
712 uchar *write_pointer = m_current_image.scanLine(y);
713 for (
int x = 0; x < m_current_image.width(); x++) {
714 *write_pointer = 255 - *src_CMY;
717 *write_pointer = 255 - *src_CMY;
720 *write_pointer = 255 - *src_CMY;
723 *write_pointer = 255 - *src_K;
730 pixels_black =
nullptr;
732 pixels_cmy =
nullptr;
736 m_parseState = ParseJpegXLError;
740 m_current_image = imageAlloc(m_basicinfo.xsize, m_basicinfo.ysize, m_input_image_format);
741 if (m_current_image.isNull()) {
742 qWarning(
"Memory cannot be allocated");
743 m_parseState = ParseJpegXLError;
747 m_current_image.setColorSpace(m_colorspace);
749 m_input_pixel_format.align = m_current_image.bytesPerLine();
751 size_t rgb_buffer_size = size_t(m_current_image.height() - 1) * size_t(m_current_image.bytesPerLine());
752 switch (m_input_pixel_format.data_type) {
754 rgb_buffer_size += 4 * size_t(m_input_pixel_format.num_channels) * size_t(m_current_image.width());
757 rgb_buffer_size += size_t(m_input_pixel_format.num_channels) * size_t(m_current_image.width());
759 case JXL_TYPE_UINT16:
760 case JXL_TYPE_FLOAT16:
761 rgb_buffer_size += 2 * size_t(m_input_pixel_format.num_channels) * size_t(m_current_image.width());
764 qWarning(
"ERROR: unsupported data type");
765 m_parseState = ParseJpegXLError;
770 if (JxlDecoderSetImageOutBuffer(m_decoder, &m_input_pixel_format, m_current_image.bits(), rgb_buffer_size) != JXL_DEC_SUCCESS) {
771 qWarning(
"ERROR: JxlDecoderSetImageOutBuffer failed");
772 m_parseState = ParseJpegXLError;
776 status = JxlDecoderProcessInput(m_decoder);
777 if (
status != JXL_DEC_FULL_IMAGE) {
778 qWarning(
"Unexpected event %d instead of JXL_DEC_FULL_IMAGE",
status);
779 m_parseState = ParseJpegXLError;
783 if (m_target_image_format != m_input_image_format) {
784 m_current_image.convertTo(m_target_image_format);
788 if (!m_xmp.isEmpty()) {
789 m_current_image.setText(QStringLiteral(META_KEY_XMP_ADOBE),
QString::fromUtf8(m_xmp));
792 if (!m_exif.isEmpty()) {
793 auto exif = MicroExif::fromByteArray(m_exif);
794 exif.updateImageResolution(m_current_image);
795 exif.updateImageMetadata(m_current_image);
798 m_next_image_delay = m_framedelays[m_currentimage_index];
799 m_previousimage_index = m_currentimage_index;
801 if (m_framedelays.count() > 1) {
802 m_currentimage_index++;
804 if (m_currentimage_index >= m_framedelays.count()) {
810 m_parseState = ParseJpegXLFinished;
812 m_parseState = ParseJpegXLSuccess;
816 m_parseState = ParseJpegXLFinished;
822bool QJpegXLHandler::read(
QImage *image)
824 if (!ensureALLCounted()) {
828 if (m_currentimage_index == m_previousimage_index) {
829 *image = m_current_image;
830 return jumpToNextImage();
833 if (decode_one_frame()) {
834 *image = m_current_image;
841bool QJpegXLHandler::write(
const QImage &image)
844 qWarning(
"No image data to save");
848 if ((image.
width() == 0) || (image.
height() == 0)) {
849 qWarning(
"Image has zero dimension!");
853 if ((image.
width() > MAX_IMAGE_WIDTH) || (image.
height() > MAX_IMAGE_HEIGHT)) {
854 qWarning(
"Image (%dx%d) is too large to save!", image.
width(), image.
height());
858 size_t pixel_count = size_t(image.
width()) * image.
height();
859 if (MAX_IMAGE_PIXELS && pixel_count > MAX_IMAGE_PIXELS) {
860 qWarning(
"Image (%dx%d) will not be saved because it has more than %d megapixels!", image.
width(), image.
height(), MAX_IMAGE_PIXELS / 1024 / 1024);
864 JxlEncoder *encoder = JxlEncoderCreate(
nullptr);
866 qWarning(
"Failed to create Jxl encoder");
870 void *runner =
nullptr;
873 if (num_worker_threads > 1) {
874 runner = JxlThreadParallelRunnerCreate(
nullptr, num_worker_threads);
875 if (JxlEncoderSetParallelRunner(encoder, JxlThreadParallelRunner, runner) != JXL_ENC_SUCCESS) {
876 qWarning(
"JxlEncoderSetParallelRunner failed");
877 JxlThreadParallelRunnerDestroy(runner);
878 JxlEncoderDestroy(encoder);
883 if (m_quality > 100) {
885 }
else if (m_quality < 0) {
889 JxlEncoderUseContainer(encoder, JXL_TRUE);
890 JxlEncoderUseBoxes(encoder);
892 JxlBasicInfo output_info;
893 JxlEncoderInitBasicInfo(&output_info);
894 output_info.have_container = JXL_TRUE;
896 output_info.animation.tps_numerator = 10;
897 output_info.animation.tps_denominator = 1;
898 output_info.orientation = JXL_ORIENT_IDENTITY;
900 output_info.orientation = JXL_ORIENT_FLIP_HORIZONTAL;
902 output_info.orientation = JXL_ORIENT_ROTATE_180;
904 output_info.orientation = JXL_ORIENT_FLIP_VERTICAL;
906 output_info.orientation = JXL_ORIENT_TRANSPOSE;
908 output_info.orientation = JXL_ORIENT_ROTATE_90_CW;
910 output_info.orientation = JXL_ORIENT_ANTI_TRANSPOSE;
912 output_info.orientation = JXL_ORIENT_ROTATE_90_CCW;
915 bool save_cmyk =
false;
916#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
923 JxlPixelFormat pixel_format;
924 pixel_format.endianness = JXL_NATIVE_ENDIAN;
925 pixel_format.align = 0;
927 auto exif_data = MicroExif::fromImage(image).toByteArray();
928 auto xmp_data = image.
text(QStringLiteral(META_KEY_XMP_ADOBE)).
toUtf8();
931#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
932 output_info.uses_original_profile = JXL_TRUE;
933 output_info.xsize = image.
width();
934 output_info.ysize = image.
height();
935 output_info.num_color_channels = 3;
936 output_info.bits_per_sample = 8;
937 output_info.alpha_bits = 0;
938 output_info.num_extra_channels = 1;
940 pixel_format.num_channels = 3;
941 pixel_format.data_type = JXL_TYPE_UINT8;
943 JxlPixelFormat format_extra;
944 format_extra.num_channels = 1;
945 format_extra.data_type = JXL_TYPE_UINT8;
946 format_extra.endianness = JXL_NATIVE_ENDIAN;
947 format_extra.align = 0;
949 JxlExtraChannelInfo extra_black_channel;
950 JxlEncoderInitExtraChannelInfo(JXL_CHANNEL_BLACK, &extra_black_channel);
951 extra_black_channel.bits_per_sample = output_info.bits_per_sample;
952 extra_black_channel.exponent_bits_per_sample = output_info.exponent_bits_per_sample;
956 qWarning(
"ERROR saving CMYK JXL: empty ICC profile");
958 JxlThreadParallelRunnerDestroy(runner);
960 JxlEncoderDestroy(encoder);
964 status = JxlEncoderSetBasicInfo(encoder, &output_info);
965 if (
status != JXL_ENC_SUCCESS) {
966 qWarning(
"JxlEncoderSetBasicInfo for CMYK image failed!");
968 JxlThreadParallelRunnerDestroy(runner);
970 JxlEncoderDestroy(encoder);
974 status = JxlEncoderSetExtraChannelInfo(encoder, 0, &extra_black_channel);
975 if (
status != JXL_ENC_SUCCESS) {
976 qWarning(
"JxlEncoderSetExtraChannelInfo for CMYK image failed!");
978 JxlThreadParallelRunnerDestroy(runner);
980 JxlEncoderDestroy(encoder);
984 status = JxlEncoderSetICCProfile(encoder,
reinterpret_cast<const uint8_t *
>(cmyk_profile.
constData()), cmyk_profile.
size());
985 if (
status != JXL_ENC_SUCCESS) {
986 qWarning(
"JxlEncoderSetICCProfile for CMYK image failed!");
988 JxlThreadParallelRunnerDestroy(runner);
990 JxlEncoderDestroy(encoder);
994 if (!exif_data.isEmpty()) {
996 const char *box_type =
"Exif";
997 status = JxlEncoderAddBox(encoder, box_type,
reinterpret_cast<const uint8_t *
>(exif_data.constData()), exif_data.size(), JXL_FALSE);
998 if (
status != JXL_ENC_SUCCESS) {
999 qWarning(
"JxlEncoderAddBox failed!");
1001 JxlThreadParallelRunnerDestroy(runner);
1003 JxlEncoderDestroy(encoder);
1008 if (!xmp_data.isEmpty()) {
1009 const char *box_type =
"xml ";
1010 status = JxlEncoderAddBox(encoder, box_type,
reinterpret_cast<const uint8_t *
>(xmp_data.constData()), xmp_data.size(), JXL_FALSE);
1011 if (
status != JXL_ENC_SUCCESS) {
1012 qWarning(
"JxlEncoderAddBox failed!");
1014 JxlThreadParallelRunnerDestroy(runner);
1016 JxlEncoderDestroy(encoder);
1020 JxlEncoderCloseBoxes(encoder);
1022 const size_t extra_buffer_size = size_t(image.
width()) * size_t(image.
height());
1023 const size_t cmy_buffer_size = extra_buffer_size * 3;
1025 uchar *pixels_cmy =
nullptr;
1026 uchar *pixels_black =
nullptr;
1028 pixels_cmy =
reinterpret_cast<uchar *
>(malloc(cmy_buffer_size));
1030 qWarning(
"Memory cannot be allocated for CMY buffer");
1032 JxlThreadParallelRunnerDestroy(runner);
1034 JxlEncoderDestroy(encoder);
1038 pixels_black =
reinterpret_cast<uchar *
>(malloc(extra_buffer_size));
1039 if (!pixels_black) {
1040 qWarning(
"Memory cannot be allocated for BLACK buffer");
1042 pixels_cmy =
nullptr;
1045 JxlThreadParallelRunnerDestroy(runner);
1047 JxlEncoderDestroy(encoder);
1051 uchar *dest_CMY = pixels_cmy;
1052 uchar *dest_K = pixels_black;
1053 for (
int y = 0; y < image.
height(); y++) {
1055 for (
int x = 0; x < image.
width(); x++) {
1056 *dest_CMY = 255 - *src_CMYK;
1059 *dest_CMY = 255 - *src_CMYK;
1062 *dest_CMY = 255 - *src_CMYK;
1065 *dest_K = 255 - *src_CMYK;
1071 JxlEncoderFrameSettings *frame_settings_lossless = JxlEncoderFrameSettingsCreate(encoder,
nullptr);
1072 JxlEncoderSetFrameDistance(frame_settings_lossless, 0);
1073 JxlEncoderSetFrameLossless(frame_settings_lossless, JXL_TRUE);
1075 status = JxlEncoderAddImageFrame(frame_settings_lossless, &pixel_format, pixels_cmy, cmy_buffer_size);
1076 if (
status == JXL_ENC_ERROR) {
1077 qWarning(
"JxlEncoderAddImageFrame failed!");
1079 pixels_black =
nullptr;
1081 pixels_cmy =
nullptr;
1083 JxlThreadParallelRunnerDestroy(runner);
1085 JxlEncoderDestroy(encoder);
1089 status = JxlEncoderSetExtraChannelBuffer(frame_settings_lossless, &format_extra, pixels_black, extra_buffer_size, 0);
1092 pixels_black =
nullptr;
1094 pixels_cmy =
nullptr;
1096 if (
status == JXL_ENC_ERROR) {
1097 qWarning(
"JxlEncoderSetExtraChannelBuffer failed!");
1099 JxlThreadParallelRunnerDestroy(runner);
1101 JxlEncoderDestroy(encoder);
1106 JxlThreadParallelRunnerDestroy(runner);
1108 JxlEncoderDestroy(encoder);
1113 bool save_fp =
false;
1114 bool is_gray =
false;
1116 switch (image.
format()) {
1120#ifndef JXL_HDR_PRESERVATION_DISABLED
1128#ifndef JXL_HDR_PRESERVATION_DISABLED
1149#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
1150 case QImage::Format_CMYK8888:
1170 if (image.
depth() > 32) {
1180 if (save_depth > 8 && is_gray) {
1181 pixel_format.data_type = JXL_TYPE_UINT16;
1182 output_info.num_color_channels = 1;
1183 output_info.bits_per_sample = 16;
1185 pixel_format.num_channels = 1;
1186 }
else if (is_gray) {
1187 pixel_format.data_type = JXL_TYPE_UINT8;
1188 output_info.num_color_channels = 1;
1189 output_info.bits_per_sample = 8;
1191 pixel_format.num_channels = 1;
1192 }
else if (save_depth > 16) {
1193 pixel_format.data_type = JXL_TYPE_FLOAT;
1194 output_info.exponent_bits_per_sample = 8;
1195 output_info.num_color_channels = 3;
1196 output_info.bits_per_sample = 32;
1200 pixel_format.num_channels = 4;
1201 output_info.alpha_bits = 32;
1202 output_info.alpha_exponent_bits = 8;
1203 output_info.num_extra_channels = 1;
1206 pixel_format.num_channels = 3;
1207 output_info.alpha_bits = 0;
1208 output_info.num_extra_channels = 0;
1210 }
else if (save_depth > 8) {
1211 pixel_format.data_type = save_fp ? JXL_TYPE_FLOAT16 : JXL_TYPE_UINT16;
1212 output_info.exponent_bits_per_sample = save_fp ? 5 : 0;
1213 output_info.num_color_channels = 3;
1214 output_info.bits_per_sample = 16;
1218 pixel_format.num_channels = 4;
1219 output_info.alpha_bits = 16;
1220 output_info.alpha_exponent_bits = save_fp ? 5 : 0;
1221 output_info.num_extra_channels = 1;
1224 pixel_format.num_channels = 3;
1225 output_info.alpha_bits = 0;
1226 output_info.num_extra_channels = 0;
1229 pixel_format.data_type = JXL_TYPE_UINT8;
1230 output_info.num_color_channels = 3;
1231 output_info.bits_per_sample = 8;
1235 pixel_format.num_channels = 4;
1236 output_info.alpha_bits = 8;
1237 output_info.num_extra_channels = 1;
1240 pixel_format.num_channels = 3;
1241 output_info.alpha_bits = 0;
1242 output_info.num_extra_channels = 0;
1246#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
1249 if (is_gray && image.
colorSpace().colorModel() != QColorSpace::ColorModel::Gray) {
1252 if (gray_whitePoint.
isNull()) {
1253 gray_whitePoint =
QPointF(0.3127f, 0.329f);
1258 if (gray_trc == QColorSpace::TransferFunction::Custom) {
1259 gray_trc = QColorSpace::TransferFunction::SRgb;
1262 const QColorSpace gray_profile(gray_whitePoint, gray_trc, gamma_gray);
1263 if (gray_profile.isValid()) {
1266 qWarning(
"JXL plugin created invalid grayscale QColorSpace!");
1269 }
else if (!is_gray && image.
colorSpace().colorModel() != QColorSpace::ColorModel::Rgb) {
1272 if (whitePoint.
isNull()) {
1273 whitePoint =
QPointF(0.3127f, 0.329f);
1276 const QPointF redP(0.64f, 0.33f);
1277 const QPointF greenP(0.3f, 0.6f);
1278 const QPointF blueP(0.15f, 0.06f);
1282 if (trc_rgb == QColorSpace::TransferFunction::Custom) {
1283 trc_rgb = QColorSpace::TransferFunction::SRgb;
1286 const QColorSpace rgb_profile(whitePoint, redP, greenP, blueP, trc_rgb, gamma_rgb);
1287 if (rgb_profile.isValid()) {
1290 qWarning(
"JXL plugin created invalid RGB QColorSpace!");
1303 output_info.xsize = tmpimage.
width();
1304 output_info.ysize = tmpimage.
height();
1306 if (output_info.xsize == 0 || output_info.ysize == 0 || tmpimage.
isNull()) {
1307 qWarning(
"Unable to allocate memory for output image");
1309 JxlThreadParallelRunnerDestroy(runner);
1311 JxlEncoderDestroy(encoder);
1315 JxlColorEncoding color_profile;
1316 JxlColorEncodingSetToSRGB(&color_profile, is_gray ? JXL_TRUE : JXL_FALSE);
1320 if (m_quality == 100) {
1321 output_info.uses_original_profile = JXL_TRUE;
1324 output_info.uses_original_profile = JXL_FALSE;
1327 QPointF whiteP(0.3127f, 0.329f);
1328#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
1333 case QColorSpace::Primaries::SRgb:
1334 color_profile.white_point = JXL_WHITE_POINT_D65;
1335 color_profile.primaries = JXL_PRIMARIES_SRGB;
1337 case QColorSpace::Primaries::AdobeRgb:
1338 color_profile.white_point = JXL_WHITE_POINT_D65;
1339 color_profile.primaries = JXL_PRIMARIES_CUSTOM;
1340 color_profile.primaries_red_xy[0] = 0.640;
1341 color_profile.primaries_red_xy[1] = 0.330;
1342 color_profile.primaries_green_xy[0] = 0.210;
1343 color_profile.primaries_green_xy[1] = 0.710;
1344 color_profile.primaries_blue_xy[0] = 0.150;
1345 color_profile.primaries_blue_xy[1] = 0.060;
1347 case QColorSpace::Primaries::DciP3D65:
1348 color_profile.white_point = JXL_WHITE_POINT_D65;
1349 color_profile.primaries = JXL_PRIMARIES_P3;
1350 color_profile.primaries_red_xy[0] = 0.680;
1351 color_profile.primaries_red_xy[1] = 0.320;
1352 color_profile.primaries_green_xy[0] = 0.265;
1353 color_profile.primaries_green_xy[1] = 0.690;
1354 color_profile.primaries_blue_xy[0] = 0.150;
1355 color_profile.primaries_blue_xy[1] = 0.060;
1357 case QColorSpace::Primaries::ProPhotoRgb:
1358 color_profile.white_point = JXL_WHITE_POINT_CUSTOM;
1359#if QT_VERSION < QT_VERSION_CHECK(6, 8, 0)
1360 whiteP =
QPointF(0.3457f, 0.3585f);
1362 color_profile.white_point_xy[0] = whiteP.x();
1363 color_profile.white_point_xy[1] = whiteP.y();
1364 color_profile.primaries = JXL_PRIMARIES_CUSTOM;
1365 color_profile.primaries_red_xy[0] = 0.7347;
1366 color_profile.primaries_red_xy[1] = 0.2653;
1367 color_profile.primaries_green_xy[0] = 0.1596;
1368 color_profile.primaries_green_xy[1] = 0.8404;
1369 color_profile.primaries_blue_xy[0] = 0.0366;
1370 color_profile.primaries_blue_xy[1] = 0.0001;
1372#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
1373 case QColorSpace::Primaries::Bt2020:
1374 color_profile.white_point = JXL_WHITE_POINT_D65;
1375 color_profile.primaries = JXL_PRIMARIES_2100;
1376 color_profile.primaries_red_xy[0] = 0.708;
1377 color_profile.primaries_red_xy[1] = 0.292;
1378 color_profile.primaries_green_xy[0] = 0.170;
1379 color_profile.primaries_green_xy[1] = 0.797;
1380 color_profile.primaries_blue_xy[0] = 0.131;
1381 color_profile.primaries_blue_xy[1] = 0.046;
1385 if (is_gray && !whiteP.isNull()) {
1386 color_profile.white_point = JXL_WHITE_POINT_CUSTOM;
1387 color_profile.white_point_xy[0] = whiteP.x();
1388 color_profile.white_point_xy[1] = whiteP.y();
1399 case QColorSpace::TransferFunction::Linear:
1400 color_profile.transfer_function = JXL_TRANSFER_FUNCTION_LINEAR;
1402 case QColorSpace::TransferFunction::Gamma:
1403 if (gamma_profile > 0) {
1404 color_profile.transfer_function = JXL_TRANSFER_FUNCTION_GAMMA;
1405 color_profile.gamma = 1.0 / gamma_profile;
1410 case QColorSpace::TransferFunction::SRgb:
1411 color_profile.transfer_function = JXL_TRANSFER_FUNCTION_SRGB;
1421 status = JxlEncoderSetBasicInfo(encoder, &output_info);
1422 if (
status != JXL_ENC_SUCCESS) {
1423 qWarning(
"JxlEncoderSetBasicInfo failed!");
1425 JxlThreadParallelRunnerDestroy(runner);
1427 JxlEncoderDestroy(encoder);
1431 if (iccprofile.
size() > 0) {
1432 status = JxlEncoderSetICCProfile(encoder,
reinterpret_cast<const uint8_t *
>(iccprofile.
constData()), iccprofile.
size());
1433 if (
status != JXL_ENC_SUCCESS) {
1434 qWarning(
"JxlEncoderSetICCProfile failed!");
1436 JxlThreadParallelRunnerDestroy(runner);
1438 JxlEncoderDestroy(encoder);
1442 status = JxlEncoderSetColorEncoding(encoder, &color_profile);
1443 if (
status != JXL_ENC_SUCCESS) {
1444 qWarning(
"JxlEncoderSetColorEncoding failed!");
1446 JxlThreadParallelRunnerDestroy(runner);
1448 JxlEncoderDestroy(encoder);
1453 if (!exif_data.isEmpty()) {
1455 const char *box_type =
"Exif";
1456 status = JxlEncoderAddBox(encoder, box_type,
reinterpret_cast<const uint8_t *
>(exif_data.constData()), exif_data.size(), JXL_FALSE);
1457 if (
status != JXL_ENC_SUCCESS) {
1458 qWarning(
"JxlEncoderAddBox failed!");
1460 JxlThreadParallelRunnerDestroy(runner);
1462 JxlEncoderDestroy(encoder);
1467 if (!xmp_data.isEmpty()) {
1468 const char *box_type =
"xml ";
1469 status = JxlEncoderAddBox(encoder, box_type,
reinterpret_cast<const uint8_t *
>(xmp_data.constData()), xmp_data.size(), JXL_FALSE);
1470 if (
status != JXL_ENC_SUCCESS) {
1471 qWarning(
"JxlEncoderAddBox failed!");
1473 JxlThreadParallelRunnerDestroy(runner);
1475 JxlEncoderDestroy(encoder);
1479 JxlEncoderCloseBoxes(encoder);
1481 JxlEncoderFrameSettings *encoder_options = JxlEncoderFrameSettingsCreate(encoder,
nullptr);
1483 if (m_quality == 100) {
1484 JxlEncoderSetFrameDistance(encoder_options, 0.0f);
1485 JxlEncoderSetFrameLossless(encoder_options, JXL_TRUE);
1487 JxlEncoderSetFrameDistance(encoder_options, JxlEncoderDistanceFromQuality(m_quality));
1488 JxlEncoderSetFrameLossless(encoder_options, JXL_FALSE);
1493 buffer_size = 12 * size_t(tmpimage.
width()) * size_t(tmpimage.
height());
1495 float *packed_pixels32 =
reinterpret_cast<float *
>(malloc(buffer_size));
1496 if (!packed_pixels32) {
1497 qWarning(
"ERROR: JXL plug-in failed to allocate memory");
1501 float *dest_pixels32 = packed_pixels32;
1502 for (
int y = 0; y < tmpimage.
height(); y++) {
1503 const float *src_pixels32 =
reinterpret_cast<const float *
>(tmpimage.
constScanLine(y));
1504 for (
int x = 0; x < tmpimage.
width(); x++) {
1505 *dest_pixels32 = *src_pixels32;
1508 *dest_pixels32 = *src_pixels32;
1511 *dest_pixels32 = *src_pixels32;
1517 status = JxlEncoderAddImageFrame(encoder_options, &pixel_format, packed_pixels32, buffer_size);
1518 free(packed_pixels32);
1521 buffer_size = 6 * size_t(tmpimage.
width()) * size_t(tmpimage.
height());
1523 quint16 *packed_pixels16 =
reinterpret_cast<quint16 *
>(malloc(buffer_size));
1524 if (!packed_pixels16) {
1525 qWarning(
"ERROR: JXL plug-in failed to allocate memory");
1529 quint16 *dest_pixels16 = packed_pixels16;
1530 for (
int y = 0; y < tmpimage.
height(); y++) {
1531 const quint16 *src_pixels16 =
reinterpret_cast<const quint16 *
>(tmpimage.
constScanLine(y));
1532 for (
int x = 0; x < tmpimage.
width(); x++) {
1533 *dest_pixels16 = *src_pixels16;
1536 *dest_pixels16 = *src_pixels16;
1539 *dest_pixels16 = *src_pixels16;
1545 status = JxlEncoderAddImageFrame(encoder_options, &pixel_format, packed_pixels16, buffer_size);
1546 free(packed_pixels16);
1551 switch (pixel_format.data_type) {
1552 case JXL_TYPE_FLOAT:
1553 buffer_size += 4 * size_t(pixel_format.num_channels) * size_t(tmpimage.
width());
1555 case JXL_TYPE_UINT8:
1556 buffer_size += size_t(pixel_format.num_channels) * size_t(tmpimage.
width());
1558 case JXL_TYPE_UINT16:
1559 case JXL_TYPE_FLOAT16:
1560 buffer_size += 2 * size_t(pixel_format.num_channels) * size_t(tmpimage.
width());
1563 qWarning(
"ERROR: unsupported data type");
1568 status = JxlEncoderAddImageFrame(encoder_options, &pixel_format, tmpimage.
constBits(), buffer_size);
1571 if (
status == JXL_ENC_ERROR) {
1572 qWarning(
"JxlEncoderAddImageFrame failed!");
1574 JxlThreadParallelRunnerDestroy(runner);
1576 JxlEncoderDestroy(encoder);
1581 JxlEncoderCloseFrames(encoder);
1583 std::vector<uint8_t> compressed;
1584 compressed.resize(4096);
1589 next_out = compressed.data() + offset;
1590 avail_out = compressed.size() - offset;
1591 status = JxlEncoderProcessOutput(encoder, &next_out, &avail_out);
1593 if (
status == JXL_ENC_NEED_MORE_OUTPUT) {
1594 offset = next_out - compressed.data();
1595 compressed.resize(compressed.size() * 2);
1596 }
else if (
status == JXL_ENC_ERROR) {
1597 qWarning(
"JxlEncoderProcessOutput failed!");
1599 JxlThreadParallelRunnerDestroy(runner);
1601 JxlEncoderDestroy(encoder);
1604 }
while (
status != JXL_ENC_SUCCESS);
1607 JxlThreadParallelRunnerDestroy(runner);
1609 JxlEncoderDestroy(encoder);
1611 compressed.resize(next_out - compressed.data());
1613 if (compressed.size() > 0) {
1614 qint64 write_status = device()->
write(
reinterpret_cast<const char *
>(compressed.data()), compressed.size());
1616 if (write_status > 0) {
1618 }
else if (write_status == -1) {
1619 qWarning(
"Write error: %s\n", qUtf8Printable(device()->errorString()));
1626QVariant QJpegXLHandler::option(ImageOption option)
const
1628 if (!supportsOption(option)) {
1632 if (option == Quality) {
1636 if (!ensureParsed()) {
1637#ifdef JXL_QT_AUTOTRANSFORM
1638 if (option == ImageTransformation) {
1639 return int(m_transformations);
1647 return QSize(m_basicinfo.xsize, m_basicinfo.ysize);
1649 if (m_basicinfo.have_animation) {
1654#ifdef JXL_QT_AUTOTRANSFORM
1655 case ImageTransformation:
1656 if (m_basicinfo.orientation == JXL_ORIENT_IDENTITY) {
1658 }
else if (m_basicinfo.orientation == JXL_ORIENT_FLIP_HORIZONTAL) {
1660 }
else if (m_basicinfo.orientation == JXL_ORIENT_ROTATE_180) {
1662 }
else if (m_basicinfo.orientation == JXL_ORIENT_FLIP_VERTICAL) {
1664 }
else if (m_basicinfo.orientation == JXL_ORIENT_TRANSPOSE) {
1666 }
else if (m_basicinfo.orientation == JXL_ORIENT_ROTATE_90_CW) {
1668 }
else if (m_basicinfo.orientation == JXL_ORIENT_ANTI_TRANSPOSE) {
1670 }
else if (m_basicinfo.orientation == JXL_ORIENT_ROTATE_90_CCW) {
1682void QJpegXLHandler::setOption(ImageOption option,
const QVariant &value)
1686 m_quality = value.
toInt();
1687 if (m_quality > 100) {
1689 }
else if (m_quality < 0) {
1693#ifdef JXL_QT_AUTOTRANSFORM
1694 case ImageTransformation:
1695 if (
auto t = value.
toInt()) {
1707bool QJpegXLHandler::supportsOption(ImageOption option)
const
1709 auto supported = option == Quality || option == Size || option ==
Animation;
1710#ifdef JXL_QT_AUTOTRANSFORM
1711 supported = supported || option == ImageTransformation;
1716int QJpegXLHandler::imageCount()
const
1718 if (!ensureParsed()) {
1722 if (m_parseState == ParseJpegXLBasicInfoParsed) {
1723 if (!m_basicinfo.have_animation) {
1727 if (!ensureALLCounted()) {
1732 if (!m_framedelays.isEmpty()) {
1733 return m_framedelays.count();
1738int QJpegXLHandler::currentImageNumber()
const
1740 if (m_parseState == ParseJpegXLNotParsed) {
1744 if (m_parseState == ParseJpegXLError || m_parseState == ParseJpegXLBasicInfoParsed || !m_decoder) {
1748 return m_currentimage_index;
1751bool QJpegXLHandler::jumpToNextImage()
1753 if (!ensureALLCounted()) {
1757 if (m_framedelays.count() > 1) {
1758 m_currentimage_index++;
1760 if (m_currentimage_index >= m_framedelays.count()) {
1765 JxlDecoderSkipFrames(m_decoder, 1);
1769 m_parseState = ParseJpegXLSuccess;
1773bool QJpegXLHandler::jumpToImage(
int imageNumber)
1775 if (!ensureALLCounted()) {
1779 if (imageNumber < 0 || imageNumber >= m_framedelays.count()) {
1783 if (imageNumber == m_currentimage_index) {
1784 m_parseState = ParseJpegXLSuccess;
1788 if (imageNumber > m_currentimage_index) {
1789 JxlDecoderSkipFrames(m_decoder, imageNumber - m_currentimage_index);
1790 m_currentimage_index = imageNumber;
1791 m_parseState = ParseJpegXLSuccess;
1799 if (imageNumber > 0) {
1800 JxlDecoderSkipFrames(m_decoder, imageNumber);
1802 m_currentimage_index = imageNumber;
1803 m_parseState = ParseJpegXLSuccess;
1807int QJpegXLHandler::nextImageDelay()
const
1809 if (!ensureALLCounted()) {
1813 if (m_framedelays.count() < 2) {
1817 return m_next_image_delay;
1820int QJpegXLHandler::loopCount()
const
1822 if (!ensureParsed()) {
1826 if (m_basicinfo.have_animation) {
1827 return (m_basicinfo.animation.num_loops > 0) ? m_basicinfo.animation.num_loops - 1 : -1;
1833bool QJpegXLHandler::rewind()
1835 m_currentimage_index = 0;
1837 JxlDecoderReleaseInput(m_decoder);
1838 JxlDecoderRewind(m_decoder);
1840 if (JxlDecoderSetParallelRunner(m_decoder, JxlThreadParallelRunner, m_runner) != JXL_DEC_SUCCESS) {
1841 qWarning(
"ERROR: JxlDecoderSetParallelRunner failed");
1842 m_parseState = ParseJpegXLError;
1847 if (JxlDecoderSetInput(m_decoder,
reinterpret_cast<const uint8_t *
>(m_rawData.constData()), m_rawData.size()) != JXL_DEC_SUCCESS) {
1848 qWarning(
"ERROR: JxlDecoderSetInput failed");
1849 m_parseState = ParseJpegXLError;
1853 JxlDecoderCloseInput(m_decoder);
1855 if (m_basicinfo.uses_original_profile == JXL_FALSE && m_basicinfo.have_animation == JXL_FALSE) {
1856 if (JxlDecoderSubscribeEvents(m_decoder, JXL_DEC_COLOR_ENCODING | JXL_DEC_FULL_IMAGE) != JXL_DEC_SUCCESS) {
1857 qWarning(
"ERROR: JxlDecoderSubscribeEvents failed");
1858 m_parseState = ParseJpegXLError;
1862 JxlDecoderStatus
status = JxlDecoderProcessInput(m_decoder);
1863 if (
status != JXL_DEC_COLOR_ENCODING) {
1864 qWarning(
"Unexpected event %d instead of JXL_DEC_COLOR_ENCODING",
status);
1865 m_parseState = ParseJpegXLError;
1869 const JxlCmsInterface *jxlcms = JxlGetDefaultCms();
1871 status = JxlDecoderSetCms(m_decoder, *jxlcms);
1872 if (
status != JXL_DEC_SUCCESS) {
1873 qWarning(
"JxlDecoderSetCms ERROR");
1876 qWarning(
"No JPEG XL CMS Interface");
1879 bool is_gray = m_basicinfo.num_color_channels == 1 && m_basicinfo.alpha_bits == 0;
1880 JxlColorEncoding color_encoding;
1881 JxlColorEncodingSetToSRGB(&color_encoding, is_gray ? JXL_TRUE : JXL_FALSE);
1882 JxlDecoderSetPreferredColorProfile(m_decoder, &color_encoding);
1884 if (JxlDecoderSubscribeEvents(m_decoder, JXL_DEC_FULL_IMAGE) != JXL_DEC_SUCCESS) {
1885 qWarning(
"ERROR: JxlDecoderSubscribeEvents failed");
1886 m_parseState = ParseJpegXLError;
1894bool QJpegXLHandler::decodeContainer()
1896#if JPEGXL_NUMERIC_VERSION >= JPEGXL_COMPUTE_NUMERIC_VERSION(0, 11, 0)
1897 if (m_basicinfo.have_container == JXL_FALSE) {
1901 const size_t len = m_rawData.size();
1903 m_parseState = ParseJpegXLError;
1907 const uint8_t *buf =
reinterpret_cast<const uint8_t *
>(m_rawData.constData());
1908 if (JxlSignatureCheck(buf, len) != JXL_SIG_CONTAINER) {
1912 JxlDecoderReleaseInput(m_decoder);
1913 JxlDecoderRewind(m_decoder);
1915 if (JxlDecoderSetInput(m_decoder, buf, len) != JXL_DEC_SUCCESS) {
1916 qWarning(
"ERROR: JxlDecoderSetInput failed");
1917 m_parseState = ParseJpegXLError;
1921 JxlDecoderCloseInput(m_decoder);
1923 if (JxlDecoderSetDecompressBoxes(m_decoder, JXL_TRUE) != JXL_DEC_SUCCESS) {
1924 qWarning(
"WARNING: JxlDecoderSetDecompressBoxes failed");
1927 if (JxlDecoderSubscribeEvents(m_decoder, JXL_DEC_BOX | JXL_DEC_BOX_COMPLETE) != JXL_DEC_SUCCESS) {
1928 qWarning(
"ERROR: JxlDecoderSubscribeEvents failed");
1929 m_parseState = ParseJpegXLError;
1933 bool search_exif =
true;
1934 bool search_xmp =
true;
1935 JxlBoxType box_type;
1940 while (search_exif || search_xmp) {
1941 JxlDecoderStatus
status = JxlDecoderProcessInput(m_decoder);
1943 case JXL_DEC_SUCCESS:
1944 search_exif =
false;
1948 status = JxlDecoderGetBoxType(m_decoder, box_type, JXL_TRUE);
1949 if (
status != JXL_DEC_SUCCESS) {
1950 qWarning(
"Error in JxlDecoderGetBoxType");
1951 m_parseState = ParseJpegXLError;
1955 if (box_type[0] ==
'E' && box_type[1] ==
'x' && box_type[2] ==
'i' && box_type[3] ==
'f' && search_exif) {
1956 search_exif =
false;
1957 if (!extractBox(exifBox, len)) {
1960 }
else if (box_type[0] ==
'x' && box_type[1] ==
'm' && box_type[2] ==
'l' && box_type[3] ==
' ' && search_xmp) {
1962 if (!extractBox(xmpBox, len)) {
1968 qWarning(
"JXL Metadata decoding error");
1969 m_parseState = ParseJpegXLError;
1972 case JXL_DEC_NEED_MORE_INPUT:
1973 qWarning(
"JXL metadata are probably incomplete");
1974 m_parseState = ParseJpegXLError;
1978 qWarning(
"Unexpected event %d instead of JXL_DEC_BOX",
status);
1979 m_parseState = ParseJpegXLError;
1985 if (xmpBox.
size() > 0) {
1989 if (exifBox.
size() > 4) {
1990 const char tiffHeaderBE[4] = {
'M',
'M', 0, 42};
1991 const char tiffHeaderLE[4] = {
'I',
'I', 42, 0};
1994 auto headerindexBE = exifBox.
indexOf(tiffBE);
1995 auto headerindexLE = exifBox.
indexOf(tiffLE);
1997 if (headerindexLE != -1) {
1998 if (headerindexBE == -1) {
1999 m_exif = exifBox.
mid(headerindexLE);
2001 m_exif = exifBox.
mid((headerindexLE <= headerindexBE) ? headerindexLE : headerindexBE);
2003 }
else if (headerindexBE != -1) {
2004 m_exif = exifBox.
mid(headerindexBE);
2006 qWarning(
"Exif box in JXL file doesn't have TIFF header");
2013bool QJpegXLHandler::extractBox(
QByteArray &output,
size_t container_size)
2015#if JPEGXL_NUMERIC_VERSION >= JPEGXL_COMPUTE_NUMERIC_VERSION(0, 11, 0)
2016 uint64_t rawboxsize = 0;
2017 JxlDecoderStatus
status = JxlDecoderGetBoxSizeRaw(m_decoder, &rawboxsize);
2018 if (
status != JXL_DEC_SUCCESS) {
2019 qWarning(
"ERROR: JxlDecoderGetBoxSizeRaw failed");
2020 m_parseState = ParseJpegXLError;
2024 if (rawboxsize > container_size) {
2025 qWarning(
"JXL metadata box is incomplete");
2026 m_parseState = ParseJpegXLError;
2030 output.
resize(rawboxsize);
2031 status = JxlDecoderSetBoxBuffer(m_decoder,
reinterpret_cast<uint8_t *
>(output.
data()), output.
size());
2032 if (
status != JXL_DEC_SUCCESS) {
2033 qWarning(
"ERROR: JxlDecoderSetBoxBuffer failed");
2034 m_parseState = ParseJpegXLError;
2039 status = JxlDecoderProcessInput(m_decoder);
2040 if (
status == JXL_DEC_BOX_NEED_MORE_OUTPUT) {
2041 size_t bytes_remains = JxlDecoderReleaseBoxBuffer(m_decoder);
2043 if (output.
size() > 4194304) {
2044 qWarning(
"JXL metadata box is too large");
2045 m_parseState = ParseJpegXLError;
2049 output.
append(16384,
'\0');
2050 size_t extension_size = 16384 + bytes_remains;
2051 uint8_t *extension_buffer =
reinterpret_cast<uint8_t *
>(output.
data()) + (output.
size() - extension_size);
2053 if (JxlDecoderSetBoxBuffer(m_decoder, extension_buffer, extension_size) != JXL_DEC_SUCCESS) {
2054 qWarning(
"ERROR: JxlDecoderSetBoxBuffer failed after JXL_DEC_BOX_NEED_MORE_OUTPUT");
2055 m_parseState = ParseJpegXLError;
2059 }
while (
status == JXL_DEC_BOX_NEED_MORE_OUTPUT);
2061 if (
status != JXL_DEC_BOX_COMPLETE) {
2062 qWarning(
"Unexpected event %d instead of JXL_DEC_BOX_COMPLETE",
status);
2063 m_parseState = ParseJpegXLError;
2067 size_t unused_bytes = JxlDecoderReleaseBoxBuffer(m_decoder);
2068 output.
chop(unused_bytes);
2075 if (format ==
"jxl") {
2087 if (device->
isReadable() && QJpegXLHandler::canRead(device)) {
2106#include "moc_jxl_p.cpp"
Q_SCRIPTABLE CaptureState status()
QFlags< Capability > Capabilities
QByteArray & append(QByteArrayView data)
const char * constData() const const
QByteArray fromHex(const QByteArray &hexEncoded)
QByteArray fromRawData(const char *data, qsizetype size)
qsizetype indexOf(QByteArrayView bv, qsizetype from) const const
bool isEmpty() const const
QByteArray mid(qsizetype pos, qsizetype len) const const
void resize(qsizetype newSize, char c)
qsizetype size() const const
QColorSpace fromIccProfile(const QByteArray &iccProfile)
float gamma() const const
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
void setColorSpace(const QColorSpace &colorSpace)
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)
bool isNull() const const
QString fromUtf8(QByteArrayView str)
QByteArray toUtf8() const const
int toInt(bool *ok) const const