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