13#include "microexif_p.h"
16#include <jxl/encode.h>
17#include <jxl/thread_parallel_runner.h>
19#if JPEGXL_NUMERIC_VERSION >= JPEGXL_COMPUTE_NUMERIC_VERSION(0, 9, 0)
26#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))
27#ifndef JXL_QT_AUTOTRANSFORM
28#define JXL_QT_AUTOTRANSFORM
32#if JPEGXL_NUMERIC_VERSION < JPEGXL_COMPUTE_NUMERIC_VERSION(0, 9, 0)
33#ifndef JXL_HDR_PRESERVATION_DISABLED
36#define JXL_HDR_PRESERVATION_DISABLED
40#ifndef JXL_DECODE_BOXES_DISABLED
46#define FEATURE_LEVEL_5_WIDTH 262144
47#define FEATURE_LEVEL_5_HEIGHT 262144
48#define FEATURE_LEVEL_5_PIXELS 268435456
50#if QT_POINTER_SIZE < 8
51#define MAX_IMAGE_WIDTH 32767
52#define MAX_IMAGE_HEIGHT 32767
53#define MAX_IMAGE_PIXELS FEATURE_LEVEL_5_PIXELS
55#define MAX_IMAGE_WIDTH FEATURE_LEVEL_5_WIDTH
56#define MAX_IMAGE_HEIGHT FEATURE_LEVEL_5_HEIGHT
57#define MAX_IMAGE_PIXELS FEATURE_LEVEL_5_PIXELS
60QJpegXLHandler::QJpegXLHandler()
61 : m_parseState(ParseJpegXLNotParsed)
63 , m_currentimage_index(0)
64 , m_previousimage_index(-1)
68 , m_next_image_delay(0)
69 , m_input_image_format(
QImage::Format_Invalid)
70 , m_target_image_format(
QImage::Format_Invalid)
75QJpegXLHandler::~QJpegXLHandler()
78 JxlThreadParallelRunnerDestroy(m_runner);
81 JxlDecoderDestroy(m_decoder);
85bool QJpegXLHandler::canRead()
const
87 if (m_parseState == ParseJpegXLNotParsed && !canRead(device())) {
91 if (m_parseState != ParseJpegXLError) {
94 if (m_parseState == ParseJpegXLFinished) {
103bool QJpegXLHandler::canRead(
QIODevice *device)
109 if (header.
size() < 12) {
113 JxlSignature signature = JxlSignatureCheck(
reinterpret_cast<const uint8_t *
>(header.
constData()), header.
size());
114 if (signature == JXL_SIG_CODESTREAM || signature == JXL_SIG_CONTAINER) {
120bool QJpegXLHandler::ensureParsed()
const
122 if (m_parseState == ParseJpegXLSuccess || m_parseState == ParseJpegXLBasicInfoParsed || m_parseState == ParseJpegXLFinished) {
125 if (m_parseState == ParseJpegXLError) {
129 QJpegXLHandler *that =
const_cast<QJpegXLHandler *
>(
this);
131 return that->ensureDecoder();
134bool QJpegXLHandler::ensureALLCounted()
const
136 if (!ensureParsed()) {
140 if (m_parseState == ParseJpegXLSuccess || m_parseState == ParseJpegXLFinished) {
144 QJpegXLHandler *that =
const_cast<QJpegXLHandler *
>(
this);
146 return that->countALLFrames();
149bool QJpegXLHandler::ensureDecoder()
155 m_rawData = device()->
readAll();
157 if (m_rawData.isEmpty()) {
161 JxlSignature signature = JxlSignatureCheck(
reinterpret_cast<const uint8_t *
>(m_rawData.constData()), m_rawData.size());
162 if (signature != JXL_SIG_CODESTREAM && signature != JXL_SIG_CONTAINER) {
163 m_parseState = ParseJpegXLError;
167 m_decoder = JxlDecoderCreate(
nullptr);
169 qWarning(
"ERROR: JxlDecoderCreate failed");
170 m_parseState = ParseJpegXLError;
174#ifdef JXL_QT_AUTOTRANSFORM
176 JxlDecoderSetKeepOrientation(m_decoder,
true);
180 if (!m_runner && num_worker_threads >= 4) {
183 num_worker_threads = num_worker_threads / 2;
184 num_worker_threads = qBound(2, num_worker_threads, 64);
185 m_runner = JxlThreadParallelRunnerCreate(
nullptr, num_worker_threads);
187 if (JxlDecoderSetParallelRunner(m_decoder, JxlThreadParallelRunner, m_runner) != JXL_DEC_SUCCESS) {
188 qWarning(
"ERROR: JxlDecoderSetParallelRunner failed");
189 m_parseState = ParseJpegXLError;
194 if (JxlDecoderSetInput(m_decoder,
reinterpret_cast<const uint8_t *
>(m_rawData.constData()), m_rawData.size()) != JXL_DEC_SUCCESS) {
195 qWarning(
"ERROR: JxlDecoderSetInput failed");
196 m_parseState = ParseJpegXLError;
200 JxlDecoderCloseInput(m_decoder);
202 JxlDecoderStatus
status = JxlDecoderSubscribeEvents(m_decoder, JXL_DEC_BASIC_INFO | JXL_DEC_COLOR_ENCODING | JXL_DEC_FRAME);
203 if (
status == JXL_DEC_ERROR) {
204 qWarning(
"ERROR: JxlDecoderSubscribeEvents failed");
205 m_parseState = ParseJpegXLError;
209 status = JxlDecoderProcessInput(m_decoder);
210 if (
status == JXL_DEC_ERROR) {
211 qWarning(
"ERROR: JXL decoding failed");
212 m_parseState = ParseJpegXLError;
215 if (
status == JXL_DEC_NEED_MORE_INPUT) {
216 qWarning(
"ERROR: JXL data incomplete");
217 m_parseState = ParseJpegXLError;
221 status = JxlDecoderGetBasicInfo(m_decoder, &m_basicinfo);
222 if (
status != JXL_DEC_SUCCESS) {
223 qWarning(
"ERROR: JXL basic info not available");
224 m_parseState = ParseJpegXLError;
228 if (m_basicinfo.xsize == 0 || m_basicinfo.ysize == 0) {
229 qWarning(
"ERROR: JXL image has zero dimensions");
230 m_parseState = ParseJpegXLError;
234 if (m_basicinfo.xsize > MAX_IMAGE_WIDTH || m_basicinfo.ysize > MAX_IMAGE_HEIGHT) {
235 qWarning(
"JXL image (%dx%d) is too large", m_basicinfo.xsize, m_basicinfo.ysize);
236 m_parseState = ParseJpegXLError;
240 m_parseState = ParseJpegXLBasicInfoParsed;
244bool QJpegXLHandler::countALLFrames()
246 if (m_parseState != ParseJpegXLBasicInfoParsed) {
250 JxlDecoderStatus
status = JxlDecoderProcessInput(m_decoder);
251 if (
status != JXL_DEC_COLOR_ENCODING) {
252 qWarning(
"Unexpected event %d instead of JXL_DEC_COLOR_ENCODING",
status);
253 m_parseState = ParseJpegXLError;
257 bool is_gray = m_basicinfo.num_color_channels == 1 && m_basicinfo.alpha_bits == 0;
258 JxlColorEncoding color_encoding;
259 if (m_basicinfo.uses_original_profile == JXL_FALSE && m_basicinfo.have_animation == JXL_FALSE) {
260#if JPEGXL_NUMERIC_VERSION >= JPEGXL_COMPUTE_NUMERIC_VERSION(0, 9, 0)
261 const JxlCmsInterface *jxlcms = JxlGetDefaultCms();
263 status = JxlDecoderSetCms(m_decoder, *jxlcms);
264 if (
status != JXL_DEC_SUCCESS) {
265 qWarning(
"JxlDecoderSetCms ERROR");
268 qWarning(
"No JPEG XL CMS Interface");
271 JxlColorEncodingSetToSRGB(&color_encoding, is_gray ? JXL_TRUE : JXL_FALSE);
272 JxlDecoderSetPreferredColorProfile(m_decoder, &color_encoding);
275 bool loadalpha =
false;
276 if (m_basicinfo.alpha_bits > 0) {
280 m_input_pixel_format.endianness = JXL_NATIVE_ENDIAN;
281 m_input_pixel_format.align = 4;
282 m_input_pixel_format.num_channels = is_gray ? 1 : 4;
284 if (m_basicinfo.bits_per_sample > 8) {
285#ifdef JXL_HDR_PRESERVATION_DISABLED
288 bool is_fp = m_basicinfo.exponent_bits_per_sample > 0 && m_basicinfo.num_color_channels == 3;
292 m_input_pixel_format.data_type = JXL_TYPE_UINT16;
294 m_buffer_size = ((size_t)m_basicinfo.ysize - 1) * (((((
size_t)m_basicinfo.xsize) * 2 + 3) >> 2) << 2) + (size_t)m_basicinfo.xsize * 2;
295 }
else if (m_basicinfo.bits_per_sample > 16 && is_fp) {
296 m_input_pixel_format.data_type = JXL_TYPE_FLOAT;
298 m_buffer_size = (size_t)m_basicinfo.xsize * (
size_t)m_basicinfo.ysize * m_input_pixel_format.num_channels * 4;
304 m_input_pixel_format.data_type = is_fp ? JXL_TYPE_FLOAT16 : JXL_TYPE_UINT16;
305 m_buffer_size = (size_t)m_basicinfo.xsize * (
size_t)m_basicinfo.ysize * m_input_pixel_format.num_channels * 2;
313 m_input_pixel_format.data_type = JXL_TYPE_UINT8;
317 m_buffer_size = ((size_t)m_basicinfo.ysize - 1) * (((((
size_t)m_basicinfo.xsize) + 3) >> 2) << 2) + (size_t)m_basicinfo.xsize;
320 m_buffer_size = (size_t)m_basicinfo.xsize * (
size_t)m_basicinfo.ysize * m_input_pixel_format.num_channels;
329 status = JxlDecoderGetColorAsEncodedProfile(m_decoder,
330#
if JPEGXL_NUMERIC_VERSION < JPEGXL_COMPUTE_NUMERIC_VERSION(0, 9, 0)
331 &m_input_pixel_format,
333 JXL_COLOR_PROFILE_TARGET_DATA,
336 if (
status == JXL_DEC_SUCCESS && color_encoding.color_space == JXL_COLOR_SPACE_RGB && color_encoding.white_point == JXL_WHITE_POINT_D65
337 && color_encoding.primaries == JXL_PRIMARIES_SRGB && color_encoding.transfer_function == JXL_TRANSFER_FUNCTION_SRGB) {
341 if (JxlDecoderGetICCProfileSize(m_decoder,
342#
if JPEGXL_NUMERIC_VERSION < JPEGXL_COMPUTE_NUMERIC_VERSION(0, 9, 0)
343 &m_input_pixel_format,
345 JXL_COLOR_PROFILE_TARGET_DATA,
347 == JXL_DEC_SUCCESS) {
350 if (JxlDecoderGetColorAsICCProfile(m_decoder,
351#
if JPEGXL_NUMERIC_VERSION < JPEGXL_COMPUTE_NUMERIC_VERSION(0, 9, 0)
352 &m_input_pixel_format,
354 JXL_COLOR_PROFILE_TARGET_DATA,
355 reinterpret_cast<uint8_t *
>(icc_data.data()),
357 == JXL_DEC_SUCCESS) {
360 if (!m_colorspace.isValid()) {
361 qWarning(
"JXL image has Qt-unsupported or invalid ICC profile!");
364 qWarning(
"Failed to obtain data from JPEG XL decoder");
367 qWarning(
"Empty ICC data");
370 qWarning(
"no ICC, other color profile");
374 if (m_basicinfo.have_animation) {
375 JxlFrameHeader frame_header;
378 for (
status = JxlDecoderProcessInput(m_decoder);
status != JXL_DEC_SUCCESS;
status = JxlDecoderProcessInput(m_decoder)) {
379 if (
status != JXL_DEC_FRAME) {
382 qWarning(
"ERROR: JXL decoding failed");
384 case JXL_DEC_NEED_MORE_INPUT:
385 qWarning(
"ERROR: JXL data incomplete");
388 qWarning(
"Unexpected event %d instead of JXL_DEC_FRAME",
status);
391 m_parseState = ParseJpegXLError;
395 if (JxlDecoderGetFrameHeader(m_decoder, &frame_header) != JXL_DEC_SUCCESS) {
396 qWarning(
"ERROR: JxlDecoderGetFrameHeader failed");
397 m_parseState = ParseJpegXLError;
401 if (m_basicinfo.animation.tps_denominator > 0 && m_basicinfo.animation.tps_numerator > 0) {
402 delay = (int)(0.5 + 1000.0 * frame_header.duration * m_basicinfo.animation.tps_denominator / m_basicinfo.animation.tps_numerator);
407 m_framedelays.append(delay);
409 if (frame_header.is_last == JXL_TRUE) {
414 if (m_framedelays.isEmpty()) {
415 qWarning(
"no frames loaded by the JXL plug-in");
416 m_parseState = ParseJpegXLError;
420 if (m_framedelays.count() == 1) {
421 qWarning(
"JXL file was marked as animation but it has only one frame.");
422 m_basicinfo.have_animation = JXL_FALSE;
425 m_framedelays.resize(1);
426 m_framedelays[0] = 0;
429#ifndef JXL_DECODE_BOXES_DISABLED
430 if (!decodeContainer()) {
439 m_next_image_delay = m_framedelays[0];
440 m_parseState = ParseJpegXLSuccess;
444bool QJpegXLHandler::decode_one_frame()
446 JxlDecoderStatus
status = JxlDecoderProcessInput(m_decoder);
447 if (
status != JXL_DEC_NEED_IMAGE_OUT_BUFFER) {
448 qWarning(
"Unexpected event %d instead of JXL_DEC_NEED_IMAGE_OUT_BUFFER",
status);
449 m_parseState = ParseJpegXLError;
453 m_current_image = imageAlloc(m_basicinfo.xsize, m_basicinfo.ysize, m_input_image_format);
454 if (m_current_image.isNull()) {
455 qWarning(
"Memory cannot be allocated");
456 m_parseState = ParseJpegXLError;
460 m_current_image.setColorSpace(m_colorspace);
461 if (!m_xmp.isEmpty()) {
462 m_current_image.setText(QStringLiteral(META_KEY_XMP_ADOBE),
QString::fromUtf8(m_xmp));
465 if (!m_exif.isEmpty()) {
466 auto exif = MicroExif::fromByteArray(m_exif);
468 if (exif.horizontalResolution() > 0)
469 m_current_image.setDotsPerMeterX(qRound(exif.horizontalResolution() / 25.4 * 1000));
470 if (exif.verticalResolution() > 0)
471 m_current_image.setDotsPerMeterY(qRound(exif.verticalResolution() / 25.4 * 1000));
473 exif.toImageMetadata(m_current_image);
476 if (JxlDecoderSetImageOutBuffer(m_decoder, &m_input_pixel_format, m_current_image.bits(), m_buffer_size) != JXL_DEC_SUCCESS) {
477 qWarning(
"ERROR: JxlDecoderSetImageOutBuffer failed");
478 m_parseState = ParseJpegXLError;
482 status = JxlDecoderProcessInput(m_decoder);
483 if (
status != JXL_DEC_FULL_IMAGE) {
484 qWarning(
"Unexpected event %d instead of JXL_DEC_FULL_IMAGE",
status);
485 m_parseState = ParseJpegXLError;
489 if (m_target_image_format != m_input_image_format) {
490 m_current_image.convertTo(m_target_image_format);
493 m_next_image_delay = m_framedelays[m_currentimage_index];
494 m_previousimage_index = m_currentimage_index;
496 if (m_framedelays.count() > 1) {
497 m_currentimage_index++;
499 if (m_currentimage_index >= m_framedelays.count()) {
505 m_parseState = ParseJpegXLFinished;
507 m_parseState = ParseJpegXLSuccess;
511 m_parseState = ParseJpegXLFinished;
517bool QJpegXLHandler::read(
QImage *image)
519 if (!ensureALLCounted()) {
523 if (m_currentimage_index == m_previousimage_index) {
524 *image = m_current_image;
525 return jumpToNextImage();
528 if (decode_one_frame()) {
529 *image = m_current_image;
537void packRGBPixels(
QImage &img)
540 auto dest_pixels =
reinterpret_cast<T *
>(img.
bits());
541 for (qint32 y = 0; y < img.
height(); y++) {
542 auto src_pixels =
reinterpret_cast<const T *
>(img.
constScanLine(y));
543 for (qint32 x = 0; x < img.
width(); x++) {
545 *dest_pixels = *src_pixels;
549 *dest_pixels = *src_pixels;
553 *dest_pixels = *src_pixels;
560bool QJpegXLHandler::write(
const QImage &image)
563 qWarning(
"No image data to save");
567 if ((image.
width() == 0) || (image.
height() == 0)) {
568 qWarning(
"Image has zero dimension!");
572 if ((image.
width() > MAX_IMAGE_WIDTH) || (image.
height() > MAX_IMAGE_HEIGHT)) {
573 qWarning(
"Image (%dx%d) is too large to save!", image.
width(), image.
height());
577 size_t pixel_count = size_t(image.
width()) * image.
height();
578 if (MAX_IMAGE_PIXELS && pixel_count > MAX_IMAGE_PIXELS) {
579 qWarning(
"Image (%dx%d) will not be saved because it has more than %d megapixels!", image.
width(), image.
height(), MAX_IMAGE_PIXELS / 1024 / 1024);
584 bool save_fp =
false;
585 bool is_gray =
false;
591#ifndef JXL_HDR_PRESERVATION_DISABLED
599#ifndef JXL_HDR_PRESERVATION_DISABLED
620#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
621 case QImage::Format_CMYK8888:
641 if (image.
depth() > 32) {
649 JxlEncoder *encoder = JxlEncoderCreate(
nullptr);
651 qWarning(
"Failed to create Jxl encoder");
655 if (m_quality > 100) {
657 }
else if (m_quality < 0) {
661 JxlEncoderUseContainer(encoder, JXL_TRUE);
662 JxlEncoderUseBoxes(encoder);
664 JxlBasicInfo output_info;
665 JxlEncoderInitBasicInfo(&output_info);
666 output_info.have_container = JXL_TRUE;
671 || m_quality == 100) {
675 if (iccprofile.
size() > 0 || m_quality == 100 || is_gray) {
676 output_info.uses_original_profile = JXL_TRUE;
681 if ( (save_depth > 8 && (image.
hasAlphaChannel() || output_info.uses_original_profile))
683 || (pixel_count > FEATURE_LEVEL_5_PIXELS)
684 || (image.
width() > FEATURE_LEVEL_5_WIDTH)
685 || (image.
height() > FEATURE_LEVEL_5_HEIGHT)) {
686 JxlEncoderSetCodestreamLevel(encoder, 10);
690 void *runner =
nullptr;
693 if (num_worker_threads > 1) {
694 runner = JxlThreadParallelRunnerCreate(
nullptr, num_worker_threads);
695 if (JxlEncoderSetParallelRunner(encoder, JxlThreadParallelRunner, runner) != JXL_ENC_SUCCESS) {
696 qWarning(
"JxlEncoderSetParallelRunner failed");
697 JxlThreadParallelRunnerDestroy(runner);
698 JxlEncoderDestroy(encoder);
703 JxlPixelFormat pixel_format;
707 pixel_format.endianness = JXL_NATIVE_ENDIAN;
708 pixel_format.align = 0;
710 output_info.animation.tps_numerator = 10;
711 output_info.animation.tps_denominator = 1;
712 output_info.orientation = JXL_ORIENT_IDENTITY;
714 output_info.orientation = JXL_ORIENT_FLIP_HORIZONTAL;
716 output_info.orientation = JXL_ORIENT_ROTATE_180;
718 output_info.orientation = JXL_ORIENT_FLIP_VERTICAL;
720 output_info.orientation = JXL_ORIENT_TRANSPOSE;
722 output_info.orientation = JXL_ORIENT_ROTATE_90_CW;
724 output_info.orientation = JXL_ORIENT_ANTI_TRANSPOSE;
726 output_info.orientation = JXL_ORIENT_ROTATE_90_CCW;
729 if (save_depth > 8 && is_gray) {
730 pixel_format.data_type = JXL_TYPE_UINT16;
731 pixel_format.align = 4;
732 output_info.num_color_channels = 1;
733 output_info.bits_per_sample = 16;
735 pixel_format.num_channels = 1;
736 }
else if (is_gray) {
737 pixel_format.data_type = JXL_TYPE_UINT8;
738 pixel_format.align = 4;
739 output_info.num_color_channels = 1;
740 output_info.bits_per_sample = 8;
742 pixel_format.num_channels = 1;
743 }
else if (save_depth > 16) {
744 pixel_format.data_type = JXL_TYPE_FLOAT;
745 output_info.exponent_bits_per_sample = 8;
746 output_info.num_color_channels = 3;
747 output_info.bits_per_sample = 32;
751 pixel_format.num_channels = 4;
752 output_info.alpha_bits = 32;
753 output_info.alpha_exponent_bits = 8;
754 output_info.num_extra_channels = 1;
757 pixel_format.num_channels = 3;
758 output_info.alpha_bits = 0;
759 output_info.num_extra_channels = 0;
761 }
else if (save_depth > 8) {
762 pixel_format.data_type = save_fp ? JXL_TYPE_FLOAT16 : JXL_TYPE_UINT16;
763 output_info.exponent_bits_per_sample = save_fp ? 5 : 0;
764 output_info.num_color_channels = 3;
765 output_info.bits_per_sample = 16;
769 pixel_format.num_channels = 4;
770 output_info.alpha_bits = 16;
771 output_info.alpha_exponent_bits = save_fp ? 5 : 0;
772 output_info.num_extra_channels = 1;
775 pixel_format.num_channels = 3;
776 output_info.alpha_bits = 0;
777 output_info.num_extra_channels = 0;
780 pixel_format.data_type = JXL_TYPE_UINT8;
781 pixel_format.align = 4;
782 output_info.num_color_channels = 3;
783 output_info.bits_per_sample = 8;
787 pixel_format.num_channels = 4;
788 output_info.alpha_bits = 8;
789 output_info.num_extra_channels = 1;
792 pixel_format.num_channels = 3;
793 output_info.alpha_bits = 0;
794 output_info.num_extra_channels = 0;
798#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
802 if (cs.isValid() && cs.colorModel() == QColorSpace::ColorModel::Cmyk && image.
format() == QImage::Format_CMYK8888) {
812 const size_t xsize = tmpimage.
width();
813 const size_t ysize = tmpimage.
height();
815 if (xsize == 0 || ysize == 0 || tmpimage.
isNull()) {
816 qWarning(
"Unable to allocate memory for output image");
818 JxlThreadParallelRunnerDestroy(runner);
820 JxlEncoderDestroy(encoder);
824 output_info.xsize = tmpimage.
width();
825 output_info.ysize = tmpimage.
height();
827 status = JxlEncoderSetBasicInfo(encoder, &output_info);
828 if (
status != JXL_ENC_SUCCESS) {
829 qWarning(
"JxlEncoderSetBasicInfo failed!");
831 JxlThreadParallelRunnerDestroy(runner);
833 JxlEncoderDestroy(encoder);
837 auto exif_data = MicroExif::fromImage(image).toByteArray();
838 if (!exif_data.isEmpty()) {
840 const char *box_type =
"Exif";
841 status = JxlEncoderAddBox(encoder, box_type,
reinterpret_cast<const uint8_t *
>(exif_data.constData()), exif_data.size(), JXL_FALSE);
842 if (
status != JXL_ENC_SUCCESS) {
843 qWarning(
"JxlEncoderAddBox failed!");
845 JxlThreadParallelRunnerDestroy(runner);
847 JxlEncoderDestroy(encoder);
851 auto xmp_data = image.
text(QStringLiteral(META_KEY_XMP_ADOBE)).
toUtf8();
852 if (!xmp_data.isEmpty()) {
853 const char *box_type =
"xml ";
854 status = JxlEncoderAddBox(encoder, box_type,
reinterpret_cast<const uint8_t *
>(xmp_data.constData()), xmp_data.size(), JXL_FALSE);
855 if (
status != JXL_ENC_SUCCESS) {
856 qWarning(
"JxlEncoderAddBox failed!");
858 JxlThreadParallelRunnerDestroy(runner);
860 JxlEncoderDestroy(encoder);
864 JxlEncoderCloseBoxes(encoder);
866 if (iccprofile.
size() > 0) {
867 status = JxlEncoderSetICCProfile(encoder,
reinterpret_cast<const uint8_t *
>(iccprofile.
constData()), iccprofile.
size());
868 if (
status != JXL_ENC_SUCCESS) {
869 qWarning(
"JxlEncoderSetICCProfile failed!");
871 JxlThreadParallelRunnerDestroy(runner);
873 JxlEncoderDestroy(encoder);
877 JxlColorEncoding color_profile;
878 JxlColorEncodingSetToSRGB(&color_profile, is_gray ? JXL_TRUE : JXL_FALSE);
880 status = JxlEncoderSetColorEncoding(encoder, &color_profile);
881 if (
status != JXL_ENC_SUCCESS) {
882 qWarning(
"JxlEncoderSetColorEncoding failed!");
884 JxlThreadParallelRunnerDestroy(runner);
886 JxlEncoderDestroy(encoder);
891 JxlEncoderFrameSettings *encoder_options = JxlEncoderFrameSettingsCreate(encoder,
nullptr);
893 JxlEncoderSetFrameDistance(encoder_options, (100.0f - m_quality) / 10.0f);
895 JxlEncoderSetFrameLossless(encoder_options, (m_quality == 100) ? JXL_TRUE : JXL_FALSE);
899 buffer_size = (size_t(save_depth / 8) * pixel_format.num_channels * xsize * ysize);
904 qWarning(
"Memory allocation error");
906 JxlThreadParallelRunnerDestroy(runner);
908 JxlEncoderDestroy(encoder);
913 if (save_depth > 16 && save_fp)
914 packRGBPixels<float>(tmpimage);
916 packRGBPixels<qfloat16>(tmpimage);
918 packRGBPixels<quint16>(tmpimage);
920 status = JxlEncoderAddImageFrame(encoder_options, &pixel_format,
static_cast<const void *
>(tmpimage.
constBits()), buffer_size);
922 if (
status == JXL_ENC_ERROR) {
923 qWarning(
"JxlEncoderAddImageFrame failed!");
925 JxlThreadParallelRunnerDestroy(runner);
927 JxlEncoderDestroy(encoder);
931 JxlEncoderCloseInput(encoder);
933 std::vector<uint8_t> compressed;
934 compressed.resize(4096);
939 next_out = compressed.data() + offset;
940 avail_out = compressed.size() - offset;
941 status = JxlEncoderProcessOutput(encoder, &next_out, &avail_out);
943 if (
status == JXL_ENC_NEED_MORE_OUTPUT) {
944 offset = next_out - compressed.data();
945 compressed.resize(compressed.size() * 2);
946 }
else if (
status == JXL_ENC_ERROR) {
947 qWarning(
"JxlEncoderProcessOutput failed!");
949 JxlThreadParallelRunnerDestroy(runner);
951 JxlEncoderDestroy(encoder);
954 }
while (
status != JXL_ENC_SUCCESS);
957 JxlThreadParallelRunnerDestroy(runner);
959 JxlEncoderDestroy(encoder);
961 compressed.resize(next_out - compressed.data());
963 if (compressed.size() > 0) {
964 qint64 write_status = device()->
write(
reinterpret_cast<const char *
>(compressed.data()), compressed.size());
966 if (write_status > 0) {
968 }
else if (write_status == -1) {
969 qWarning(
"Write error: %s\n", qUtf8Printable(device()->errorString()));
976QVariant QJpegXLHandler::option(ImageOption option)
const
978 if (!supportsOption(option)) {
982 if (option == Quality) {
986 if (!ensureParsed()) {
987#ifdef JXL_QT_AUTOTRANSFORM
988 if (option == ImageTransformation) {
989 return int(m_transformations);
997 return QSize(m_basicinfo.xsize, m_basicinfo.ysize);
999 if (m_basicinfo.have_animation) {
1004#ifdef JXL_QT_AUTOTRANSFORM
1005 case ImageTransformation:
1006 if (m_basicinfo.orientation == JXL_ORIENT_IDENTITY) {
1008 }
else if (m_basicinfo.orientation == JXL_ORIENT_FLIP_HORIZONTAL) {
1010 }
else if (m_basicinfo.orientation == JXL_ORIENT_ROTATE_180) {
1012 }
else if (m_basicinfo.orientation == JXL_ORIENT_FLIP_VERTICAL) {
1014 }
else if (m_basicinfo.orientation == JXL_ORIENT_TRANSPOSE) {
1016 }
else if (m_basicinfo.orientation == JXL_ORIENT_ROTATE_90_CW) {
1018 }
else if (m_basicinfo.orientation == JXL_ORIENT_ANTI_TRANSPOSE) {
1020 }
else if (m_basicinfo.orientation == JXL_ORIENT_ROTATE_90_CCW) {
1032void QJpegXLHandler::setOption(ImageOption option,
const QVariant &value)
1036 m_quality = value.
toInt();
1037 if (m_quality > 100) {
1039 }
else if (m_quality < 0) {
1043#ifdef JXL_QT_AUTOTRANSFORM
1044 case ImageTransformation:
1045 if (
auto t = value.
toInt()) {
1057bool QJpegXLHandler::supportsOption(ImageOption option)
const
1059 auto supported = option == Quality || option == Size || option ==
Animation;
1060#ifdef JXL_QT_AUTOTRANSFORM
1061 supported = supported || option == ImageTransformation;
1066int QJpegXLHandler::imageCount()
const
1068 if (!ensureParsed()) {
1072 if (m_parseState == ParseJpegXLBasicInfoParsed) {
1073 if (!m_basicinfo.have_animation) {
1077 if (!ensureALLCounted()) {
1082 if (!m_framedelays.isEmpty()) {
1083 return m_framedelays.count();
1088int QJpegXLHandler::currentImageNumber()
const
1090 if (m_parseState == ParseJpegXLNotParsed) {
1094 if (m_parseState == ParseJpegXLError || m_parseState == ParseJpegXLBasicInfoParsed || !m_decoder) {
1098 return m_currentimage_index;
1101bool QJpegXLHandler::jumpToNextImage()
1103 if (!ensureALLCounted()) {
1107 if (m_framedelays.count() > 1) {
1108 m_currentimage_index++;
1110 if (m_currentimage_index >= m_framedelays.count()) {
1115 JxlDecoderSkipFrames(m_decoder, 1);
1119 m_parseState = ParseJpegXLSuccess;
1123bool QJpegXLHandler::jumpToImage(
int imageNumber)
1125 if (!ensureALLCounted()) {
1129 if (imageNumber < 0 || imageNumber >= m_framedelays.count()) {
1133 if (imageNumber == m_currentimage_index) {
1134 m_parseState = ParseJpegXLSuccess;
1138 if (imageNumber > m_currentimage_index) {
1139 JxlDecoderSkipFrames(m_decoder, imageNumber - m_currentimage_index);
1140 m_currentimage_index = imageNumber;
1141 m_parseState = ParseJpegXLSuccess;
1149 if (imageNumber > 0) {
1150 JxlDecoderSkipFrames(m_decoder, imageNumber);
1152 m_currentimage_index = imageNumber;
1153 m_parseState = ParseJpegXLSuccess;
1157int QJpegXLHandler::nextImageDelay()
const
1159 if (!ensureALLCounted()) {
1163 if (m_framedelays.count() < 2) {
1167 return m_next_image_delay;
1170int QJpegXLHandler::loopCount()
const
1172 if (!ensureParsed()) {
1176 if (m_basicinfo.have_animation) {
1177 return (m_basicinfo.animation.num_loops > 0) ? m_basicinfo.animation.num_loops - 1 : -1;
1183bool QJpegXLHandler::rewind()
1185 m_currentimage_index = 0;
1187 JxlDecoderReleaseInput(m_decoder);
1188 JxlDecoderRewind(m_decoder);
1190 if (JxlDecoderSetParallelRunner(m_decoder, JxlThreadParallelRunner, m_runner) != JXL_DEC_SUCCESS) {
1191 qWarning(
"ERROR: JxlDecoderSetParallelRunner failed");
1192 m_parseState = ParseJpegXLError;
1197 if (JxlDecoderSetInput(m_decoder,
reinterpret_cast<const uint8_t *
>(m_rawData.constData()), m_rawData.size()) != JXL_DEC_SUCCESS) {
1198 qWarning(
"ERROR: JxlDecoderSetInput failed");
1199 m_parseState = ParseJpegXLError;
1203 JxlDecoderCloseInput(m_decoder);
1205 if (m_basicinfo.uses_original_profile == JXL_FALSE && m_basicinfo.have_animation == JXL_FALSE) {
1206 if (JxlDecoderSubscribeEvents(m_decoder, JXL_DEC_COLOR_ENCODING | JXL_DEC_FULL_IMAGE) != JXL_DEC_SUCCESS) {
1207 qWarning(
"ERROR: JxlDecoderSubscribeEvents failed");
1208 m_parseState = ParseJpegXLError;
1212 JxlDecoderStatus
status = JxlDecoderProcessInput(m_decoder);
1213 if (
status != JXL_DEC_COLOR_ENCODING) {
1214 qWarning(
"Unexpected event %d instead of JXL_DEC_COLOR_ENCODING",
status);
1215 m_parseState = ParseJpegXLError;
1219#if JPEGXL_NUMERIC_VERSION >= JPEGXL_COMPUTE_NUMERIC_VERSION(0, 9, 0)
1220 const JxlCmsInterface *jxlcms = JxlGetDefaultCms();
1222 status = JxlDecoderSetCms(m_decoder, *jxlcms);
1223 if (
status != JXL_DEC_SUCCESS) {
1224 qWarning(
"JxlDecoderSetCms ERROR");
1227 qWarning(
"No JPEG XL CMS Interface");
1231 bool is_gray = m_basicinfo.num_color_channels == 1 && m_basicinfo.alpha_bits == 0;
1232 JxlColorEncoding color_encoding;
1233 JxlColorEncodingSetToSRGB(&color_encoding, is_gray ? JXL_TRUE : JXL_FALSE);
1234 JxlDecoderSetPreferredColorProfile(m_decoder, &color_encoding);
1236 if (JxlDecoderSubscribeEvents(m_decoder, JXL_DEC_FULL_IMAGE) != JXL_DEC_SUCCESS) {
1237 qWarning(
"ERROR: JxlDecoderSubscribeEvents failed");
1238 m_parseState = ParseJpegXLError;
1246bool QJpegXLHandler::decodeContainer()
1248#if JPEGXL_NUMERIC_VERSION >= JPEGXL_COMPUTE_NUMERIC_VERSION(0, 11, 0)
1249 if (m_basicinfo.have_container == JXL_FALSE) {
1253 const size_t len = m_rawData.size();
1255 m_parseState = ParseJpegXLError;
1259 const uint8_t *buf =
reinterpret_cast<const uint8_t *
>(m_rawData.constData());
1260 if (JxlSignatureCheck(buf, len) != JXL_SIG_CONTAINER) {
1264 JxlDecoderReleaseInput(m_decoder);
1265 JxlDecoderRewind(m_decoder);
1267 if (JxlDecoderSetInput(m_decoder, buf, len) != JXL_DEC_SUCCESS) {
1268 qWarning(
"ERROR: JxlDecoderSetInput failed");
1269 m_parseState = ParseJpegXLError;
1273 JxlDecoderCloseInput(m_decoder);
1275 if (JxlDecoderSetDecompressBoxes(m_decoder, JXL_TRUE) != JXL_DEC_SUCCESS) {
1276 qWarning(
"WARNING: JxlDecoderSetDecompressBoxes failed");
1279 if (JxlDecoderSubscribeEvents(m_decoder, JXL_DEC_BOX | JXL_DEC_BOX_COMPLETE) != JXL_DEC_SUCCESS) {
1280 qWarning(
"ERROR: JxlDecoderSubscribeEvents failed");
1281 m_parseState = ParseJpegXLError;
1285 bool search_exif =
true;
1286 bool search_xmp =
true;
1287 JxlBoxType box_type;
1292 while (search_exif || search_xmp) {
1293 JxlDecoderStatus
status = JxlDecoderProcessInput(m_decoder);
1295 case JXL_DEC_SUCCESS:
1296 search_exif =
false;
1300 status = JxlDecoderGetBoxType(m_decoder, box_type, JXL_TRUE);
1301 if (
status != JXL_DEC_SUCCESS) {
1302 qWarning(
"Error in JxlDecoderGetBoxType");
1303 m_parseState = ParseJpegXLError;
1307 if (box_type[0] ==
'E' && box_type[1] ==
'x' && box_type[2] ==
'i' && box_type[3] ==
'f' && search_exif) {
1308 search_exif =
false;
1309 if (!extractBox(exifBox, len)) {
1312 }
else if (box_type[0] ==
'x' && box_type[1] ==
'm' && box_type[2] ==
'l' && box_type[3] ==
' ' && search_xmp) {
1314 if (!extractBox(xmpBox, len)) {
1320 qWarning(
"JXL Metadata decoding error");
1321 m_parseState = ParseJpegXLError;
1324 case JXL_DEC_NEED_MORE_INPUT:
1325 qWarning(
"JXL metadata are probably incomplete");
1326 m_parseState = ParseJpegXLError;
1330 qWarning(
"Unexpected event %d instead of JXL_DEC_BOX",
status);
1331 m_parseState = ParseJpegXLError;
1337 if (xmpBox.
size() > 0) {
1341 if (exifBox.
size() > 4) {
1342 const char tiffHeaderBE[4] = {
'M',
'M', 0, 42};
1343 const char tiffHeaderLE[4] = {
'I',
'I', 42, 0};
1346 auto headerindexBE = exifBox.
indexOf(tiffBE);
1347 auto headerindexLE = exifBox.
indexOf(tiffLE);
1349 if (headerindexLE != -1) {
1350 if (headerindexBE == -1) {
1351 m_exif = exifBox.
mid(headerindexLE);
1353 m_exif = exifBox.
mid((headerindexLE <= headerindexBE) ? headerindexLE : headerindexBE);
1355 }
else if (headerindexBE != -1) {
1356 m_exif = exifBox.
mid(headerindexBE);
1358 qWarning(
"Exif box in JXL file doesn't have TIFF header");
1365bool QJpegXLHandler::extractBox(
QByteArray &output,
size_t container_size)
1367#if JPEGXL_NUMERIC_VERSION >= JPEGXL_COMPUTE_NUMERIC_VERSION(0, 11, 0)
1368 uint64_t rawboxsize = 0;
1369 JxlDecoderStatus
status = JxlDecoderGetBoxSizeRaw(m_decoder, &rawboxsize);
1370 if (
status != JXL_DEC_SUCCESS) {
1371 qWarning(
"ERROR: JxlDecoderGetBoxSizeRaw failed");
1372 m_parseState = ParseJpegXLError;
1376 if (rawboxsize > container_size) {
1377 qWarning(
"JXL metadata box is incomplete");
1378 m_parseState = ParseJpegXLError;
1382 output.
resize(rawboxsize);
1383 status = JxlDecoderSetBoxBuffer(m_decoder,
reinterpret_cast<uint8_t *
>(output.
data()), output.
size());
1384 if (
status != JXL_DEC_SUCCESS) {
1385 qWarning(
"ERROR: JxlDecoderSetBoxBuffer failed");
1386 m_parseState = ParseJpegXLError;
1391 status = JxlDecoderProcessInput(m_decoder);
1392 if (
status == JXL_DEC_BOX_NEED_MORE_OUTPUT) {
1393 size_t bytes_remains = JxlDecoderReleaseBoxBuffer(m_decoder);
1395 if (output.
size() > 4194304) {
1396 qWarning(
"JXL metadata box is too large");
1397 m_parseState = ParseJpegXLError;
1401 output.
append(16384,
'\0');
1402 size_t extension_size = 16384 + bytes_remains;
1403 uint8_t *extension_buffer =
reinterpret_cast<uint8_t *
>(output.
data()) + (output.
size() - extension_size);
1405 if (JxlDecoderSetBoxBuffer(m_decoder, extension_buffer, extension_size) != JXL_DEC_SUCCESS) {
1406 qWarning(
"ERROR: JxlDecoderSetBoxBuffer failed after JXL_DEC_BOX_NEED_MORE_OUTPUT");
1407 m_parseState = ParseJpegXLError;
1411 }
while (
status == JXL_DEC_BOX_NEED_MORE_OUTPUT);
1413 if (
status != JXL_DEC_BOX_COMPLETE) {
1414 qWarning(
"Unexpected event %d instead of JXL_DEC_BOX_COMPLETE",
status);
1415 m_parseState = ParseJpegXLError;
1419 size_t unused_bytes = JxlDecoderReleaseBoxBuffer(m_decoder);
1420 output.
chop(unused_bytes);
1427 if (format ==
"jxl") {
1439 if (device->
isReadable() && QJpegXLHandler::canRead(device)) {
1458#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
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