KImageFormats

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

KDE's Doxygen guidelines are available online.