15#include <jxl/encode.h>
16#include <jxl/thread_parallel_runner.h>
20#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))
21#ifndef JXL_QT_AUTOTRANSFORM
22#define JXL_QT_AUTOTRANSFORM
26#if JPEGXL_NUMERIC_VERSION < JPEGXL_COMPUTE_NUMERIC_VERSION(0, 9, 0)
27#ifndef JXL_HDR_PRESERVATION_DISABLED
30#define JXL_HDR_PRESERVATION_DISABLED
34#ifndef JXL_DECODE_BOXES_DISABLED
40#define FEATURE_LEVEL_5_WIDTH 262144
41#define FEATURE_LEVEL_5_HEIGHT 262144
42#define FEATURE_LEVEL_5_PIXELS 268435456
44#if QT_POINTER_SIZE < 8
45#define MAX_IMAGE_WIDTH 32767
46#define MAX_IMAGE_HEIGHT 32767
47#define MAX_IMAGE_PIXELS FEATURE_LEVEL_5_PIXELS
49#define MAX_IMAGE_WIDTH FEATURE_LEVEL_5_WIDTH
50#define MAX_IMAGE_HEIGHT FEATURE_LEVEL_5_HEIGHT
51#define MAX_IMAGE_PIXELS FEATURE_LEVEL_5_PIXELS
54QJpegXLHandler::QJpegXLHandler()
55 : m_parseState(ParseJpegXLNotParsed)
57 , m_currentimage_index(0)
58 , m_previousimage_index(-1)
62 , m_next_image_delay(0)
63 , m_input_image_format(
QImage::Format_Invalid)
64 , m_target_image_format(
QImage::Format_Invalid)
69QJpegXLHandler::~QJpegXLHandler()
72 JxlThreadParallelRunnerDestroy(m_runner);
75 JxlDecoderDestroy(m_decoder);
79bool QJpegXLHandler::canRead()
const
81 if (m_parseState == ParseJpegXLNotParsed && !canRead(device())) {
85 if (m_parseState != ParseJpegXLError) {
88 if (m_parseState == ParseJpegXLFinished) {
97bool QJpegXLHandler::canRead(
QIODevice *device)
103 if (header.
size() < 12) {
107 JxlSignature signature = JxlSignatureCheck(
reinterpret_cast<const uint8_t *
>(header.
constData()), header.
size());
108 if (signature == JXL_SIG_CODESTREAM || signature == JXL_SIG_CONTAINER) {
114bool QJpegXLHandler::ensureParsed()
const
116 if (m_parseState == ParseJpegXLSuccess || m_parseState == ParseJpegXLBasicInfoParsed || m_parseState == ParseJpegXLFinished) {
119 if (m_parseState == ParseJpegXLError) {
123 QJpegXLHandler *that =
const_cast<QJpegXLHandler *
>(
this);
125 return that->ensureDecoder();
128bool QJpegXLHandler::ensureALLCounted()
const
130 if (!ensureParsed()) {
134 if (m_parseState == ParseJpegXLSuccess || m_parseState == ParseJpegXLFinished) {
138 QJpegXLHandler *that =
const_cast<QJpegXLHandler *
>(
this);
140 return that->countALLFrames();
143bool QJpegXLHandler::ensureDecoder()
149 m_rawData = device()->
readAll();
151 if (m_rawData.isEmpty()) {
155 JxlSignature signature = JxlSignatureCheck(
reinterpret_cast<const uint8_t *
>(m_rawData.constData()), m_rawData.size());
156 if (signature != JXL_SIG_CODESTREAM && signature != JXL_SIG_CONTAINER) {
157 m_parseState = ParseJpegXLError;
161 m_decoder = JxlDecoderCreate(
nullptr);
163 qWarning(
"ERROR: JxlDecoderCreate failed");
164 m_parseState = ParseJpegXLError;
168#ifdef JXL_QT_AUTOTRANSFORM
170 JxlDecoderSetKeepOrientation(m_decoder,
true);
174 if (!m_runner && num_worker_threads >= 4) {
177 num_worker_threads = num_worker_threads / 2;
178 num_worker_threads = qBound(2, num_worker_threads, 64);
179 m_runner = JxlThreadParallelRunnerCreate(
nullptr, num_worker_threads);
181 if (JxlDecoderSetParallelRunner(m_decoder, JxlThreadParallelRunner, m_runner) != JXL_DEC_SUCCESS) {
182 qWarning(
"ERROR: JxlDecoderSetParallelRunner failed");
183 m_parseState = ParseJpegXLError;
188 if (JxlDecoderSetInput(m_decoder,
reinterpret_cast<const uint8_t *
>(m_rawData.constData()), m_rawData.size()) != JXL_DEC_SUCCESS) {
189 qWarning(
"ERROR: JxlDecoderSetInput failed");
190 m_parseState = ParseJpegXLError;
194 JxlDecoderCloseInput(m_decoder);
195#ifndef JXL_DECODE_BOXES_DISABLED
196 JxlDecoderStatus
status = JxlDecoderSubscribeEvents(m_decoder, JXL_DEC_BASIC_INFO | JXL_DEC_COLOR_ENCODING | JXL_DEC_FRAME | JXL_DEC_BOX);
198 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 if (!decodeBoxes(
status)) {
210 status = JxlDecoderGetBasicInfo(m_decoder, &m_basicinfo);
211 if (
status != JXL_DEC_SUCCESS) {
212 qWarning(
"ERROR: JXL basic info not available");
213 m_parseState = ParseJpegXLError;
217 if (m_basicinfo.xsize == 0 || m_basicinfo.ysize == 0) {
218 qWarning(
"ERROR: JXL image has zero dimensions");
219 m_parseState = ParseJpegXLError;
223 if (m_basicinfo.xsize > MAX_IMAGE_WIDTH || m_basicinfo.ysize > MAX_IMAGE_HEIGHT) {
224 qWarning(
"JXL image (%dx%d) is too large", m_basicinfo.xsize, m_basicinfo.ysize);
225 m_parseState = ParseJpegXLError;
229 m_parseState = ParseJpegXLBasicInfoParsed;
233bool QJpegXLHandler::countALLFrames()
235 if (m_parseState != ParseJpegXLBasicInfoParsed) {
240 if (!decodeBoxes(
status)) {
244 if (
status != JXL_DEC_COLOR_ENCODING) {
245 qWarning(
"Unexpected event %d instead of JXL_DEC_COLOR_ENCODING",
status);
246 m_parseState = ParseJpegXLError;
250 bool is_gray = m_basicinfo.num_color_channels == 1 && m_basicinfo.num_extra_channels == 0;
251 JxlColorEncoding color_encoding;
252 if (m_basicinfo.uses_original_profile == JXL_FALSE) {
253 JxlColorEncodingSetToSRGB(&color_encoding, is_gray ? JXL_TRUE : JXL_FALSE);
254 JxlDecoderSetPreferredColorProfile(m_decoder, &color_encoding);
257 bool loadalpha =
false;
258 if (m_basicinfo.alpha_bits > 0) {
262 m_input_pixel_format.endianness = JXL_NATIVE_ENDIAN;
263 m_input_pixel_format.align = 0;
264 m_input_pixel_format.num_channels = is_gray ? 1 : 4;
266 if (m_basicinfo.bits_per_sample > 8) {
267#ifdef JXL_HDR_PRESERVATION_DISABLED
270 bool is_fp = m_basicinfo.exponent_bits_per_sample > 0 && m_basicinfo.num_color_channels == 3;
272 m_input_pixel_format.data_type = is_fp ? JXL_TYPE_FLOAT16 : JXL_TYPE_UINT16;
273 m_buffer_size = (size_t)m_basicinfo.xsize * (
size_t)m_basicinfo.ysize * m_input_pixel_format.num_channels * 2;
276 m_input_pixel_format.data_type = JXL_TYPE_UINT16;
278 m_buffer_size = (size_t)m_basicinfo.xsize * (
size_t)m_basicinfo.ysize * m_input_pixel_format.num_channels * 2;
279 }
else if (m_basicinfo.bits_per_sample > 16 && is_fp) {
280 m_input_pixel_format.data_type = JXL_TYPE_FLOAT;
282 m_buffer_size = (size_t)m_basicinfo.xsize * (
size_t)m_basicinfo.ysize * m_input_pixel_format.num_channels * 4;
288 m_buffer_size = (size_t)m_basicinfo.xsize * (
size_t)m_basicinfo.ysize * m_input_pixel_format.num_channels * 2;
296 m_input_pixel_format.data_type = JXL_TYPE_UINT8;
297 m_buffer_size = (size_t)m_basicinfo.xsize * (
size_t)m_basicinfo.ysize * m_input_pixel_format.num_channels;
311 status = JxlDecoderGetColorAsEncodedProfile(m_decoder,
312#
if JPEGXL_NUMERIC_VERSION < JPEGXL_COMPUTE_NUMERIC_VERSION(0, 9, 0)
313 &m_input_pixel_format,
315 JXL_COLOR_PROFILE_TARGET_DATA,
318 if (
status == JXL_DEC_SUCCESS && color_encoding.color_space == JXL_COLOR_SPACE_RGB && color_encoding.white_point == JXL_WHITE_POINT_D65
319 && color_encoding.primaries == JXL_PRIMARIES_SRGB && color_encoding.transfer_function == JXL_TRANSFER_FUNCTION_SRGB) {
323 if (JxlDecoderGetICCProfileSize(m_decoder,
324#
if JPEGXL_NUMERIC_VERSION < JPEGXL_COMPUTE_NUMERIC_VERSION(0, 9, 0)
325 &m_input_pixel_format,
327 JXL_COLOR_PROFILE_TARGET_DATA,
329 == JXL_DEC_SUCCESS) {
332 if (JxlDecoderGetColorAsICCProfile(m_decoder,
333#
if JPEGXL_NUMERIC_VERSION < JPEGXL_COMPUTE_NUMERIC_VERSION(0, 9, 0)
334 &m_input_pixel_format,
336 JXL_COLOR_PROFILE_TARGET_DATA,
337 reinterpret_cast<uint8_t *
>(icc_data.data()),
339 == JXL_DEC_SUCCESS) {
342 if (!m_colorspace.isValid()) {
343 qWarning(
"JXL image has Qt-unsupported or invalid ICC profile!");
346 qWarning(
"Failed to obtain data from JPEG XL decoder");
349 qWarning(
"Empty ICC data");
352 qWarning(
"no ICC, other color profile");
356 if (m_basicinfo.have_animation) {
357 JxlFrameHeader frame_header;
360 for (
status = JxlDecoderProcessInput(m_decoder);
status != JXL_DEC_SUCCESS;
status = JxlDecoderProcessInput(m_decoder)) {
361 if (
status != JXL_DEC_FRAME) {
364 qWarning(
"ERROR: JXL decoding failed");
366 case JXL_DEC_NEED_MORE_INPUT:
367 qWarning(
"ERROR: JXL data incomplete");
370 qWarning(
"Unexpected event %d instead of JXL_DEC_FRAME",
status);
373 m_parseState = ParseJpegXLError;
377 if (JxlDecoderGetFrameHeader(m_decoder, &frame_header) != JXL_DEC_SUCCESS) {
378 qWarning(
"ERROR: JxlDecoderGetFrameHeader failed");
379 m_parseState = ParseJpegXLError;
383 if (m_basicinfo.animation.tps_denominator > 0 && m_basicinfo.animation.tps_numerator > 0) {
384 delay = (int)(0.5 + 1000.0 * frame_header.duration * m_basicinfo.animation.tps_denominator / m_basicinfo.animation.tps_numerator);
389 m_framedelays.append(delay);
392 if (m_framedelays.isEmpty()) {
393 qWarning(
"no frames loaded by the JXL plug-in");
394 m_parseState = ParseJpegXLError;
398 if (m_framedelays.count() == 1) {
399 qWarning(
"JXL file was marked as animation but it has only one frame.");
400 m_basicinfo.have_animation = JXL_FALSE;
403 m_framedelays.resize(1);
404 m_framedelays[0] = 0;
407#ifndef JXL_DECODE_BOXES_DISABLED
408 if (!decodeBoxes(
status)) {
417 m_next_image_delay = m_framedelays[0];
418 m_parseState = ParseJpegXLSuccess;
422bool QJpegXLHandler::decode_one_frame()
424 JxlDecoderStatus
status = JxlDecoderProcessInput(m_decoder);
425 if (
status != JXL_DEC_NEED_IMAGE_OUT_BUFFER) {
426 qWarning(
"Unexpected event %d instead of JXL_DEC_NEED_IMAGE_OUT_BUFFER",
status);
427 m_parseState = ParseJpegXLError;
431 m_current_image = imageAlloc(m_basicinfo.xsize, m_basicinfo.ysize, m_input_image_format);
432 if (m_current_image.isNull()) {
433 qWarning(
"Memory cannot be allocated");
434 m_parseState = ParseJpegXLError;
438 m_current_image.setColorSpace(m_colorspace);
439 if (!m_xmp.isEmpty()) {
440 m_current_image.setText(QStringLiteral(META_KEY_XMP_ADOBE),
QString::fromUtf8(m_xmp));
443 if (JxlDecoderSetImageOutBuffer(m_decoder, &m_input_pixel_format, m_current_image.bits(), m_buffer_size) != JXL_DEC_SUCCESS) {
444 qWarning(
"ERROR: JxlDecoderSetImageOutBuffer failed");
445 m_parseState = ParseJpegXLError;
449 status = JxlDecoderProcessInput(m_decoder);
450 if (
status != JXL_DEC_FULL_IMAGE) {
451 qWarning(
"Unexpected event %d instead of JXL_DEC_FULL_IMAGE",
status);
452 m_parseState = ParseJpegXLError;
456 if (m_target_image_format != m_input_image_format) {
457 m_current_image.convertTo(m_target_image_format);
460 m_next_image_delay = m_framedelays[m_currentimage_index];
461 m_previousimage_index = m_currentimage_index;
463 if (m_framedelays.count() > 1) {
464 m_currentimage_index++;
466 if (m_currentimage_index >= m_framedelays.count()) {
472 m_parseState = ParseJpegXLFinished;
474 m_parseState = ParseJpegXLSuccess;
478 m_parseState = ParseJpegXLFinished;
484bool QJpegXLHandler::read(
QImage *image)
486 if (!ensureALLCounted()) {
490 if (m_currentimage_index == m_previousimage_index) {
491 *image = m_current_image;
492 return jumpToNextImage();
495 if (decode_one_frame()) {
496 *image = m_current_image;
504void packRGBPixels(
QImage &img)
507 auto dest_pixels =
reinterpret_cast<T *
>(img.
bits());
508 for (qint32 y = 0; y < img.
height(); y++) {
509 auto src_pixels =
reinterpret_cast<const T *
>(img.
constScanLine(y));
510 for (qint32 x = 0; x < img.
width(); x++) {
512 *dest_pixels = *src_pixels;
516 *dest_pixels = *src_pixels;
520 *dest_pixels = *src_pixels;
527bool QJpegXLHandler::write(
const QImage &image)
530 qWarning(
"No image data to save");
534 if ((image.
width() == 0) || (image.
height() == 0)) {
535 qWarning(
"Image has zero dimension!");
539 if ((image.
width() > MAX_IMAGE_WIDTH) || (image.
height() > MAX_IMAGE_HEIGHT)) {
540 qWarning(
"Image (%dx%d) is too large to save!", image.
width(), image.
height());
544 size_t pixel_count = size_t(image.
width()) * image.
height();
545 if (MAX_IMAGE_PIXELS && pixel_count > MAX_IMAGE_PIXELS) {
546 qWarning(
"Image (%dx%d) will not be saved because it has more than %d megapixels!", image.
width(), image.
height(), MAX_IMAGE_PIXELS / 1024 / 1024);
551 bool save_fp =
false;
552 bool is_gray =
false;
558#ifndef JXL_HDR_PRESERVATION_DISABLED
566#ifndef JXL_HDR_PRESERVATION_DISABLED
605 if (image.
depth() > 32) {
613 JxlEncoder *encoder = JxlEncoderCreate(
nullptr);
615 qWarning(
"Failed to create Jxl encoder");
618 JxlEncoderUseBoxes(encoder);
620 if (m_quality > 100) {
622 }
else if (m_quality < 0) {
626 JxlBasicInfo output_info;
627 JxlEncoderInitBasicInfo(&output_info);
631 if (!tmpcs.
isValid() || tmpcs.
primaries() != QColorSpace::Primaries::SRgb || tmpcs.
transferFunction() != QColorSpace::TransferFunction::SRgb || m_quality == 100) {
635 if (iccprofile.
size() > 0 || m_quality == 100 || is_gray) {
636 output_info.uses_original_profile = JXL_TRUE;
641 if ( (save_depth > 8 && (image.
hasAlphaChannel() || output_info.uses_original_profile))
643 || (pixel_count > FEATURE_LEVEL_5_PIXELS)
644 || (image.
width() > FEATURE_LEVEL_5_WIDTH)
645 || (image.
height() > FEATURE_LEVEL_5_HEIGHT)) {
646 output_info.have_container = JXL_TRUE;
647 JxlEncoderUseContainer(encoder, JXL_TRUE);
648 JxlEncoderSetCodestreamLevel(encoder, 10);
652 void *runner =
nullptr;
655 if (num_worker_threads > 1) {
656 runner = JxlThreadParallelRunnerCreate(
nullptr, num_worker_threads);
657 if (JxlEncoderSetParallelRunner(encoder, JxlThreadParallelRunner, runner) != JXL_ENC_SUCCESS) {
658 qWarning(
"JxlEncoderSetParallelRunner failed");
659 JxlThreadParallelRunnerDestroy(runner);
660 JxlEncoderDestroy(encoder);
665 JxlPixelFormat pixel_format;
669 pixel_format.endianness = JXL_NATIVE_ENDIAN;
670 pixel_format.align = 0;
672 output_info.animation.tps_numerator = 10;
673 output_info.animation.tps_denominator = 1;
674 output_info.orientation = JXL_ORIENT_IDENTITY;
676 output_info.orientation = JXL_ORIENT_FLIP_HORIZONTAL;
678 output_info.orientation = JXL_ORIENT_ROTATE_180;
680 output_info.orientation = JXL_ORIENT_FLIP_VERTICAL;
682 output_info.orientation = JXL_ORIENT_TRANSPOSE;
684 output_info.orientation = JXL_ORIENT_ROTATE_90_CW;
686 output_info.orientation = JXL_ORIENT_ANTI_TRANSPOSE;
688 output_info.orientation = JXL_ORIENT_ROTATE_90_CCW;
691 if (save_depth > 8 && is_gray) {
692 pixel_format.data_type = JXL_TYPE_UINT16;
693 pixel_format.align = 4;
694 output_info.num_color_channels = 1;
695 output_info.bits_per_sample = 16;
697 pixel_format.num_channels = 1;
698 }
else if (is_gray) {
699 pixel_format.data_type = JXL_TYPE_UINT8;
700 pixel_format.align = 4;
701 output_info.num_color_channels = 1;
702 output_info.bits_per_sample = 8;
704 pixel_format.num_channels = 1;
705 }
else if (save_depth > 16) {
706 pixel_format.data_type = JXL_TYPE_FLOAT;
707 output_info.exponent_bits_per_sample = 8;
708 output_info.num_color_channels = 3;
709 output_info.bits_per_sample = 32;
713 pixel_format.num_channels = 4;
714 output_info.alpha_bits = 32;
715 output_info.alpha_exponent_bits = 8;
716 output_info.num_extra_channels = 1;
719 pixel_format.num_channels = 3;
720 output_info.alpha_bits = 0;
721 output_info.num_extra_channels = 0;
723 }
else if (save_depth > 8) {
724 pixel_format.data_type = save_fp ? JXL_TYPE_FLOAT16 : JXL_TYPE_UINT16;
725 output_info.exponent_bits_per_sample = save_fp ? 5 : 0;
726 output_info.num_color_channels = 3;
727 output_info.bits_per_sample = 16;
731 pixel_format.num_channels = 4;
732 output_info.alpha_bits = 16;
733 output_info.alpha_exponent_bits = save_fp ? 5 : 0;
734 output_info.num_extra_channels = 1;
737 pixel_format.num_channels = 3;
738 output_info.alpha_bits = 0;
739 output_info.num_extra_channels = 0;
742 pixel_format.data_type = JXL_TYPE_UINT8;
743 pixel_format.align = 4;
744 output_info.num_color_channels = 3;
745 output_info.bits_per_sample = 8;
749 pixel_format.num_channels = 4;
750 output_info.alpha_bits = 8;
751 output_info.num_extra_channels = 1;
754 pixel_format.num_channels = 3;
755 output_info.alpha_bits = 0;
756 output_info.num_extra_channels = 0;
761 const size_t xsize = tmpimage.
width();
762 const size_t ysize = tmpimage.
height();
764 if (xsize == 0 || ysize == 0 || tmpimage.
isNull()) {
765 qWarning(
"Unable to allocate memory for output image");
767 JxlThreadParallelRunnerDestroy(runner);
769 JxlEncoderDestroy(encoder);
773 output_info.xsize = tmpimage.
width();
774 output_info.ysize = tmpimage.
height();
776 status = JxlEncoderSetBasicInfo(encoder, &output_info);
777 if (
status != JXL_ENC_SUCCESS) {
778 qWarning(
"JxlEncoderSetBasicInfo failed!");
780 JxlThreadParallelRunnerDestroy(runner);
782 JxlEncoderDestroy(encoder);
786 auto xmp_data = image.
text(QStringLiteral(META_KEY_XMP_ADOBE)).
toUtf8();
787 if (!xmp_data.isEmpty()) {
788 const char *box_type =
"xml ";
789 status = JxlEncoderAddBox(encoder, box_type,
reinterpret_cast<const uint8_t *
>(xmp_data.constData()), xmp_data.size(), JXL_FALSE);
790 if (
status != JXL_ENC_SUCCESS) {
791 qWarning(
"JxlEncoderAddBox failed!");
793 JxlThreadParallelRunnerDestroy(runner);
795 JxlEncoderDestroy(encoder);
799 JxlEncoderCloseBoxes(encoder);
801 if (iccprofile.
size() > 0) {
802 status = JxlEncoderSetICCProfile(encoder,
reinterpret_cast<const uint8_t *
>(iccprofile.
constData()), iccprofile.
size());
803 if (
status != JXL_ENC_SUCCESS) {
804 qWarning(
"JxlEncoderSetICCProfile failed!");
806 JxlThreadParallelRunnerDestroy(runner);
808 JxlEncoderDestroy(encoder);
812 JxlColorEncoding color_profile;
813 JxlColorEncodingSetToSRGB(&color_profile, is_gray ? JXL_TRUE : JXL_FALSE);
815 status = JxlEncoderSetColorEncoding(encoder, &color_profile);
816 if (
status != JXL_ENC_SUCCESS) {
817 qWarning(
"JxlEncoderSetColorEncoding failed!");
819 JxlThreadParallelRunnerDestroy(runner);
821 JxlEncoderDestroy(encoder);
826 JxlEncoderFrameSettings *encoder_options = JxlEncoderFrameSettingsCreate(encoder,
nullptr);
828 JxlEncoderSetFrameDistance(encoder_options, (100.0f - m_quality) / 10.0f);
830 JxlEncoderSetFrameLossless(encoder_options, (m_quality == 100) ? JXL_TRUE : JXL_FALSE);
834 buffer_size = (size_t(save_depth / 8) * pixel_format.num_channels * xsize * ysize);
839 qWarning(
"Memory allocation error");
841 JxlThreadParallelRunnerDestroy(runner);
843 JxlEncoderDestroy(encoder);
848 if (save_depth > 16 && save_fp)
849 packRGBPixels<float>(tmpimage);
851 packRGBPixels<qfloat16>(tmpimage);
853 packRGBPixels<quint16>(tmpimage);
855 status = JxlEncoderAddImageFrame(encoder_options, &pixel_format,
static_cast<const void *
>(tmpimage.
constBits()), buffer_size);
857 if (
status == JXL_ENC_ERROR) {
858 qWarning(
"JxlEncoderAddImageFrame failed!");
860 JxlThreadParallelRunnerDestroy(runner);
862 JxlEncoderDestroy(encoder);
866 JxlEncoderCloseInput(encoder);
868 std::vector<uint8_t> compressed;
869 compressed.resize(4096);
874 next_out = compressed.data() + offset;
875 avail_out = compressed.size() - offset;
876 status = JxlEncoderProcessOutput(encoder, &next_out, &avail_out);
878 if (
status == JXL_ENC_NEED_MORE_OUTPUT) {
879 offset = next_out - compressed.data();
880 compressed.resize(compressed.size() * 2);
881 }
else if (
status == JXL_ENC_ERROR) {
882 qWarning(
"JxlEncoderProcessOutput failed!");
884 JxlThreadParallelRunnerDestroy(runner);
886 JxlEncoderDestroy(encoder);
889 }
while (
status != JXL_ENC_SUCCESS);
892 JxlThreadParallelRunnerDestroy(runner);
894 JxlEncoderDestroy(encoder);
896 compressed.resize(next_out - compressed.data());
898 if (compressed.size() > 0) {
899 qint64 write_status = device()->
write(
reinterpret_cast<const char *
>(compressed.data()), compressed.size());
901 if (write_status > 0) {
903 }
else if (write_status == -1) {
904 qWarning(
"Write error: %s\n", qUtf8Printable(device()->errorString()));
911QVariant QJpegXLHandler::option(ImageOption option)
const
913 if (!supportsOption(option)) {
917 if (option == Quality) {
921 if (!ensureParsed()) {
922#ifdef JXL_QT_AUTOTRANSFORM
923 if (option == ImageTransformation) {
924 return int(m_transformations);
933 return QSize(m_basicinfo.xsize, m_basicinfo.ysize);
935 if (m_basicinfo.have_animation) {
940#ifdef JXL_QT_AUTOTRANSFORM
941 case ImageTransformation:
942 if (m_basicinfo.orientation == JXL_ORIENT_IDENTITY) {
944 }
else if (m_basicinfo.orientation == JXL_ORIENT_FLIP_HORIZONTAL) {
946 }
else if (m_basicinfo.orientation == JXL_ORIENT_ROTATE_180) {
948 }
else if (m_basicinfo.orientation == JXL_ORIENT_FLIP_VERTICAL) {
950 }
else if (m_basicinfo.orientation == JXL_ORIENT_TRANSPOSE) {
952 }
else if (m_basicinfo.orientation == JXL_ORIENT_ROTATE_90_CW) {
954 }
else if (m_basicinfo.orientation == JXL_ORIENT_ANTI_TRANSPOSE) {
956 }
else if (m_basicinfo.orientation == JXL_ORIENT_ROTATE_90_CCW) {
968void QJpegXLHandler::setOption(ImageOption option,
const QVariant &value)
972 m_quality = value.
toInt();
973 if (m_quality > 100) {
975 }
else if (m_quality < 0) {
979#ifdef JXL_QT_AUTOTRANSFORM
980 case ImageTransformation:
981 if (
auto t = value.
toInt()) {
993bool QJpegXLHandler::supportsOption(ImageOption option)
const
995 auto supported = option == Quality || option == Size || option ==
Animation;
996#ifdef JXL_QT_AUTOTRANSFORM
997 supported = supported || option == ImageTransformation;
1002int QJpegXLHandler::imageCount()
const
1004 if (!ensureParsed()) {
1008 if (m_parseState == ParseJpegXLBasicInfoParsed) {
1009 if (!m_basicinfo.have_animation) {
1013 if (!ensureALLCounted()) {
1018 if (!m_framedelays.isEmpty()) {
1019 return m_framedelays.count();
1024int QJpegXLHandler::currentImageNumber()
const
1026 if (m_parseState == ParseJpegXLNotParsed) {
1030 if (m_parseState == ParseJpegXLError || m_parseState == ParseJpegXLBasicInfoParsed || !m_decoder) {
1034 return m_currentimage_index;
1037bool QJpegXLHandler::jumpToNextImage()
1039 if (!ensureALLCounted()) {
1043 if (m_framedelays.count() > 1) {
1044 m_currentimage_index++;
1046 if (m_currentimage_index >= m_framedelays.count()) {
1051 JxlDecoderSkipFrames(m_decoder, 1);
1055 m_parseState = ParseJpegXLSuccess;
1059bool QJpegXLHandler::jumpToImage(
int imageNumber)
1061 if (!ensureALLCounted()) {
1065 if (imageNumber < 0 || imageNumber >= m_framedelays.count()) {
1069 if (imageNumber == m_currentimage_index) {
1070 m_parseState = ParseJpegXLSuccess;
1074 if (imageNumber > m_currentimage_index) {
1075 JxlDecoderSkipFrames(m_decoder, imageNumber - m_currentimage_index);
1076 m_currentimage_index = imageNumber;
1077 m_parseState = ParseJpegXLSuccess;
1085 if (imageNumber > 0) {
1086 JxlDecoderSkipFrames(m_decoder, imageNumber);
1088 m_currentimage_index = imageNumber;
1089 m_parseState = ParseJpegXLSuccess;
1093int QJpegXLHandler::nextImageDelay()
const
1095 if (!ensureALLCounted()) {
1099 if (m_framedelays.count() < 2) {
1103 return m_next_image_delay;
1106int QJpegXLHandler::loopCount()
const
1108 if (!ensureParsed()) {
1112 if (m_basicinfo.have_animation) {
1113 return (m_basicinfo.animation.num_loops > 0) ? m_basicinfo.animation.num_loops - 1 : -1;
1119bool QJpegXLHandler::rewind()
1121 m_currentimage_index = 0;
1123 JxlDecoderReleaseInput(m_decoder);
1124 JxlDecoderRewind(m_decoder);
1126 if (JxlDecoderSetParallelRunner(m_decoder, JxlThreadParallelRunner, m_runner) != JXL_DEC_SUCCESS) {
1127 qWarning(
"ERROR: JxlDecoderSetParallelRunner failed");
1128 m_parseState = ParseJpegXLError;
1133 if (JxlDecoderSetInput(m_decoder,
reinterpret_cast<const uint8_t *
>(m_rawData.constData()), m_rawData.size()) != JXL_DEC_SUCCESS) {
1134 qWarning(
"ERROR: JxlDecoderSetInput failed");
1135 m_parseState = ParseJpegXLError;
1139 JxlDecoderCloseInput(m_decoder);
1141 if (m_basicinfo.uses_original_profile) {
1142 if (JxlDecoderSubscribeEvents(m_decoder, JXL_DEC_FULL_IMAGE) != JXL_DEC_SUCCESS) {
1143 qWarning(
"ERROR: JxlDecoderSubscribeEvents failed");
1144 m_parseState = ParseJpegXLError;
1148 if (JxlDecoderSubscribeEvents(m_decoder, JXL_DEC_COLOR_ENCODING | JXL_DEC_FULL_IMAGE) != JXL_DEC_SUCCESS) {
1149 qWarning(
"ERROR: JxlDecoderSubscribeEvents failed");
1150 m_parseState = ParseJpegXLError;
1154 JxlDecoderStatus
status = JxlDecoderProcessInput(m_decoder);
1155 if (
status != JXL_DEC_COLOR_ENCODING) {
1156 qWarning(
"Unexpected event %d instead of JXL_DEC_COLOR_ENCODING",
status);
1157 m_parseState = ParseJpegXLError;
1161 JxlColorEncoding color_encoding;
1162 JxlColorEncodingSetToSRGB(&color_encoding, JXL_FALSE);
1163 JxlDecoderSetPreferredColorProfile(m_decoder, &color_encoding);
1169bool QJpegXLHandler::decodeBoxes(JxlDecoderStatus &
status)
1172 status = JxlDecoderProcessInput(m_decoder);
1173 if (
status == JXL_DEC_BOX) {
1175 JxlDecoderGetBoxType(m_decoder, type, JXL_FALSE);
1176 if (memcmp(type,
"xml ", 4) == 0) {
1178 if (JxlDecoderGetBoxSizeRaw(m_decoder, &size) == JXL_DEC_SUCCESS && size < uint64_t(kMaxQVectorSize)) {
1180 JxlDecoderSetBoxBuffer(m_decoder,
reinterpret_cast<uint8_t *
>(m_xmp.data()), m_xmp.size());
1184 }
while (
status == JXL_DEC_BOX);
1186 if (
status == JXL_DEC_ERROR) {
1187 qWarning(
"ERROR: JXL decoding failed");
1188 m_parseState = ParseJpegXLError;
1191 if (
status == JXL_DEC_NEED_MORE_INPUT) {
1192 qWarning(
"ERROR: JXL data incomplete");
1193 m_parseState = ParseJpegXLError;
1201 if (format ==
"jxl") {
1213 if (device->
isReadable() && QJpegXLHandler::canRead(device)) {
1232#include "moc_jxl_p.cpp"
Q_SCRIPTABLE CaptureState status()
QFlags< Capability > Capabilities
VehicleSection::Type type(QStringView coachNumber, QStringView coachClassification)
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
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