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 indate 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) {
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);
795 if (exif.horizontalResolution() > 0)
796 m_current_image.setDotsPerMeterX(qRound(exif.horizontalResolution() / 25.4 * 1000));
797 if (exif.verticalResolution() > 0)
798 m_current_image.setDotsPerMeterY(qRound(exif.verticalResolution() / 25.4 * 1000));
800 exif.toImageMetadata(m_current_image);
803 m_next_image_delay = m_framedelays[m_currentimage_index];
804 m_previousimage_index = m_currentimage_index;
806 if (m_framedelays.count() > 1) {
807 m_currentimage_index++;
809 if (m_currentimage_index >= m_framedelays.count()) {
815 m_parseState = ParseJpegXLFinished;
817 m_parseState = ParseJpegXLSuccess;
821 m_parseState = ParseJpegXLFinished;
827bool QJpegXLHandler::read(
QImage *image)
829 if (!ensureALLCounted()) {
833 if (m_currentimage_index == m_previousimage_index) {
834 *image = m_current_image;
835 return jumpToNextImage();
838 if (decode_one_frame()) {
839 *image = m_current_image;
847void packRGBPixels(
QImage &img)
850 auto dest_pixels =
reinterpret_cast<T *
>(img.
bits());
851 for (qint32 y = 0; y < img.
height(); y++) {
852 auto src_pixels =
reinterpret_cast<const T *
>(img.
constScanLine(y));
853 for (qint32 x = 0; x < img.
width(); x++) {
855 *dest_pixels = *src_pixels;
859 *dest_pixels = *src_pixels;
863 *dest_pixels = *src_pixels;
870bool QJpegXLHandler::write(
const QImage &image)
873 qWarning(
"No image data to save");
877 if ((image.
width() == 0) || (image.
height() == 0)) {
878 qWarning(
"Image has zero dimension!");
882 if ((image.
width() > MAX_IMAGE_WIDTH) || (image.
height() > MAX_IMAGE_HEIGHT)) {
883 qWarning(
"Image (%dx%d) is too large to save!", image.
width(), image.
height());
887 size_t pixel_count = size_t(image.
width()) * image.
height();
888 if (MAX_IMAGE_PIXELS && pixel_count > MAX_IMAGE_PIXELS) {
889 qWarning(
"Image (%dx%d) will not be saved because it has more than %d megapixels!", image.
width(), image.
height(), MAX_IMAGE_PIXELS / 1024 / 1024);
894 bool save_fp =
false;
895 bool is_gray =
false;
901#ifndef JXL_HDR_PRESERVATION_DISABLED
909#ifndef JXL_HDR_PRESERVATION_DISABLED
930#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
931 case QImage::Format_CMYK8888:
951 if (image.
depth() > 32) {
959 JxlEncoder *encoder = JxlEncoderCreate(
nullptr);
961 qWarning(
"Failed to create Jxl encoder");
965 if (m_quality > 100) {
967 }
else if (m_quality < 0) {
971 JxlEncoderUseContainer(encoder, JXL_TRUE);
972 JxlEncoderUseBoxes(encoder);
974 JxlBasicInfo output_info;
975 JxlEncoderInitBasicInfo(&output_info);
976 output_info.have_container = JXL_TRUE;
981 || m_quality == 100) {
985 if (iccprofile.
size() > 0 || m_quality == 100 || is_gray) {
986 output_info.uses_original_profile = JXL_TRUE;
991 if ( (save_depth > 8 && (image.
hasAlphaChannel() || output_info.uses_original_profile))
993 || (pixel_count > FEATURE_LEVEL_5_PIXELS)
994 || (image.
width() > FEATURE_LEVEL_5_WIDTH)
995 || (image.
height() > FEATURE_LEVEL_5_HEIGHT)) {
996 JxlEncoderSetCodestreamLevel(encoder, 10);
1000 void *runner =
nullptr;
1003 if (num_worker_threads > 1) {
1004 runner = JxlThreadParallelRunnerCreate(
nullptr, num_worker_threads);
1005 if (JxlEncoderSetParallelRunner(encoder, JxlThreadParallelRunner, runner) != JXL_ENC_SUCCESS) {
1006 qWarning(
"JxlEncoderSetParallelRunner failed");
1007 JxlThreadParallelRunnerDestroy(runner);
1008 JxlEncoderDestroy(encoder);
1013 JxlPixelFormat pixel_format;
1017 pixel_format.endianness = JXL_NATIVE_ENDIAN;
1018 pixel_format.align = 0;
1020 output_info.animation.tps_numerator = 10;
1021 output_info.animation.tps_denominator = 1;
1022 output_info.orientation = JXL_ORIENT_IDENTITY;
1024 output_info.orientation = JXL_ORIENT_FLIP_HORIZONTAL;
1026 output_info.orientation = JXL_ORIENT_ROTATE_180;
1028 output_info.orientation = JXL_ORIENT_FLIP_VERTICAL;
1030 output_info.orientation = JXL_ORIENT_TRANSPOSE;
1032 output_info.orientation = JXL_ORIENT_ROTATE_90_CW;
1034 output_info.orientation = JXL_ORIENT_ANTI_TRANSPOSE;
1036 output_info.orientation = JXL_ORIENT_ROTATE_90_CCW;
1039 if (save_depth > 8 && is_gray) {
1040 pixel_format.data_type = JXL_TYPE_UINT16;
1041 pixel_format.align = 4;
1042 output_info.num_color_channels = 1;
1043 output_info.bits_per_sample = 16;
1045 pixel_format.num_channels = 1;
1046 }
else if (is_gray) {
1047 pixel_format.data_type = JXL_TYPE_UINT8;
1048 pixel_format.align = 4;
1049 output_info.num_color_channels = 1;
1050 output_info.bits_per_sample = 8;
1052 pixel_format.num_channels = 1;
1053 }
else if (save_depth > 16) {
1054 pixel_format.data_type = JXL_TYPE_FLOAT;
1055 output_info.exponent_bits_per_sample = 8;
1056 output_info.num_color_channels = 3;
1057 output_info.bits_per_sample = 32;
1061 pixel_format.num_channels = 4;
1062 output_info.alpha_bits = 32;
1063 output_info.alpha_exponent_bits = 8;
1064 output_info.num_extra_channels = 1;
1067 pixel_format.num_channels = 3;
1068 output_info.alpha_bits = 0;
1069 output_info.num_extra_channels = 0;
1071 }
else if (save_depth > 8) {
1072 pixel_format.data_type = save_fp ? JXL_TYPE_FLOAT16 : JXL_TYPE_UINT16;
1073 output_info.exponent_bits_per_sample = save_fp ? 5 : 0;
1074 output_info.num_color_channels = 3;
1075 output_info.bits_per_sample = 16;
1079 pixel_format.num_channels = 4;
1080 output_info.alpha_bits = 16;
1081 output_info.alpha_exponent_bits = save_fp ? 5 : 0;
1082 output_info.num_extra_channels = 1;
1085 pixel_format.num_channels = 3;
1086 output_info.alpha_bits = 0;
1087 output_info.num_extra_channels = 0;
1090 pixel_format.data_type = JXL_TYPE_UINT8;
1091 pixel_format.align = 4;
1092 output_info.num_color_channels = 3;
1093 output_info.bits_per_sample = 8;
1097 pixel_format.num_channels = 4;
1098 output_info.alpha_bits = 8;
1099 output_info.num_extra_channels = 1;
1102 pixel_format.num_channels = 3;
1103 output_info.alpha_bits = 0;
1104 output_info.num_extra_channels = 0;
1108#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
1112 if (cs.isValid() && cs.colorModel() == QColorSpace::ColorModel::Cmyk && image.
format() == QImage::Format_CMYK8888) {
1122 const size_t xsize = tmpimage.
width();
1123 const size_t ysize = tmpimage.
height();
1125 if (xsize == 0 || ysize == 0 || tmpimage.
isNull()) {
1126 qWarning(
"Unable to allocate memory for output image");
1128 JxlThreadParallelRunnerDestroy(runner);
1130 JxlEncoderDestroy(encoder);
1134 output_info.xsize = tmpimage.
width();
1135 output_info.ysize = tmpimage.
height();
1137 status = JxlEncoderSetBasicInfo(encoder, &output_info);
1138 if (
status != JXL_ENC_SUCCESS) {
1139 qWarning(
"JxlEncoderSetBasicInfo failed!");
1141 JxlThreadParallelRunnerDestroy(runner);
1143 JxlEncoderDestroy(encoder);
1147 auto exif_data = MicroExif::fromImage(image).toByteArray();
1148 if (!exif_data.isEmpty()) {
1150 const char *box_type =
"Exif";
1151 status = JxlEncoderAddBox(encoder, box_type,
reinterpret_cast<const uint8_t *
>(exif_data.constData()), exif_data.size(), JXL_FALSE);
1152 if (
status != JXL_ENC_SUCCESS) {
1153 qWarning(
"JxlEncoderAddBox failed!");
1155 JxlThreadParallelRunnerDestroy(runner);
1157 JxlEncoderDestroy(encoder);
1161 auto xmp_data = image.
text(QStringLiteral(META_KEY_XMP_ADOBE)).
toUtf8();
1162 if (!xmp_data.isEmpty()) {
1163 const char *box_type =
"xml ";
1164 status = JxlEncoderAddBox(encoder, box_type,
reinterpret_cast<const uint8_t *
>(xmp_data.constData()), xmp_data.size(), JXL_FALSE);
1165 if (
status != JXL_ENC_SUCCESS) {
1166 qWarning(
"JxlEncoderAddBox failed!");
1168 JxlThreadParallelRunnerDestroy(runner);
1170 JxlEncoderDestroy(encoder);
1174 JxlEncoderCloseBoxes(encoder);
1176 if (iccprofile.
size() > 0) {
1177 status = JxlEncoderSetICCProfile(encoder,
reinterpret_cast<const uint8_t *
>(iccprofile.
constData()), iccprofile.
size());
1178 if (
status != JXL_ENC_SUCCESS) {
1179 qWarning(
"JxlEncoderSetICCProfile failed!");
1181 JxlThreadParallelRunnerDestroy(runner);
1183 JxlEncoderDestroy(encoder);
1187 JxlColorEncoding color_profile;
1188 JxlColorEncodingSetToSRGB(&color_profile, is_gray ? JXL_TRUE : JXL_FALSE);
1190 status = JxlEncoderSetColorEncoding(encoder, &color_profile);
1191 if (
status != JXL_ENC_SUCCESS) {
1192 qWarning(
"JxlEncoderSetColorEncoding failed!");
1194 JxlThreadParallelRunnerDestroy(runner);
1196 JxlEncoderDestroy(encoder);
1201 JxlEncoderFrameSettings *encoder_options = JxlEncoderFrameSettingsCreate(encoder,
nullptr);
1203 JxlEncoderSetFrameDistance(encoder_options, (100.0f - m_quality) / 10.0f);
1205 JxlEncoderSetFrameLossless(encoder_options, (m_quality == 100) ? JXL_TRUE : JXL_FALSE);
1209 buffer_size = (size_t(save_depth / 8) * pixel_format.num_channels * xsize * ysize);
1214 qWarning(
"Memory allocation error");
1216 JxlThreadParallelRunnerDestroy(runner);
1218 JxlEncoderDestroy(encoder);
1223 if (save_depth > 16 && save_fp)
1224 packRGBPixels<float>(tmpimage);
1226 packRGBPixels<qfloat16>(tmpimage);
1228 packRGBPixels<quint16>(tmpimage);
1230 status = JxlEncoderAddImageFrame(encoder_options, &pixel_format,
static_cast<const void *
>(tmpimage.
constBits()), buffer_size);
1232 if (
status == JXL_ENC_ERROR) {
1233 qWarning(
"JxlEncoderAddImageFrame failed!");
1235 JxlThreadParallelRunnerDestroy(runner);
1237 JxlEncoderDestroy(encoder);
1241 JxlEncoderCloseInput(encoder);
1243 std::vector<uint8_t> compressed;
1244 compressed.resize(4096);
1249 next_out = compressed.data() + offset;
1250 avail_out = compressed.size() - offset;
1251 status = JxlEncoderProcessOutput(encoder, &next_out, &avail_out);
1253 if (
status == JXL_ENC_NEED_MORE_OUTPUT) {
1254 offset = next_out - compressed.data();
1255 compressed.resize(compressed.size() * 2);
1256 }
else if (
status == JXL_ENC_ERROR) {
1257 qWarning(
"JxlEncoderProcessOutput failed!");
1259 JxlThreadParallelRunnerDestroy(runner);
1261 JxlEncoderDestroy(encoder);
1264 }
while (
status != JXL_ENC_SUCCESS);
1267 JxlThreadParallelRunnerDestroy(runner);
1269 JxlEncoderDestroy(encoder);
1271 compressed.resize(next_out - compressed.data());
1273 if (compressed.size() > 0) {
1274 qint64 write_status = device()->
write(
reinterpret_cast<const char *
>(compressed.data()), compressed.size());
1276 if (write_status > 0) {
1278 }
else if (write_status == -1) {
1279 qWarning(
"Write error: %s\n", qUtf8Printable(device()->errorString()));
1286QVariant QJpegXLHandler::option(ImageOption option)
const
1288 if (!supportsOption(option)) {
1292 if (option == Quality) {
1296 if (!ensureParsed()) {
1297#ifdef JXL_QT_AUTOTRANSFORM
1298 if (option == ImageTransformation) {
1299 return int(m_transformations);
1307 return QSize(m_basicinfo.xsize, m_basicinfo.ysize);
1309 if (m_basicinfo.have_animation) {
1314#ifdef JXL_QT_AUTOTRANSFORM
1315 case ImageTransformation:
1316 if (m_basicinfo.orientation == JXL_ORIENT_IDENTITY) {
1318 }
else if (m_basicinfo.orientation == JXL_ORIENT_FLIP_HORIZONTAL) {
1320 }
else if (m_basicinfo.orientation == JXL_ORIENT_ROTATE_180) {
1322 }
else if (m_basicinfo.orientation == JXL_ORIENT_FLIP_VERTICAL) {
1324 }
else if (m_basicinfo.orientation == JXL_ORIENT_TRANSPOSE) {
1326 }
else if (m_basicinfo.orientation == JXL_ORIENT_ROTATE_90_CW) {
1328 }
else if (m_basicinfo.orientation == JXL_ORIENT_ANTI_TRANSPOSE) {
1330 }
else if (m_basicinfo.orientation == JXL_ORIENT_ROTATE_90_CCW) {
1342void QJpegXLHandler::setOption(ImageOption option,
const QVariant &value)
1346 m_quality = value.
toInt();
1347 if (m_quality > 100) {
1349 }
else if (m_quality < 0) {
1353#ifdef JXL_QT_AUTOTRANSFORM
1354 case ImageTransformation:
1355 if (
auto t = value.
toInt()) {
1367bool QJpegXLHandler::supportsOption(ImageOption option)
const
1369 auto supported = option == Quality || option == Size || option ==
Animation;
1370#ifdef JXL_QT_AUTOTRANSFORM
1371 supported = supported || option == ImageTransformation;
1376int QJpegXLHandler::imageCount()
const
1378 if (!ensureParsed()) {
1382 if (m_parseState == ParseJpegXLBasicInfoParsed) {
1383 if (!m_basicinfo.have_animation) {
1387 if (!ensureALLCounted()) {
1392 if (!m_framedelays.isEmpty()) {
1393 return m_framedelays.count();
1398int QJpegXLHandler::currentImageNumber()
const
1400 if (m_parseState == ParseJpegXLNotParsed) {
1404 if (m_parseState == ParseJpegXLError || m_parseState == ParseJpegXLBasicInfoParsed || !m_decoder) {
1408 return m_currentimage_index;
1411bool QJpegXLHandler::jumpToNextImage()
1413 if (!ensureALLCounted()) {
1417 if (m_framedelays.count() > 1) {
1418 m_currentimage_index++;
1420 if (m_currentimage_index >= m_framedelays.count()) {
1425 JxlDecoderSkipFrames(m_decoder, 1);
1429 m_parseState = ParseJpegXLSuccess;
1433bool QJpegXLHandler::jumpToImage(
int imageNumber)
1435 if (!ensureALLCounted()) {
1439 if (imageNumber < 0 || imageNumber >= m_framedelays.count()) {
1443 if (imageNumber == m_currentimage_index) {
1444 m_parseState = ParseJpegXLSuccess;
1448 if (imageNumber > m_currentimage_index) {
1449 JxlDecoderSkipFrames(m_decoder, imageNumber - m_currentimage_index);
1450 m_currentimage_index = imageNumber;
1451 m_parseState = ParseJpegXLSuccess;
1459 if (imageNumber > 0) {
1460 JxlDecoderSkipFrames(m_decoder, imageNumber);
1462 m_currentimage_index = imageNumber;
1463 m_parseState = ParseJpegXLSuccess;
1467int QJpegXLHandler::nextImageDelay()
const
1469 if (!ensureALLCounted()) {
1473 if (m_framedelays.count() < 2) {
1477 return m_next_image_delay;
1480int QJpegXLHandler::loopCount()
const
1482 if (!ensureParsed()) {
1486 if (m_basicinfo.have_animation) {
1487 return (m_basicinfo.animation.num_loops > 0) ? m_basicinfo.animation.num_loops - 1 : -1;
1493bool QJpegXLHandler::rewind()
1495 m_currentimage_index = 0;
1497 JxlDecoderReleaseInput(m_decoder);
1498 JxlDecoderRewind(m_decoder);
1500 if (JxlDecoderSetParallelRunner(m_decoder, JxlThreadParallelRunner, m_runner) != JXL_DEC_SUCCESS) {
1501 qWarning(
"ERROR: JxlDecoderSetParallelRunner failed");
1502 m_parseState = ParseJpegXLError;
1507 if (JxlDecoderSetInput(m_decoder,
reinterpret_cast<const uint8_t *
>(m_rawData.constData()), m_rawData.size()) != JXL_DEC_SUCCESS) {
1508 qWarning(
"ERROR: JxlDecoderSetInput failed");
1509 m_parseState = ParseJpegXLError;
1513 JxlDecoderCloseInput(m_decoder);
1515 if (m_basicinfo.uses_original_profile == JXL_FALSE && m_basicinfo.have_animation == JXL_FALSE) {
1516 if (JxlDecoderSubscribeEvents(m_decoder, JXL_DEC_COLOR_ENCODING | JXL_DEC_FULL_IMAGE) != JXL_DEC_SUCCESS) {
1517 qWarning(
"ERROR: JxlDecoderSubscribeEvents failed");
1518 m_parseState = ParseJpegXLError;
1522 JxlDecoderStatus
status = JxlDecoderProcessInput(m_decoder);
1523 if (
status != JXL_DEC_COLOR_ENCODING) {
1524 qWarning(
"Unexpected event %d instead of JXL_DEC_COLOR_ENCODING",
status);
1525 m_parseState = ParseJpegXLError;
1529 const JxlCmsInterface *jxlcms = JxlGetDefaultCms();
1531 status = JxlDecoderSetCms(m_decoder, *jxlcms);
1532 if (
status != JXL_DEC_SUCCESS) {
1533 qWarning(
"JxlDecoderSetCms ERROR");
1536 qWarning(
"No JPEG XL CMS Interface");
1539 bool is_gray = m_basicinfo.num_color_channels == 1 && m_basicinfo.alpha_bits == 0;
1540 JxlColorEncoding color_encoding;
1541 JxlColorEncodingSetToSRGB(&color_encoding, is_gray ? JXL_TRUE : JXL_FALSE);
1542 JxlDecoderSetPreferredColorProfile(m_decoder, &color_encoding);
1544 if (JxlDecoderSubscribeEvents(m_decoder, JXL_DEC_FULL_IMAGE) != JXL_DEC_SUCCESS) {
1545 qWarning(
"ERROR: JxlDecoderSubscribeEvents failed");
1546 m_parseState = ParseJpegXLError;
1554bool QJpegXLHandler::decodeContainer()
1556#if JPEGXL_NUMERIC_VERSION >= JPEGXL_COMPUTE_NUMERIC_VERSION(0, 11, 0)
1557 if (m_basicinfo.have_container == JXL_FALSE) {
1561 const size_t len = m_rawData.size();
1563 m_parseState = ParseJpegXLError;
1567 const uint8_t *buf =
reinterpret_cast<const uint8_t *
>(m_rawData.constData());
1568 if (JxlSignatureCheck(buf, len) != JXL_SIG_CONTAINER) {
1572 JxlDecoderReleaseInput(m_decoder);
1573 JxlDecoderRewind(m_decoder);
1575 if (JxlDecoderSetInput(m_decoder, buf, len) != JXL_DEC_SUCCESS) {
1576 qWarning(
"ERROR: JxlDecoderSetInput failed");
1577 m_parseState = ParseJpegXLError;
1581 JxlDecoderCloseInput(m_decoder);
1583 if (JxlDecoderSetDecompressBoxes(m_decoder, JXL_TRUE) != JXL_DEC_SUCCESS) {
1584 qWarning(
"WARNING: JxlDecoderSetDecompressBoxes failed");
1587 if (JxlDecoderSubscribeEvents(m_decoder, JXL_DEC_BOX | JXL_DEC_BOX_COMPLETE) != JXL_DEC_SUCCESS) {
1588 qWarning(
"ERROR: JxlDecoderSubscribeEvents failed");
1589 m_parseState = ParseJpegXLError;
1593 bool search_exif =
true;
1594 bool search_xmp =
true;
1595 JxlBoxType box_type;
1600 while (search_exif || search_xmp) {
1601 JxlDecoderStatus
status = JxlDecoderProcessInput(m_decoder);
1603 case JXL_DEC_SUCCESS:
1604 search_exif =
false;
1608 status = JxlDecoderGetBoxType(m_decoder, box_type, JXL_TRUE);
1609 if (
status != JXL_DEC_SUCCESS) {
1610 qWarning(
"Error in JxlDecoderGetBoxType");
1611 m_parseState = ParseJpegXLError;
1615 if (box_type[0] ==
'E' && box_type[1] ==
'x' && box_type[2] ==
'i' && box_type[3] ==
'f' && search_exif) {
1616 search_exif =
false;
1617 if (!extractBox(exifBox, len)) {
1620 }
else if (box_type[0] ==
'x' && box_type[1] ==
'm' && box_type[2] ==
'l' && box_type[3] ==
' ' && search_xmp) {
1622 if (!extractBox(xmpBox, len)) {
1628 qWarning(
"JXL Metadata decoding error");
1629 m_parseState = ParseJpegXLError;
1632 case JXL_DEC_NEED_MORE_INPUT:
1633 qWarning(
"JXL metadata are probably incomplete");
1634 m_parseState = ParseJpegXLError;
1638 qWarning(
"Unexpected event %d instead of JXL_DEC_BOX",
status);
1639 m_parseState = ParseJpegXLError;
1645 if (xmpBox.
size() > 0) {
1649 if (exifBox.
size() > 4) {
1650 const char tiffHeaderBE[4] = {
'M',
'M', 0, 42};
1651 const char tiffHeaderLE[4] = {
'I',
'I', 42, 0};
1654 auto headerindexBE = exifBox.
indexOf(tiffBE);
1655 auto headerindexLE = exifBox.
indexOf(tiffLE);
1657 if (headerindexLE != -1) {
1658 if (headerindexBE == -1) {
1659 m_exif = exifBox.
mid(headerindexLE);
1661 m_exif = exifBox.
mid((headerindexLE <= headerindexBE) ? headerindexLE : headerindexBE);
1663 }
else if (headerindexBE != -1) {
1664 m_exif = exifBox.
mid(headerindexBE);
1666 qWarning(
"Exif box in JXL file doesn't have TIFF header");
1673bool QJpegXLHandler::extractBox(
QByteArray &output,
size_t container_size)
1675#if JPEGXL_NUMERIC_VERSION >= JPEGXL_COMPUTE_NUMERIC_VERSION(0, 11, 0)
1676 uint64_t rawboxsize = 0;
1677 JxlDecoderStatus
status = JxlDecoderGetBoxSizeRaw(m_decoder, &rawboxsize);
1678 if (
status != JXL_DEC_SUCCESS) {
1679 qWarning(
"ERROR: JxlDecoderGetBoxSizeRaw failed");
1680 m_parseState = ParseJpegXLError;
1684 if (rawboxsize > container_size) {
1685 qWarning(
"JXL metadata box is incomplete");
1686 m_parseState = ParseJpegXLError;
1690 output.
resize(rawboxsize);
1691 status = JxlDecoderSetBoxBuffer(m_decoder,
reinterpret_cast<uint8_t *
>(output.
data()), output.
size());
1692 if (
status != JXL_DEC_SUCCESS) {
1693 qWarning(
"ERROR: JxlDecoderSetBoxBuffer failed");
1694 m_parseState = ParseJpegXLError;
1699 status = JxlDecoderProcessInput(m_decoder);
1700 if (
status == JXL_DEC_BOX_NEED_MORE_OUTPUT) {
1701 size_t bytes_remains = JxlDecoderReleaseBoxBuffer(m_decoder);
1703 if (output.
size() > 4194304) {
1704 qWarning(
"JXL metadata box is too large");
1705 m_parseState = ParseJpegXLError;
1709 output.
append(16384,
'\0');
1710 size_t extension_size = 16384 + bytes_remains;
1711 uint8_t *extension_buffer =
reinterpret_cast<uint8_t *
>(output.
data()) + (output.
size() - extension_size);
1713 if (JxlDecoderSetBoxBuffer(m_decoder, extension_buffer, extension_size) != JXL_DEC_SUCCESS) {
1714 qWarning(
"ERROR: JxlDecoderSetBoxBuffer failed after JXL_DEC_BOX_NEED_MORE_OUTPUT");
1715 m_parseState = ParseJpegXLError;
1719 }
while (
status == JXL_DEC_BOX_NEED_MORE_OUTPUT);
1721 if (
status != JXL_DEC_BOX_COMPLETE) {
1722 qWarning(
"Unexpected event %d instead of JXL_DEC_BOX_COMPLETE",
status);
1723 m_parseState = ParseJpegXLError;
1727 size_t unused_bytes = JxlDecoderReleaseBoxBuffer(m_decoder);
1728 output.
chop(unused_bytes);
1735 if (format ==
"jxl") {
1747 if (device->
isReadable() && QJpegXLHandler::canRead(device)) {
1766#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)
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)
QString fromUtf8(QByteArrayView str)
QByteArray toUtf8() const const
int toInt(bool *ok) const const