1 /*
2 JPEG XL (JXL) support for QImage.
3
4 SPDX-FileCopyrightText: 2021 Daniel Novomesky <dnovomesky@gmail.com>
5
6 SPDX-License-Identifier: BSD-2-Clause
7 */
8
9 #include <QThread>
10 #include <QtGlobal>
11
12 #include "jxl_p.h"
13 #include <jxl/encode.h>
14 #include <jxl/thread_parallel_runner.h>
15
QJpegXLHandler()16 QJpegXLHandler::QJpegXLHandler()
17 : m_parseState(ParseJpegXLNotParsed)
18 , m_quality(90)
19 , m_currentimage_index(0)
20 , m_previousimage_index(-1)
21 , m_decoder(nullptr)
22 , m_runner(nullptr)
23 , m_next_image_delay(0)
24 , m_input_image_format(QImage::Format_Invalid)
25 , m_target_image_format(QImage::Format_Invalid)
26 , m_buffer_size(0)
27 {
28 }
29
~QJpegXLHandler()30 QJpegXLHandler::~QJpegXLHandler()
31 {
32 if (m_runner) {
33 JxlThreadParallelRunnerDestroy(m_runner);
34 }
35 if (m_decoder) {
36 JxlDecoderDestroy(m_decoder);
37 }
38 }
39
canRead() const40 bool QJpegXLHandler::canRead() const
41 {
42 if (m_parseState == ParseJpegXLNotParsed && !canRead(device())) {
43 return false;
44 }
45
46 if (m_parseState != ParseJpegXLError) {
47 setFormat("jxl");
48 return true;
49 }
50 return false;
51 }
52
canRead(QIODevice * device)53 bool QJpegXLHandler::canRead(QIODevice *device)
54 {
55 if (!device) {
56 return false;
57 }
58 QByteArray header = device->peek(32);
59 if (header.size() < 12) {
60 return false;
61 }
62
63 JxlSignature signature = JxlSignatureCheck((const uint8_t *)header.constData(), header.size());
64 if (signature == JXL_SIG_CODESTREAM || signature == JXL_SIG_CONTAINER) {
65 return true;
66 }
67 return false;
68 }
69
ensureParsed() const70 bool QJpegXLHandler::ensureParsed() const
71 {
72 if (m_parseState == ParseJpegXLSuccess || m_parseState == ParseJpegXLBasicInfoParsed) {
73 return true;
74 }
75 if (m_parseState == ParseJpegXLError) {
76 return false;
77 }
78
79 QJpegXLHandler *that = const_cast<QJpegXLHandler *>(this);
80
81 return that->ensureDecoder();
82 }
83
ensureALLCounted() const84 bool QJpegXLHandler::ensureALLCounted() const
85 {
86 if (!ensureParsed()) {
87 return false;
88 }
89
90 if (m_parseState == ParseJpegXLSuccess) {
91 return true;
92 }
93
94 QJpegXLHandler *that = const_cast<QJpegXLHandler *>(this);
95
96 return that->countALLFrames();
97 }
98
ensureDecoder()99 bool QJpegXLHandler::ensureDecoder()
100 {
101 if (m_decoder) {
102 return true;
103 }
104
105 m_rawData = device()->readAll();
106
107 if (m_rawData.isEmpty()) {
108 return false;
109 }
110
111 JxlSignature signature = JxlSignatureCheck((const uint8_t *)m_rawData.constData(), m_rawData.size());
112 if (signature != JXL_SIG_CODESTREAM && signature != JXL_SIG_CONTAINER) {
113 m_parseState = ParseJpegXLError;
114 return false;
115 }
116
117 m_decoder = JxlDecoderCreate(nullptr);
118 if (!m_decoder) {
119 qWarning("ERROR: JxlDecoderCreate failed");
120 m_parseState = ParseJpegXLError;
121 return false;
122 }
123
124 int num_worker_threads = QThread::idealThreadCount();
125 if (!m_runner && num_worker_threads >= 4) {
126 /* use half of the threads because plug-in is usually used in environment
127 * where application performs another tasks in backround (pre-load other images) */
128 num_worker_threads = num_worker_threads / 2;
129 num_worker_threads = qBound(2, num_worker_threads, 64);
130 m_runner = JxlThreadParallelRunnerCreate(nullptr, num_worker_threads);
131
132 if (JxlDecoderSetParallelRunner(m_decoder, JxlThreadParallelRunner, m_runner) != JXL_DEC_SUCCESS) {
133 qWarning("ERROR: JxlDecoderSetParallelRunner failed");
134 m_parseState = ParseJpegXLError;
135 return false;
136 }
137 }
138
139 if (JxlDecoderSetInput(m_decoder, (const uint8_t *)m_rawData.constData(), m_rawData.size()) != JXL_DEC_SUCCESS) {
140 qWarning("ERROR: JxlDecoderSetInput failed");
141 m_parseState = ParseJpegXLError;
142 return false;
143 }
144
145 JxlDecoderStatus status = JxlDecoderSubscribeEvents(m_decoder, JXL_DEC_BASIC_INFO | JXL_DEC_COLOR_ENCODING | JXL_DEC_FRAME);
146 if (status == JXL_DEC_ERROR) {
147 qWarning("ERROR: JxlDecoderSubscribeEvents failed");
148 m_parseState = ParseJpegXLError;
149 return false;
150 }
151
152 status = JxlDecoderProcessInput(m_decoder);
153 if (status == JXL_DEC_ERROR) {
154 qWarning("ERROR: JXL decoding failed");
155 m_parseState = ParseJpegXLError;
156 return false;
157 }
158 if (status == JXL_DEC_NEED_MORE_INPUT) {
159 qWarning("ERROR: JXL data incomplete");
160 m_parseState = ParseJpegXLError;
161 return false;
162 }
163
164 status = JxlDecoderGetBasicInfo(m_decoder, &m_basicinfo);
165 if (status != JXL_DEC_SUCCESS) {
166 qWarning("ERROR: JXL basic info not available");
167 m_parseState = ParseJpegXLError;
168 return false;
169 }
170
171 if (m_basicinfo.xsize == 0 || m_basicinfo.ysize == 0) {
172 qWarning("ERROR: JXL image has zero dimensions");
173 m_parseState = ParseJpegXLError;
174 return false;
175 }
176
177 if (m_basicinfo.xsize > 32768 || m_basicinfo.ysize > 32768) {
178 qWarning("JXL image (%dx%d) is too large", m_basicinfo.xsize, m_basicinfo.ysize);
179 m_parseState = ParseJpegXLError;
180 return false;
181 } else if (sizeof(void *) <= 4) {
182 /* On 32bit systems, there is limited address space.
183 * We skip imagess bigger than 8192 x 8192 pixels.
184 * If we don't do it, abort() in libjxl may close whole application */
185 if ((m_basicinfo.xsize * m_basicinfo.ysize) > 67108864) {
186 qWarning("JXL image (%dx%d) is too large for 32bit build of the plug-in", m_basicinfo.xsize, m_basicinfo.ysize);
187 m_parseState = ParseJpegXLError;
188 return false;
189 }
190 }
191
192 m_parseState = ParseJpegXLBasicInfoParsed;
193 return true;
194 }
195
countALLFrames()196 bool QJpegXLHandler::countALLFrames()
197 {
198 if (m_parseState != ParseJpegXLBasicInfoParsed) {
199 return false;
200 }
201
202 JxlDecoderStatus status = JxlDecoderProcessInput(m_decoder);
203 if (status != JXL_DEC_COLOR_ENCODING) {
204 qWarning("Unexpected event %d instead of JXL_DEC_COLOR_ENCODING", status);
205 m_parseState = ParseJpegXLError;
206 return false;
207 }
208
209 JxlColorEncoding color_encoding;
210 if (m_basicinfo.uses_original_profile == JXL_FALSE) {
211 JxlColorEncodingSetToSRGB(&color_encoding, JXL_FALSE);
212 JxlDecoderSetPreferredColorProfile(m_decoder, &color_encoding);
213 }
214
215 bool loadalpha;
216
217 if (m_basicinfo.alpha_bits > 0) {
218 loadalpha = true;
219 } else {
220 loadalpha = false;
221 }
222
223 m_input_pixel_format.endianness = JXL_NATIVE_ENDIAN;
224 m_input_pixel_format.align = 0;
225 m_input_pixel_format.num_channels = 4;
226
227 if (m_basicinfo.bits_per_sample > 8) { // high bit depth
228 m_input_pixel_format.data_type = JXL_TYPE_UINT16;
229 m_buffer_size = 8 * (size_t)m_basicinfo.xsize * (size_t)m_basicinfo.ysize;
230 m_input_image_format = QImage::Format_RGBA64;
231
232 if (loadalpha) {
233 m_target_image_format = QImage::Format_RGBA64;
234 } else {
235 m_target_image_format = QImage::Format_RGBX64;
236 }
237 } else { // 8bit depth
238 m_input_pixel_format.data_type = JXL_TYPE_UINT8;
239 m_buffer_size = 4 * (size_t)m_basicinfo.xsize * (size_t)m_basicinfo.ysize;
240 m_input_image_format = QImage::Format_RGBA8888;
241
242 if (loadalpha) {
243 m_target_image_format = QImage::Format_ARGB32;
244 } else {
245 m_target_image_format = QImage::Format_RGB32;
246 }
247 }
248
249 status = JxlDecoderGetColorAsEncodedProfile(m_decoder, &m_input_pixel_format, JXL_COLOR_PROFILE_TARGET_DATA, &color_encoding);
250
251 if (status == JXL_DEC_SUCCESS && color_encoding.color_space == JXL_COLOR_SPACE_RGB && color_encoding.white_point == JXL_WHITE_POINT_D65
252 && color_encoding.primaries == JXL_PRIMARIES_SRGB && color_encoding.transfer_function == JXL_TRANSFER_FUNCTION_SRGB) {
253 m_colorspace = QColorSpace(QColorSpace::SRgb);
254 } else {
255 size_t icc_size = 0;
256 if (JxlDecoderGetICCProfileSize(m_decoder, &m_input_pixel_format, JXL_COLOR_PROFILE_TARGET_DATA, &icc_size) == JXL_DEC_SUCCESS) {
257 if (icc_size > 0) {
258 QByteArray icc_data((int)icc_size, 0);
259 if (JxlDecoderGetColorAsICCProfile(m_decoder, &m_input_pixel_format, JXL_COLOR_PROFILE_TARGET_DATA, (uint8_t *)icc_data.data(), icc_data.size())
260 == JXL_DEC_SUCCESS) {
261 m_colorspace = QColorSpace::fromIccProfile(icc_data);
262
263 if (!m_colorspace.isValid()) {
264 qWarning("JXL image has Qt-unsupported or invalid ICC profile!");
265 }
266 } else {
267 qWarning("Failed to obtain data from JPEG XL decoder");
268 }
269 } else {
270 qWarning("Empty ICC data");
271 }
272 } else {
273 qWarning("no ICC, other color profile");
274 }
275 }
276
277 if (m_basicinfo.have_animation) { // count all frames
278 JxlFrameHeader frame_header;
279 int delay;
280
281 for (status = JxlDecoderProcessInput(m_decoder); status != JXL_DEC_SUCCESS; status = JxlDecoderProcessInput(m_decoder)) {
282 if (status != JXL_DEC_FRAME) {
283 switch (status) {
284 case JXL_DEC_ERROR:
285 qWarning("ERROR: JXL decoding failed");
286 break;
287 case JXL_DEC_NEED_MORE_INPUT:
288 qWarning("ERROR: JXL data incomplete");
289 break;
290 default:
291 qWarning("Unexpected event %d instead of JXL_DEC_FRAME", status);
292 break;
293 }
294 m_parseState = ParseJpegXLError;
295 return false;
296 }
297
298 if (JxlDecoderGetFrameHeader(m_decoder, &frame_header) != JXL_DEC_SUCCESS) {
299 qWarning("ERROR: JxlDecoderGetFrameHeader failed");
300 m_parseState = ParseJpegXLError;
301 return false;
302 }
303
304 if (m_basicinfo.animation.tps_denominator > 0 && m_basicinfo.animation.tps_numerator > 0) {
305 delay = (int)(0.5 + 1000.0 * frame_header.duration * m_basicinfo.animation.tps_denominator / m_basicinfo.animation.tps_numerator);
306 } else {
307 delay = 0;
308 }
309
310 m_framedelays.append(delay);
311 }
312
313 if (m_framedelays.isEmpty()) {
314 qWarning("no frames loaded by the JXL plug-in");
315 m_parseState = ParseJpegXLError;
316 return false;
317 }
318
319 if (m_framedelays.count() == 1) {
320 qWarning("JXL file was marked as animation but it has only one frame.");
321 m_basicinfo.have_animation = JXL_FALSE;
322 }
323 } else { // static picture
324 m_framedelays.resize(1);
325 m_framedelays[0] = 0;
326 }
327
328 if (!rewind()) {
329 return false;
330 }
331
332 m_next_image_delay = m_framedelays[0];
333 m_parseState = ParseJpegXLSuccess;
334 return true;
335 }
336
decode_one_frame()337 bool QJpegXLHandler::decode_one_frame()
338 {
339 JxlDecoderStatus status = JxlDecoderProcessInput(m_decoder);
340 if (status != JXL_DEC_NEED_IMAGE_OUT_BUFFER) {
341 qWarning("Unexpected event %d instead of JXL_DEC_NEED_IMAGE_OUT_BUFFER", status);
342 m_parseState = ParseJpegXLError;
343 return false;
344 }
345
346 m_current_image = QImage(m_basicinfo.xsize, m_basicinfo.ysize, m_input_image_format);
347 if (m_current_image.isNull()) {
348 qWarning("Memory cannot be allocated");
349 m_parseState = ParseJpegXLError;
350 return false;
351 }
352
353 m_current_image.setColorSpace(m_colorspace);
354
355 if (JxlDecoderSetImageOutBuffer(m_decoder, &m_input_pixel_format, m_current_image.bits(), m_buffer_size) != JXL_DEC_SUCCESS) {
356 qWarning("ERROR: JxlDecoderSetImageOutBuffer failed");
357 m_parseState = ParseJpegXLError;
358 return false;
359 }
360
361 status = JxlDecoderProcessInput(m_decoder);
362 if (status != JXL_DEC_FULL_IMAGE) {
363 qWarning("Unexpected event %d instead of JXL_DEC_FULL_IMAGE", status);
364 m_parseState = ParseJpegXLError;
365 return false;
366 }
367
368 if (m_target_image_format != m_input_image_format) {
369 m_current_image.convertTo(m_target_image_format);
370 }
371
372 m_next_image_delay = m_framedelays[m_currentimage_index];
373 m_previousimage_index = m_currentimage_index;
374
375 if (m_framedelays.count() > 1) {
376 m_currentimage_index++;
377
378 if (m_currentimage_index >= m_framedelays.count()) {
379 if (!rewind()) {
380 return false;
381 }
382 }
383 }
384
385 return true;
386 }
387
read(QImage * image)388 bool QJpegXLHandler::read(QImage *image)
389 {
390 if (!ensureALLCounted()) {
391 return false;
392 }
393
394 if (m_currentimage_index == m_previousimage_index) {
395 *image = m_current_image;
396 return jumpToNextImage();
397 }
398
399 if (decode_one_frame()) {
400 *image = m_current_image;
401 return true;
402 } else {
403 return false;
404 }
405 }
406
write(const QImage & image)407 bool QJpegXLHandler::write(const QImage &image)
408 {
409 if (image.format() == QImage::Format_Invalid) {
410 qWarning("No image data to save");
411 return false;
412 }
413
414 if ((image.width() > 32768) || (image.height() > 32768)) {
415 qWarning("Image is too large");
416 return false;
417 }
418
419 JxlEncoder *encoder = JxlEncoderCreate(nullptr);
420 if (!encoder) {
421 qWarning("Failed to create Jxl encoder");
422 return false;
423 }
424
425 void *runner = nullptr;
426 int num_worker_threads = qBound(1, QThread::idealThreadCount(), 64);
427
428 if (num_worker_threads > 1) {
429 runner = JxlThreadParallelRunnerCreate(nullptr, num_worker_threads);
430 if (JxlEncoderSetParallelRunner(encoder, JxlThreadParallelRunner, runner) != JXL_ENC_SUCCESS) {
431 qWarning("JxlEncoderSetParallelRunner failed");
432 JxlThreadParallelRunnerDestroy(runner);
433 JxlEncoderDestroy(encoder);
434 return false;
435 }
436 }
437
438 JxlEncoderOptions *encoder_options = JxlEncoderOptionsCreate(encoder, nullptr);
439
440 if (m_quality > 100) {
441 m_quality = 100;
442 } else if (m_quality < 0) {
443 m_quality = 90;
444 }
445
446 JxlEncoderOptionsSetDistance(encoder_options, (100.0f - m_quality) / 10.0f);
447
448 JxlEncoderOptionsSetLossless(encoder_options, (m_quality == 100) ? JXL_TRUE : JXL_FALSE);
449
450 JxlBasicInfo output_info;
451 JxlEncoderInitBasicInfo(&output_info);
452
453 JxlColorEncoding color_profile;
454 JxlColorEncodingSetToSRGB(&color_profile, JXL_FALSE);
455
456 bool convert_color_profile;
457 QByteArray iccprofile;
458
459 if (image.colorSpace().isValid()) {
460 if (image.colorSpace().primaries() != QColorSpace::Primaries::SRgb || image.colorSpace().transferFunction() != QColorSpace::TransferFunction::SRgb) {
461 convert_color_profile = true;
462 } else {
463 convert_color_profile = false;
464 }
465 } else { // no profile or Qt-unsupported ICC profile
466 convert_color_profile = false;
467 iccprofile = image.colorSpace().iccProfile();
468 if (iccprofile.size() > 0) {
469 output_info.uses_original_profile = 1;
470 }
471 }
472
473 JxlPixelFormat pixel_format;
474 QImage::Format tmpformat;
475 JxlEncoderStatus status;
476
477 pixel_format.data_type = JXL_TYPE_UINT16;
478 pixel_format.endianness = JXL_NATIVE_ENDIAN;
479 pixel_format.align = 0;
480
481 if (image.hasAlphaChannel()) {
482 tmpformat = QImage::Format_RGBA64;
483 pixel_format.num_channels = 4;
484 output_info.alpha_bits = 16;
485 output_info.num_extra_channels = 1;
486 } else {
487 tmpformat = QImage::Format_RGBX64;
488 pixel_format.num_channels = 3;
489 output_info.alpha_bits = 0;
490 }
491
492 const QImage tmpimage =
493 convert_color_profile ? image.convertToFormat(tmpformat).convertedToColorSpace(QColorSpace(QColorSpace::SRgb)) : image.convertToFormat(tmpformat);
494
495 const size_t xsize = tmpimage.width();
496 const size_t ysize = tmpimage.height();
497 const size_t buffer_size = 2 * pixel_format.num_channels * xsize * ysize;
498
499 if (xsize == 0 || ysize == 0 || tmpimage.isNull()) {
500 qWarning("Unable to allocate memory for output image");
501 if (runner) {
502 JxlThreadParallelRunnerDestroy(runner);
503 }
504 JxlEncoderDestroy(encoder);
505 return false;
506 }
507
508 output_info.xsize = tmpimage.width();
509 output_info.ysize = tmpimage.height();
510 output_info.bits_per_sample = 16;
511 output_info.intensity_target = 255.0f;
512 output_info.orientation = JXL_ORIENT_IDENTITY;
513 output_info.num_color_channels = 3;
514 output_info.animation.tps_numerator = 10;
515 output_info.animation.tps_denominator = 1;
516
517 status = JxlEncoderSetBasicInfo(encoder, &output_info);
518 if (status != JXL_ENC_SUCCESS) {
519 qWarning("JxlEncoderSetBasicInfo failed!");
520 if (runner) {
521 JxlThreadParallelRunnerDestroy(runner);
522 }
523 JxlEncoderDestroy(encoder);
524 return false;
525 }
526
527 if (!convert_color_profile && iccprofile.size() > 0) {
528 status = JxlEncoderSetICCProfile(encoder, (const uint8_t *)iccprofile.constData(), iccprofile.size());
529 if (status != JXL_ENC_SUCCESS) {
530 qWarning("JxlEncoderSetICCProfile failed!");
531 if (runner) {
532 JxlThreadParallelRunnerDestroy(runner);
533 }
534 JxlEncoderDestroy(encoder);
535 return false;
536 }
537 } else {
538 status = JxlEncoderSetColorEncoding(encoder, &color_profile);
539 if (status != JXL_ENC_SUCCESS) {
540 qWarning("JxlEncoderSetColorEncoding failed!");
541 if (runner) {
542 JxlThreadParallelRunnerDestroy(runner);
543 }
544 JxlEncoderDestroy(encoder);
545 return false;
546 }
547 }
548
549 if (image.hasAlphaChannel()) {
550 status = JxlEncoderAddImageFrame(encoder_options, &pixel_format, (void *)tmpimage.constBits(), buffer_size);
551 } else {
552 uint16_t *tmp_buffer = new (std::nothrow) uint16_t[3 * xsize * ysize];
553 if (!tmp_buffer) {
554 qWarning("Memory allocation error");
555 if (runner) {
556 JxlThreadParallelRunnerDestroy(runner);
557 }
558 JxlEncoderDestroy(encoder);
559 return false;
560 }
561
562 uint16_t *dest_pixels = tmp_buffer;
563 for (int y = 0; y < tmpimage.height(); y++) {
564 const uint16_t *src_pixels = reinterpret_cast<const uint16_t *>(tmpimage.constScanLine(y));
565 for (int x = 0; x < tmpimage.width(); x++) {
566 // R
567 *dest_pixels = *src_pixels;
568 dest_pixels++;
569 src_pixels++;
570 // G
571 *dest_pixels = *src_pixels;
572 dest_pixels++;
573 src_pixels++;
574 // B
575 *dest_pixels = *src_pixels;
576 dest_pixels++;
577 src_pixels += 2; // skipalpha
578 }
579 }
580 status = JxlEncoderAddImageFrame(encoder_options, &pixel_format, (void *)tmp_buffer, buffer_size);
581 delete[] tmp_buffer;
582 }
583
584 if (status == JXL_ENC_ERROR) {
585 qWarning("JxlEncoderAddImageFrame failed!");
586 if (runner) {
587 JxlThreadParallelRunnerDestroy(runner);
588 }
589 JxlEncoderDestroy(encoder);
590 return false;
591 }
592
593 JxlEncoderCloseInput(encoder);
594
595 std::vector<uint8_t> compressed;
596 compressed.resize(4096);
597 size_t offset = 0;
598 uint8_t *next_out;
599 size_t avail_out;
600 do {
601 next_out = compressed.data() + offset;
602 avail_out = compressed.size() - offset;
603 status = JxlEncoderProcessOutput(encoder, &next_out, &avail_out);
604
605 if (status == JXL_ENC_NEED_MORE_OUTPUT) {
606 offset = next_out - compressed.data();
607 compressed.resize(compressed.size() * 2);
608 } else if (status == JXL_ENC_ERROR) {
609 qWarning("JxlEncoderProcessOutput failed!");
610 if (runner) {
611 JxlThreadParallelRunnerDestroy(runner);
612 }
613 JxlEncoderDestroy(encoder);
614 return false;
615 }
616 } while (status != JXL_ENC_SUCCESS);
617
618 if (runner) {
619 JxlThreadParallelRunnerDestroy(runner);
620 }
621 JxlEncoderDestroy(encoder);
622
623 compressed.resize(next_out - compressed.data());
624
625 if (compressed.size() > 0) {
626 qint64 write_status = device()->write((const char *)compressed.data(), compressed.size());
627
628 if (write_status > 0) {
629 return true;
630 } else if (write_status == -1) {
631 qWarning("Write error: %s\n", qUtf8Printable(device()->errorString()));
632 }
633 }
634
635 return false;
636 }
637
option(ImageOption option) const638 QVariant QJpegXLHandler::option(ImageOption option) const
639 {
640 if (option == Quality) {
641 return m_quality;
642 }
643
644 if (!supportsOption(option) || !ensureParsed()) {
645 return QVariant();
646 }
647
648 switch (option) {
649 case Size:
650 return QSize(m_basicinfo.xsize, m_basicinfo.ysize);
651 case Animation:
652 if (m_basicinfo.have_animation) {
653 return true;
654 } else {
655 return false;
656 }
657 default:
658 return QVariant();
659 }
660 }
661
setOption(ImageOption option,const QVariant & value)662 void QJpegXLHandler::setOption(ImageOption option, const QVariant &value)
663 {
664 switch (option) {
665 case Quality:
666 m_quality = value.toInt();
667 if (m_quality > 100) {
668 m_quality = 100;
669 } else if (m_quality < 0) {
670 m_quality = 90;
671 }
672 return;
673 default:
674 break;
675 }
676 QImageIOHandler::setOption(option, value);
677 }
678
supportsOption(ImageOption option) const679 bool QJpegXLHandler::supportsOption(ImageOption option) const
680 {
681 return option == Quality || option == Size || option == Animation;
682 }
683
imageCount() const684 int QJpegXLHandler::imageCount() const
685 {
686 if (!ensureParsed()) {
687 return 0;
688 }
689
690 if (m_parseState == ParseJpegXLBasicInfoParsed) {
691 if (!m_basicinfo.have_animation) {
692 return 1;
693 }
694
695 if (!ensureALLCounted()) {
696 return 0;
697 }
698 }
699
700 if (!m_framedelays.isEmpty()) {
701 return m_framedelays.count();
702 }
703 return 0;
704 }
705
currentImageNumber() const706 int QJpegXLHandler::currentImageNumber() const
707 {
708 if (m_parseState == ParseJpegXLNotParsed) {
709 return -1;
710 }
711
712 if (m_parseState == ParseJpegXLError || m_parseState == ParseJpegXLBasicInfoParsed || !m_decoder) {
713 return 0;
714 }
715
716 return m_currentimage_index;
717 }
718
jumpToNextImage()719 bool QJpegXLHandler::jumpToNextImage()
720 {
721 if (!ensureALLCounted()) {
722 return false;
723 }
724
725 if (m_framedelays.count() > 1) {
726 m_currentimage_index++;
727
728 if (m_currentimage_index >= m_framedelays.count()) {
729 if (!rewind()) {
730 return false;
731 }
732 } else {
733 JxlDecoderSkipFrames(m_decoder, 1);
734 }
735 }
736
737 return true;
738 }
739
jumpToImage(int imageNumber)740 bool QJpegXLHandler::jumpToImage(int imageNumber)
741 {
742 if (!ensureALLCounted()) {
743 return false;
744 }
745
746 if (imageNumber < 0 || imageNumber >= m_framedelays.count()) {
747 return false;
748 }
749
750 if (imageNumber == m_currentimage_index) {
751 return true;
752 }
753
754 if (imageNumber > m_currentimage_index) {
755 JxlDecoderSkipFrames(m_decoder, imageNumber - m_currentimage_index);
756 m_currentimage_index = imageNumber;
757 return true;
758 }
759
760 if (!rewind()) {
761 return false;
762 }
763
764 if (imageNumber > 0) {
765 JxlDecoderSkipFrames(m_decoder, imageNumber);
766 }
767 m_currentimage_index = imageNumber;
768 return true;
769 }
770
nextImageDelay() const771 int QJpegXLHandler::nextImageDelay() const
772 {
773 if (!ensureALLCounted()) {
774 return 0;
775 }
776
777 if (m_framedelays.count() < 2) {
778 return 0;
779 }
780
781 return m_next_image_delay;
782 }
783
loopCount() const784 int QJpegXLHandler::loopCount() const
785 {
786 if (!ensureParsed()) {
787 return 0;
788 }
789
790 if (m_basicinfo.have_animation) {
791 return 1;
792 } else {
793 return 0;
794 }
795 }
796
rewind()797 bool QJpegXLHandler::rewind()
798 {
799 m_currentimage_index = 0;
800
801 JxlDecoderReleaseInput(m_decoder);
802 JxlDecoderRewind(m_decoder);
803 if (m_runner) {
804 if (JxlDecoderSetParallelRunner(m_decoder, JxlThreadParallelRunner, m_runner) != JXL_DEC_SUCCESS) {
805 qWarning("ERROR: JxlDecoderSetParallelRunner failed");
806 m_parseState = ParseJpegXLError;
807 return false;
808 }
809 }
810
811 if (JxlDecoderSetInput(m_decoder, (const uint8_t *)m_rawData.constData(), m_rawData.size()) != JXL_DEC_SUCCESS) {
812 qWarning("ERROR: JxlDecoderSetInput failed");
813 m_parseState = ParseJpegXLError;
814 return false;
815 }
816
817 if (m_basicinfo.uses_original_profile) {
818 if (JxlDecoderSubscribeEvents(m_decoder, JXL_DEC_FULL_IMAGE) != JXL_DEC_SUCCESS) {
819 qWarning("ERROR: JxlDecoderSubscribeEvents failed");
820 m_parseState = ParseJpegXLError;
821 return false;
822 }
823 } else {
824 if (JxlDecoderSubscribeEvents(m_decoder, JXL_DEC_COLOR_ENCODING | JXL_DEC_FULL_IMAGE) != JXL_DEC_SUCCESS) {
825 qWarning("ERROR: JxlDecoderSubscribeEvents failed");
826 m_parseState = ParseJpegXLError;
827 return false;
828 }
829
830 JxlDecoderStatus status = JxlDecoderProcessInput(m_decoder);
831 if (status != JXL_DEC_COLOR_ENCODING) {
832 qWarning("Unexpected event %d instead of JXL_DEC_COLOR_ENCODING", status);
833 m_parseState = ParseJpegXLError;
834 return false;
835 }
836
837 JxlColorEncoding color_encoding;
838 JxlColorEncodingSetToSRGB(&color_encoding, JXL_FALSE);
839 JxlDecoderSetPreferredColorProfile(m_decoder, &color_encoding);
840 }
841
842 return true;
843 }
844
capabilities(QIODevice * device,const QByteArray & format) const845 QImageIOPlugin::Capabilities QJpegXLPlugin::capabilities(QIODevice *device, const QByteArray &format) const
846 {
847 if (format == "jxl") {
848 return Capabilities(CanRead | CanWrite);
849 }
850
851 if (!format.isEmpty()) {
852 return {};
853 }
854 if (!device->isOpen()) {
855 return {};
856 }
857
858 Capabilities cap;
859 if (device->isReadable() && QJpegXLHandler::canRead(device)) {
860 cap |= CanRead;
861 }
862
863 if (device->isWritable()) {
864 cap |= CanWrite;
865 }
866
867 return cap;
868 }
869
create(QIODevice * device,const QByteArray & format) const870 QImageIOHandler *QJpegXLPlugin::create(QIODevice *device, const QByteArray &format) const
871 {
872 QImageIOHandler *handler = new QJpegXLHandler;
873 handler->setDevice(device);
874 handler->setFormat(format);
875 return handler;
876 }
877