File indexing completed on 2024-04-28 15:25:41

0001 /*
0002     JPEG XL (JXL) support for QImage.
0003 
0004     SPDX-FileCopyrightText: 2021 Daniel Novomesky <dnovomesky@gmail.com>
0005 
0006     SPDX-License-Identifier: BSD-2-Clause
0007 */
0008 
0009 #include <QThread>
0010 #include <QtGlobal>
0011 
0012 #include "jxl_p.h"
0013 #include "util_p.h"
0014 
0015 #include <jxl/encode.h>
0016 #include <jxl/thread_parallel_runner.h>
0017 #include <string.h>
0018 
0019 QJpegXLHandler::QJpegXLHandler()
0020     : m_parseState(ParseJpegXLNotParsed)
0021     , m_quality(90)
0022     , m_currentimage_index(0)
0023     , m_previousimage_index(-1)
0024     , m_decoder(nullptr)
0025     , m_runner(nullptr)
0026     , m_next_image_delay(0)
0027     , m_input_image_format(QImage::Format_Invalid)
0028     , m_target_image_format(QImage::Format_Invalid)
0029     , m_buffer_size(0)
0030 {
0031 }
0032 
0033 QJpegXLHandler::~QJpegXLHandler()
0034 {
0035     if (m_runner) {
0036         JxlThreadParallelRunnerDestroy(m_runner);
0037     }
0038     if (m_decoder) {
0039         JxlDecoderDestroy(m_decoder);
0040     }
0041 }
0042 
0043 bool QJpegXLHandler::canRead() const
0044 {
0045     if (m_parseState == ParseJpegXLNotParsed && !canRead(device())) {
0046         return false;
0047     }
0048 
0049     if (m_parseState != ParseJpegXLError) {
0050         setFormat("jxl");
0051 
0052         if (m_parseState == ParseJpegXLFinished) {
0053             return false;
0054         }
0055 
0056         return true;
0057     }
0058     return false;
0059 }
0060 
0061 bool QJpegXLHandler::canRead(QIODevice *device)
0062 {
0063     if (!device) {
0064         return false;
0065     }
0066     QByteArray header = device->peek(32);
0067     if (header.size() < 12) {
0068         return false;
0069     }
0070 
0071     JxlSignature signature = JxlSignatureCheck(reinterpret_cast<const uint8_t *>(header.constData()), header.size());
0072     if (signature == JXL_SIG_CODESTREAM || signature == JXL_SIG_CONTAINER) {
0073         return true;
0074     }
0075     return false;
0076 }
0077 
0078 bool QJpegXLHandler::ensureParsed() const
0079 {
0080     if (m_parseState == ParseJpegXLSuccess || m_parseState == ParseJpegXLBasicInfoParsed || m_parseState == ParseJpegXLFinished) {
0081         return true;
0082     }
0083     if (m_parseState == ParseJpegXLError) {
0084         return false;
0085     }
0086 
0087     QJpegXLHandler *that = const_cast<QJpegXLHandler *>(this);
0088 
0089     return that->ensureDecoder();
0090 }
0091 
0092 bool QJpegXLHandler::ensureALLCounted() const
0093 {
0094     if (!ensureParsed()) {
0095         return false;
0096     }
0097 
0098     if (m_parseState == ParseJpegXLSuccess || m_parseState == ParseJpegXLFinished) {
0099         return true;
0100     }
0101 
0102     QJpegXLHandler *that = const_cast<QJpegXLHandler *>(this);
0103 
0104     return that->countALLFrames();
0105 }
0106 
0107 bool QJpegXLHandler::ensureDecoder()
0108 {
0109     if (m_decoder) {
0110         return true;
0111     }
0112 
0113     m_rawData = device()->readAll();
0114 
0115     if (m_rawData.isEmpty()) {
0116         return false;
0117     }
0118 
0119     JxlSignature signature = JxlSignatureCheck(reinterpret_cast<const uint8_t *>(m_rawData.constData()), m_rawData.size());
0120     if (signature != JXL_SIG_CODESTREAM && signature != JXL_SIG_CONTAINER) {
0121         m_parseState = ParseJpegXLError;
0122         return false;
0123     }
0124 
0125     m_decoder = JxlDecoderCreate(nullptr);
0126     if (!m_decoder) {
0127         qWarning("ERROR: JxlDecoderCreate failed");
0128         m_parseState = ParseJpegXLError;
0129         return false;
0130     }
0131 
0132     int num_worker_threads = QThread::idealThreadCount();
0133     if (!m_runner && num_worker_threads >= 4) {
0134         /* use half of the threads because plug-in is usually used in environment
0135          * where application performs another tasks in backround (pre-load other images) */
0136         num_worker_threads = num_worker_threads / 2;
0137         num_worker_threads = qBound(2, num_worker_threads, 64);
0138         m_runner = JxlThreadParallelRunnerCreate(nullptr, num_worker_threads);
0139 
0140         if (JxlDecoderSetParallelRunner(m_decoder, JxlThreadParallelRunner, m_runner) != JXL_DEC_SUCCESS) {
0141             qWarning("ERROR: JxlDecoderSetParallelRunner failed");
0142             m_parseState = ParseJpegXLError;
0143             return false;
0144         }
0145     }
0146 
0147     if (JxlDecoderSetInput(m_decoder, reinterpret_cast<const uint8_t *>(m_rawData.constData()), m_rawData.size()) != JXL_DEC_SUCCESS) {
0148         qWarning("ERROR: JxlDecoderSetInput failed");
0149         m_parseState = ParseJpegXLError;
0150         return false;
0151     }
0152 
0153     JxlDecoderCloseInput(m_decoder);
0154 
0155     JxlDecoderStatus status = JxlDecoderSubscribeEvents(m_decoder, JXL_DEC_BASIC_INFO | JXL_DEC_COLOR_ENCODING | JXL_DEC_FRAME);
0156     if (status == JXL_DEC_ERROR) {
0157         qWarning("ERROR: JxlDecoderSubscribeEvents failed");
0158         m_parseState = ParseJpegXLError;
0159         return false;
0160     }
0161 
0162     status = JxlDecoderProcessInput(m_decoder);
0163     if (status == JXL_DEC_ERROR) {
0164         qWarning("ERROR: JXL decoding failed");
0165         m_parseState = ParseJpegXLError;
0166         return false;
0167     }
0168     if (status == JXL_DEC_NEED_MORE_INPUT) {
0169         qWarning("ERROR: JXL data incomplete");
0170         m_parseState = ParseJpegXLError;
0171         return false;
0172     }
0173 
0174     status = JxlDecoderGetBasicInfo(m_decoder, &m_basicinfo);
0175     if (status != JXL_DEC_SUCCESS) {
0176         qWarning("ERROR: JXL basic info not available");
0177         m_parseState = ParseJpegXLError;
0178         return false;
0179     }
0180 
0181     if (m_basicinfo.xsize == 0 || m_basicinfo.ysize == 0) {
0182         qWarning("ERROR: JXL image has zero dimensions");
0183         m_parseState = ParseJpegXLError;
0184         return false;
0185     }
0186 
0187     if (m_basicinfo.xsize > 65535 || m_basicinfo.ysize > 65535) {
0188         qWarning("JXL image (%dx%d) is too large", m_basicinfo.xsize, m_basicinfo.ysize);
0189         m_parseState = ParseJpegXLError;
0190         return false;
0191     }
0192 
0193     if (sizeof(void *) <= 4) {
0194         /* On 32bit systems, there is limited address space.
0195          * We skip imagess bigger than 8192 x 8192 pixels.
0196          * If we don't do it, abort() in libjxl may close whole application */
0197         if (m_basicinfo.xsize > ((8192 * 8192) / m_basicinfo.ysize)) {
0198             qWarning("JXL image (%dx%d) is too large for 32bit build of the plug-in", m_basicinfo.xsize, m_basicinfo.ysize);
0199             m_parseState = ParseJpegXLError;
0200             return false;
0201         }
0202     } else {
0203         /* On 64bit systems
0204          * We skip images bigger than 16384 x 16384 pixels.
0205          * It is an artificial limit not to use extreme amount of memory */
0206         if (m_basicinfo.xsize > ((16384 * 16384) / m_basicinfo.ysize)) {
0207             qWarning("JXL image (%dx%d) is bigger than security limit 256 megapixels", m_basicinfo.xsize, m_basicinfo.ysize);
0208             m_parseState = ParseJpegXLError;
0209             return false;
0210         }
0211     }
0212 
0213     m_parseState = ParseJpegXLBasicInfoParsed;
0214     return true;
0215 }
0216 
0217 bool QJpegXLHandler::countALLFrames()
0218 {
0219     if (m_parseState != ParseJpegXLBasicInfoParsed) {
0220         return false;
0221     }
0222 
0223     JxlDecoderStatus status = JxlDecoderProcessInput(m_decoder);
0224     if (status != JXL_DEC_COLOR_ENCODING) {
0225         qWarning("Unexpected event %d instead of JXL_DEC_COLOR_ENCODING", status);
0226         m_parseState = ParseJpegXLError;
0227         return false;
0228     }
0229 
0230     JxlColorEncoding color_encoding;
0231     if (m_basicinfo.uses_original_profile == JXL_FALSE) {
0232         JxlColorEncodingSetToSRGB(&color_encoding, JXL_FALSE);
0233         JxlDecoderSetPreferredColorProfile(m_decoder, &color_encoding);
0234     }
0235 
0236     bool loadalpha;
0237 
0238     if (m_basicinfo.alpha_bits > 0) {
0239         loadalpha = true;
0240     } else {
0241         loadalpha = false;
0242     }
0243 
0244     m_input_pixel_format.endianness = JXL_NATIVE_ENDIAN;
0245     m_input_pixel_format.align = 0;
0246     m_input_pixel_format.num_channels = 4;
0247 
0248     if (m_basicinfo.bits_per_sample > 8) { // high bit depth
0249         m_input_pixel_format.data_type = JXL_TYPE_UINT16;
0250         m_buffer_size = 8 * (size_t)m_basicinfo.xsize * (size_t)m_basicinfo.ysize;
0251         m_input_image_format = QImage::Format_RGBA64;
0252 
0253         if (loadalpha) {
0254             m_target_image_format = QImage::Format_RGBA64;
0255         } else {
0256             m_target_image_format = QImage::Format_RGBX64;
0257         }
0258     } else { // 8bit depth
0259         m_input_pixel_format.data_type = JXL_TYPE_UINT8;
0260         m_buffer_size = 4 * (size_t)m_basicinfo.xsize * (size_t)m_basicinfo.ysize;
0261         m_input_image_format = QImage::Format_RGBA8888;
0262 
0263         if (loadalpha) {
0264             m_target_image_format = QImage::Format_ARGB32;
0265         } else {
0266             m_target_image_format = QImage::Format_RGB32;
0267         }
0268     }
0269 
0270     status = JxlDecoderGetColorAsEncodedProfile(m_decoder,
0271 #if JPEGXL_NUMERIC_VERSION < JPEGXL_COMPUTE_NUMERIC_VERSION(0, 9, 0)
0272                                                 &m_input_pixel_format,
0273 #endif
0274                                                 JXL_COLOR_PROFILE_TARGET_DATA,
0275                                                 &color_encoding);
0276 
0277     if (status == JXL_DEC_SUCCESS && color_encoding.color_space == JXL_COLOR_SPACE_RGB && color_encoding.white_point == JXL_WHITE_POINT_D65
0278         && color_encoding.primaries == JXL_PRIMARIES_SRGB && color_encoding.transfer_function == JXL_TRANSFER_FUNCTION_SRGB) {
0279         m_colorspace = QColorSpace(QColorSpace::SRgb);
0280     } else {
0281         size_t icc_size = 0;
0282         if (JxlDecoderGetICCProfileSize(m_decoder,
0283 #if JPEGXL_NUMERIC_VERSION < JPEGXL_COMPUTE_NUMERIC_VERSION(0, 9, 0)
0284                                         &m_input_pixel_format,
0285 #endif
0286                                         JXL_COLOR_PROFILE_TARGET_DATA,
0287                                         &icc_size)
0288             == JXL_DEC_SUCCESS) {
0289             if (icc_size > 0) {
0290                 QByteArray icc_data(icc_size, 0);
0291                 if (JxlDecoderGetColorAsICCProfile(m_decoder,
0292 #if JPEGXL_NUMERIC_VERSION < JPEGXL_COMPUTE_NUMERIC_VERSION(0, 9, 0)
0293                                                    &m_input_pixel_format,
0294 #endif
0295                                                    JXL_COLOR_PROFILE_TARGET_DATA,
0296                                                    reinterpret_cast<uint8_t *>(icc_data.data()),
0297                                                    icc_data.size())
0298                     == JXL_DEC_SUCCESS) {
0299                     m_colorspace = QColorSpace::fromIccProfile(icc_data);
0300 
0301                     if (!m_colorspace.isValid()) {
0302                         qWarning("JXL image has Qt-unsupported or invalid ICC profile!");
0303                     }
0304                 } else {
0305                     qWarning("Failed to obtain data from JPEG XL decoder");
0306                 }
0307             } else {
0308                 qWarning("Empty ICC data");
0309             }
0310         } else {
0311             qWarning("no ICC, other color profile");
0312         }
0313     }
0314 
0315     if (m_basicinfo.have_animation) { // count all frames
0316         JxlFrameHeader frame_header;
0317         int delay;
0318 
0319         for (status = JxlDecoderProcessInput(m_decoder); status != JXL_DEC_SUCCESS; status = JxlDecoderProcessInput(m_decoder)) {
0320             if (status != JXL_DEC_FRAME) {
0321                 switch (status) {
0322                 case JXL_DEC_ERROR:
0323                     qWarning("ERROR: JXL decoding failed");
0324                     break;
0325                 case JXL_DEC_NEED_MORE_INPUT:
0326                     qWarning("ERROR: JXL data incomplete");
0327                     break;
0328                 default:
0329                     qWarning("Unexpected event %d instead of JXL_DEC_FRAME", status);
0330                     break;
0331                 }
0332                 m_parseState = ParseJpegXLError;
0333                 return false;
0334             }
0335 
0336             if (JxlDecoderGetFrameHeader(m_decoder, &frame_header) != JXL_DEC_SUCCESS) {
0337                 qWarning("ERROR: JxlDecoderGetFrameHeader failed");
0338                 m_parseState = ParseJpegXLError;
0339                 return false;
0340             }
0341 
0342             if (m_basicinfo.animation.tps_denominator > 0 && m_basicinfo.animation.tps_numerator > 0) {
0343                 delay = (int)(0.5 + 1000.0 * frame_header.duration * m_basicinfo.animation.tps_denominator / m_basicinfo.animation.tps_numerator);
0344             } else {
0345                 delay = 0;
0346             }
0347 
0348             m_framedelays.append(delay);
0349         }
0350 
0351         if (m_framedelays.isEmpty()) {
0352             qWarning("no frames loaded by the JXL plug-in");
0353             m_parseState = ParseJpegXLError;
0354             return false;
0355         }
0356 
0357         if (m_framedelays.count() == 1) {
0358             qWarning("JXL file was marked as animation but it has only one frame.");
0359             m_basicinfo.have_animation = JXL_FALSE;
0360         }
0361     } else { // static picture
0362         m_framedelays.resize(1);
0363         m_framedelays[0] = 0;
0364     }
0365 
0366     if (!rewind()) {
0367         return false;
0368     }
0369 
0370     m_next_image_delay = m_framedelays[0];
0371     m_parseState = ParseJpegXLSuccess;
0372     return true;
0373 }
0374 
0375 bool QJpegXLHandler::decode_one_frame()
0376 {
0377     JxlDecoderStatus status = JxlDecoderProcessInput(m_decoder);
0378     if (status != JXL_DEC_NEED_IMAGE_OUT_BUFFER) {
0379         qWarning("Unexpected event %d instead of JXL_DEC_NEED_IMAGE_OUT_BUFFER", status);
0380         m_parseState = ParseJpegXLError;
0381         return false;
0382     }
0383 
0384     m_current_image = imageAlloc(m_basicinfo.xsize, m_basicinfo.ysize, m_input_image_format);
0385     if (m_current_image.isNull()) {
0386         qWarning("Memory cannot be allocated");
0387         m_parseState = ParseJpegXLError;
0388         return false;
0389     }
0390 
0391     m_current_image.setColorSpace(m_colorspace);
0392 
0393     if (JxlDecoderSetImageOutBuffer(m_decoder, &m_input_pixel_format, m_current_image.bits(), m_buffer_size) != JXL_DEC_SUCCESS) {
0394         qWarning("ERROR: JxlDecoderSetImageOutBuffer failed");
0395         m_parseState = ParseJpegXLError;
0396         return false;
0397     }
0398 
0399     status = JxlDecoderProcessInput(m_decoder);
0400     if (status != JXL_DEC_FULL_IMAGE) {
0401         qWarning("Unexpected event %d instead of JXL_DEC_FULL_IMAGE", status);
0402         m_parseState = ParseJpegXLError;
0403         return false;
0404     }
0405 
0406     if (m_target_image_format != m_input_image_format) {
0407         m_current_image.convertTo(m_target_image_format);
0408     }
0409 
0410     m_next_image_delay = m_framedelays[m_currentimage_index];
0411     m_previousimage_index = m_currentimage_index;
0412 
0413     if (m_framedelays.count() > 1) {
0414         m_currentimage_index++;
0415 
0416         if (m_currentimage_index >= m_framedelays.count()) {
0417             if (!rewind()) {
0418                 return false;
0419             }
0420 
0421             // all frames in animation have been read
0422             m_parseState = ParseJpegXLFinished;
0423         } else {
0424             m_parseState = ParseJpegXLSuccess;
0425         }
0426     } else {
0427         // the static image has been read
0428         m_parseState = ParseJpegXLFinished;
0429     }
0430 
0431     return true;
0432 }
0433 
0434 bool QJpegXLHandler::read(QImage *image)
0435 {
0436     if (!ensureALLCounted()) {
0437         return false;
0438     }
0439 
0440     if (m_currentimage_index == m_previousimage_index) {
0441         *image = m_current_image;
0442         return jumpToNextImage();
0443     }
0444 
0445     if (decode_one_frame()) {
0446         *image = m_current_image;
0447         return true;
0448     } else {
0449         return false;
0450     }
0451 }
0452 
0453 bool QJpegXLHandler::write(const QImage &image)
0454 {
0455     if (image.format() == QImage::Format_Invalid) {
0456         qWarning("No image data to save");
0457         return false;
0458     }
0459 
0460     if ((image.width() > 0) && (image.height() > 0)) {
0461         if ((image.width() > 65535) || (image.height() > 65535)) {
0462             qWarning("Image (%dx%d) is too large to save!", image.width(), image.height());
0463             return false;
0464         }
0465 
0466         if (sizeof(void *) <= 4) {
0467             if (image.width() > ((8192 * 8192) / image.height())) {
0468                 qWarning("Image (%dx%d) is too large save via 32bit build of JXL plug-in", image.width(), image.height());
0469                 return false;
0470             }
0471         } else {
0472             if (image.width() > ((16384 * 16384) / image.height())) {
0473                 qWarning("Image (%dx%d) will not be saved because it has more than 256 megapixels", image.width(), image.height());
0474                 return false;
0475             }
0476         }
0477     } else {
0478         qWarning("Image has zero dimension!");
0479         return false;
0480     }
0481 
0482     int save_depth = 8; // 8 or 16
0483     // depth detection
0484     switch (image.format()) {
0485     case QImage::Format_BGR30:
0486     case QImage::Format_A2BGR30_Premultiplied:
0487     case QImage::Format_RGB30:
0488     case QImage::Format_A2RGB30_Premultiplied:
0489     case QImage::Format_Grayscale16:
0490     case QImage::Format_RGBX64:
0491     case QImage::Format_RGBA64:
0492     case QImage::Format_RGBA64_Premultiplied:
0493         save_depth = 16;
0494         break;
0495     case QImage::Format_RGB32:
0496     case QImage::Format_ARGB32:
0497     case QImage::Format_ARGB32_Premultiplied:
0498     case QImage::Format_RGB888:
0499     case QImage::Format_RGBX8888:
0500     case QImage::Format_RGBA8888:
0501     case QImage::Format_RGBA8888_Premultiplied:
0502         save_depth = 8;
0503         break;
0504     default:
0505         if (image.depth() > 32) {
0506             save_depth = 16;
0507         } else {
0508             save_depth = 8;
0509         }
0510         break;
0511     }
0512 
0513     JxlEncoder *encoder = JxlEncoderCreate(nullptr);
0514     if (!encoder) {
0515         qWarning("Failed to create Jxl encoder");
0516         return false;
0517     }
0518 
0519     if (m_quality > 100) {
0520         m_quality = 100;
0521     } else if (m_quality < 0) {
0522         m_quality = 90;
0523     }
0524 
0525     JxlBasicInfo output_info;
0526     JxlEncoderInitBasicInfo(&output_info);
0527 
0528     bool convert_color_profile;
0529     QByteArray iccprofile;
0530 
0531     if (image.colorSpace().isValid() && (m_quality < 100)) {
0532         if (image.colorSpace().primaries() != QColorSpace::Primaries::SRgb || image.colorSpace().transferFunction() != QColorSpace::TransferFunction::SRgb) {
0533             convert_color_profile = true;
0534         } else {
0535             convert_color_profile = false;
0536         }
0537     } else { // lossless or no profile or Qt-unsupported ICC profile
0538         convert_color_profile = false;
0539         iccprofile = image.colorSpace().iccProfile();
0540         if (iccprofile.size() > 0 || m_quality == 100) {
0541             output_info.uses_original_profile = JXL_TRUE;
0542         }
0543     }
0544 
0545     if (save_depth == 16 && (image.hasAlphaChannel() || output_info.uses_original_profile)) {
0546         output_info.have_container = JXL_TRUE;
0547         JxlEncoderUseContainer(encoder, JXL_TRUE);
0548         JxlEncoderSetCodestreamLevel(encoder, 10);
0549     }
0550 
0551     void *runner = nullptr;
0552     int num_worker_threads = qBound(1, QThread::idealThreadCount(), 64);
0553 
0554     if (num_worker_threads > 1) {
0555         runner = JxlThreadParallelRunnerCreate(nullptr, num_worker_threads);
0556         if (JxlEncoderSetParallelRunner(encoder, JxlThreadParallelRunner, runner) != JXL_ENC_SUCCESS) {
0557             qWarning("JxlEncoderSetParallelRunner failed");
0558             JxlThreadParallelRunnerDestroy(runner);
0559             JxlEncoderDestroy(encoder);
0560             return false;
0561         }
0562     }
0563 
0564     JxlPixelFormat pixel_format;
0565     QImage::Format tmpformat;
0566     JxlEncoderStatus status;
0567 
0568     pixel_format.endianness = JXL_NATIVE_ENDIAN;
0569     pixel_format.align = 0;
0570 
0571     output_info.orientation = JXL_ORIENT_IDENTITY;
0572     output_info.num_color_channels = 3;
0573     output_info.animation.tps_numerator = 10;
0574     output_info.animation.tps_denominator = 1;
0575 
0576     if (save_depth > 8) { // 16bit depth
0577         pixel_format.data_type = JXL_TYPE_UINT16;
0578 
0579         output_info.bits_per_sample = 16;
0580 
0581         if (image.hasAlphaChannel()) {
0582             tmpformat = QImage::Format_RGBA64;
0583             pixel_format.num_channels = 4;
0584             output_info.alpha_bits = 16;
0585             output_info.num_extra_channels = 1;
0586         } else {
0587             tmpformat = QImage::Format_RGBX64;
0588             pixel_format.num_channels = 3;
0589             output_info.alpha_bits = 0;
0590         }
0591     } else { // 8bit depth
0592         pixel_format.data_type = JXL_TYPE_UINT8;
0593 
0594         output_info.bits_per_sample = 8;
0595 
0596         if (image.hasAlphaChannel()) {
0597             tmpformat = QImage::Format_RGBA8888;
0598             pixel_format.num_channels = 4;
0599             output_info.alpha_bits = 8;
0600             output_info.num_extra_channels = 1;
0601         } else {
0602             tmpformat = QImage::Format_RGB888;
0603             pixel_format.num_channels = 3;
0604             output_info.alpha_bits = 0;
0605         }
0606     }
0607 
0608     const QImage tmpimage =
0609         convert_color_profile ? image.convertToFormat(tmpformat).convertedToColorSpace(QColorSpace(QColorSpace::SRgb)) : image.convertToFormat(tmpformat);
0610 
0611     const size_t xsize = tmpimage.width();
0612     const size_t ysize = tmpimage.height();
0613     const size_t buffer_size = (save_depth > 8) ? (2 * pixel_format.num_channels * xsize * ysize) : (pixel_format.num_channels * xsize * ysize);
0614 
0615     if (xsize == 0 || ysize == 0 || tmpimage.isNull()) {
0616         qWarning("Unable to allocate memory for output image");
0617         if (runner) {
0618             JxlThreadParallelRunnerDestroy(runner);
0619         }
0620         JxlEncoderDestroy(encoder);
0621         return false;
0622     }
0623 
0624     output_info.xsize = tmpimage.width();
0625     output_info.ysize = tmpimage.height();
0626 
0627     status = JxlEncoderSetBasicInfo(encoder, &output_info);
0628     if (status != JXL_ENC_SUCCESS) {
0629         qWarning("JxlEncoderSetBasicInfo failed!");
0630         if (runner) {
0631             JxlThreadParallelRunnerDestroy(runner);
0632         }
0633         JxlEncoderDestroy(encoder);
0634         return false;
0635     }
0636 
0637     if (!convert_color_profile && iccprofile.size() > 0) {
0638         status = JxlEncoderSetICCProfile(encoder, reinterpret_cast<const uint8_t *>(iccprofile.constData()), iccprofile.size());
0639         if (status != JXL_ENC_SUCCESS) {
0640             qWarning("JxlEncoderSetICCProfile failed!");
0641             if (runner) {
0642                 JxlThreadParallelRunnerDestroy(runner);
0643             }
0644             JxlEncoderDestroy(encoder);
0645             return false;
0646         }
0647     } else {
0648         JxlColorEncoding color_profile;
0649         JxlColorEncodingSetToSRGB(&color_profile, JXL_FALSE);
0650 
0651         status = JxlEncoderSetColorEncoding(encoder, &color_profile);
0652         if (status != JXL_ENC_SUCCESS) {
0653             qWarning("JxlEncoderSetColorEncoding failed!");
0654             if (runner) {
0655                 JxlThreadParallelRunnerDestroy(runner);
0656             }
0657             JxlEncoderDestroy(encoder);
0658             return false;
0659         }
0660     }
0661 
0662     JxlEncoderFrameSettings *encoder_options = JxlEncoderFrameSettingsCreate(encoder, nullptr);
0663 
0664     JxlEncoderSetFrameDistance(encoder_options, (100.0f - m_quality) / 10.0f);
0665 
0666     JxlEncoderSetFrameLossless(encoder_options, (m_quality == 100) ? JXL_TRUE : JXL_FALSE);
0667 
0668     if (image.hasAlphaChannel() || ((save_depth == 8) && (xsize % 4 == 0))) {
0669         status = JxlEncoderAddImageFrame(encoder_options, &pixel_format, static_cast<const void *>(tmpimage.constBits()), buffer_size);
0670     } else {
0671         if (save_depth > 8) { // 16bit depth without alpha channel
0672             uint16_t *tmp_buffer = new (std::nothrow) uint16_t[3 * xsize * ysize];
0673             if (!tmp_buffer) {
0674                 qWarning("Memory allocation error");
0675                 if (runner) {
0676                     JxlThreadParallelRunnerDestroy(runner);
0677                 }
0678                 JxlEncoderDestroy(encoder);
0679                 return false;
0680             }
0681 
0682             uint16_t *dest_pixels = tmp_buffer;
0683             for (int y = 0; y < tmpimage.height(); y++) {
0684                 const uint16_t *src_pixels = reinterpret_cast<const uint16_t *>(tmpimage.constScanLine(y));
0685                 for (int x = 0; x < tmpimage.width(); x++) {
0686                     // R
0687                     *dest_pixels = *src_pixels;
0688                     dest_pixels++;
0689                     src_pixels++;
0690                     // G
0691                     *dest_pixels = *src_pixels;
0692                     dest_pixels++;
0693                     src_pixels++;
0694                     // B
0695                     *dest_pixels = *src_pixels;
0696                     dest_pixels++;
0697                     src_pixels += 2; // skipalpha
0698                 }
0699             }
0700             status = JxlEncoderAddImageFrame(encoder_options, &pixel_format, static_cast<const void *>(tmp_buffer), buffer_size);
0701             delete[] tmp_buffer;
0702         } else { // 8bit depth without alpha channel
0703             uchar *tmp_buffer8 = new (std::nothrow) uchar[3 * xsize * ysize];
0704             if (!tmp_buffer8) {
0705                 qWarning("Memory allocation error");
0706                 if (runner) {
0707                     JxlThreadParallelRunnerDestroy(runner);
0708                 }
0709                 JxlEncoderDestroy(encoder);
0710                 return false;
0711             }
0712 
0713             uchar *dest_pixels8 = tmp_buffer8;
0714             const size_t rowbytes = 3 * xsize;
0715             for (int y = 0; y < tmpimage.height(); y++) {
0716                 memcpy(dest_pixels8, tmpimage.constScanLine(y), rowbytes);
0717                 dest_pixels8 += rowbytes;
0718             }
0719             status = JxlEncoderAddImageFrame(encoder_options, &pixel_format, static_cast<const void *>(tmp_buffer8), buffer_size);
0720             delete[] tmp_buffer8;
0721         }
0722     }
0723 
0724     if (status == JXL_ENC_ERROR) {
0725         qWarning("JxlEncoderAddImageFrame failed!");
0726         if (runner) {
0727             JxlThreadParallelRunnerDestroy(runner);
0728         }
0729         JxlEncoderDestroy(encoder);
0730         return false;
0731     }
0732 
0733     JxlEncoderCloseInput(encoder);
0734 
0735     std::vector<uint8_t> compressed;
0736     compressed.resize(4096);
0737     size_t offset = 0;
0738     uint8_t *next_out;
0739     size_t avail_out;
0740     do {
0741         next_out = compressed.data() + offset;
0742         avail_out = compressed.size() - offset;
0743         status = JxlEncoderProcessOutput(encoder, &next_out, &avail_out);
0744 
0745         if (status == JXL_ENC_NEED_MORE_OUTPUT) {
0746             offset = next_out - compressed.data();
0747             compressed.resize(compressed.size() * 2);
0748         } else if (status == JXL_ENC_ERROR) {
0749             qWarning("JxlEncoderProcessOutput failed!");
0750             if (runner) {
0751                 JxlThreadParallelRunnerDestroy(runner);
0752             }
0753             JxlEncoderDestroy(encoder);
0754             return false;
0755         }
0756     } while (status != JXL_ENC_SUCCESS);
0757 
0758     if (runner) {
0759         JxlThreadParallelRunnerDestroy(runner);
0760     }
0761     JxlEncoderDestroy(encoder);
0762 
0763     compressed.resize(next_out - compressed.data());
0764 
0765     if (compressed.size() > 0) {
0766         qint64 write_status = device()->write(reinterpret_cast<const char *>(compressed.data()), compressed.size());
0767 
0768         if (write_status > 0) {
0769             return true;
0770         } else if (write_status == -1) {
0771             qWarning("Write error: %s\n", qUtf8Printable(device()->errorString()));
0772         }
0773     }
0774 
0775     return false;
0776 }
0777 
0778 QVariant QJpegXLHandler::option(ImageOption option) const
0779 {
0780     if (option == Quality) {
0781         return m_quality;
0782     }
0783 
0784     if (!supportsOption(option) || !ensureParsed()) {
0785         return QVariant();
0786     }
0787 
0788     switch (option) {
0789     case Size:
0790         return QSize(m_basicinfo.xsize, m_basicinfo.ysize);
0791     case Animation:
0792         if (m_basicinfo.have_animation) {
0793             return true;
0794         } else {
0795             return false;
0796         }
0797     default:
0798         return QVariant();
0799     }
0800 }
0801 
0802 void QJpegXLHandler::setOption(ImageOption option, const QVariant &value)
0803 {
0804     switch (option) {
0805     case Quality:
0806         m_quality = value.toInt();
0807         if (m_quality > 100) {
0808             m_quality = 100;
0809         } else if (m_quality < 0) {
0810             m_quality = 90;
0811         }
0812         return;
0813     default:
0814         break;
0815     }
0816     QImageIOHandler::setOption(option, value);
0817 }
0818 
0819 bool QJpegXLHandler::supportsOption(ImageOption option) const
0820 {
0821     return option == Quality || option == Size || option == Animation;
0822 }
0823 
0824 int QJpegXLHandler::imageCount() const
0825 {
0826     if (!ensureParsed()) {
0827         return 0;
0828     }
0829 
0830     if (m_parseState == ParseJpegXLBasicInfoParsed) {
0831         if (!m_basicinfo.have_animation) {
0832             return 1;
0833         }
0834 
0835         if (!ensureALLCounted()) {
0836             return 0;
0837         }
0838     }
0839 
0840     if (!m_framedelays.isEmpty()) {
0841         return m_framedelays.count();
0842     }
0843     return 0;
0844 }
0845 
0846 int QJpegXLHandler::currentImageNumber() const
0847 {
0848     if (m_parseState == ParseJpegXLNotParsed) {
0849         return -1;
0850     }
0851 
0852     if (m_parseState == ParseJpegXLError || m_parseState == ParseJpegXLBasicInfoParsed || !m_decoder) {
0853         return 0;
0854     }
0855 
0856     return m_currentimage_index;
0857 }
0858 
0859 bool QJpegXLHandler::jumpToNextImage()
0860 {
0861     if (!ensureALLCounted()) {
0862         return false;
0863     }
0864 
0865     if (m_framedelays.count() > 1) {
0866         m_currentimage_index++;
0867 
0868         if (m_currentimage_index >= m_framedelays.count()) {
0869             if (!rewind()) {
0870                 return false;
0871             }
0872         } else {
0873             JxlDecoderSkipFrames(m_decoder, 1);
0874         }
0875     }
0876 
0877     m_parseState = ParseJpegXLSuccess;
0878     return true;
0879 }
0880 
0881 bool QJpegXLHandler::jumpToImage(int imageNumber)
0882 {
0883     if (!ensureALLCounted()) {
0884         return false;
0885     }
0886 
0887     if (imageNumber < 0 || imageNumber >= m_framedelays.count()) {
0888         return false;
0889     }
0890 
0891     if (imageNumber == m_currentimage_index) {
0892         m_parseState = ParseJpegXLSuccess;
0893         return true;
0894     }
0895 
0896     if (imageNumber > m_currentimage_index) {
0897         JxlDecoderSkipFrames(m_decoder, imageNumber - m_currentimage_index);
0898         m_currentimage_index = imageNumber;
0899         m_parseState = ParseJpegXLSuccess;
0900         return true;
0901     }
0902 
0903     if (!rewind()) {
0904         return false;
0905     }
0906 
0907     if (imageNumber > 0) {
0908         JxlDecoderSkipFrames(m_decoder, imageNumber);
0909     }
0910     m_currentimage_index = imageNumber;
0911     m_parseState = ParseJpegXLSuccess;
0912     return true;
0913 }
0914 
0915 int QJpegXLHandler::nextImageDelay() const
0916 {
0917     if (!ensureALLCounted()) {
0918         return 0;
0919     }
0920 
0921     if (m_framedelays.count() < 2) {
0922         return 0;
0923     }
0924 
0925     return m_next_image_delay;
0926 }
0927 
0928 int QJpegXLHandler::loopCount() const
0929 {
0930     if (!ensureParsed()) {
0931         return 0;
0932     }
0933 
0934     if (m_basicinfo.have_animation) {
0935         return (m_basicinfo.animation.num_loops > 0) ? m_basicinfo.animation.num_loops - 1 : -1;
0936     } else {
0937         return 0;
0938     }
0939 }
0940 
0941 bool QJpegXLHandler::rewind()
0942 {
0943     m_currentimage_index = 0;
0944 
0945     JxlDecoderReleaseInput(m_decoder);
0946     JxlDecoderRewind(m_decoder);
0947     if (m_runner) {
0948         if (JxlDecoderSetParallelRunner(m_decoder, JxlThreadParallelRunner, m_runner) != JXL_DEC_SUCCESS) {
0949             qWarning("ERROR: JxlDecoderSetParallelRunner failed");
0950             m_parseState = ParseJpegXLError;
0951             return false;
0952         }
0953     }
0954 
0955     if (JxlDecoderSetInput(m_decoder, reinterpret_cast<const uint8_t *>(m_rawData.constData()), m_rawData.size()) != JXL_DEC_SUCCESS) {
0956         qWarning("ERROR: JxlDecoderSetInput failed");
0957         m_parseState = ParseJpegXLError;
0958         return false;
0959     }
0960 
0961     JxlDecoderCloseInput(m_decoder);
0962 
0963     if (m_basicinfo.uses_original_profile) {
0964         if (JxlDecoderSubscribeEvents(m_decoder, JXL_DEC_FULL_IMAGE) != JXL_DEC_SUCCESS) {
0965             qWarning("ERROR: JxlDecoderSubscribeEvents failed");
0966             m_parseState = ParseJpegXLError;
0967             return false;
0968         }
0969     } else {
0970         if (JxlDecoderSubscribeEvents(m_decoder, JXL_DEC_COLOR_ENCODING | JXL_DEC_FULL_IMAGE) != JXL_DEC_SUCCESS) {
0971             qWarning("ERROR: JxlDecoderSubscribeEvents failed");
0972             m_parseState = ParseJpegXLError;
0973             return false;
0974         }
0975 
0976         JxlDecoderStatus status = JxlDecoderProcessInput(m_decoder);
0977         if (status != JXL_DEC_COLOR_ENCODING) {
0978             qWarning("Unexpected event %d instead of JXL_DEC_COLOR_ENCODING", status);
0979             m_parseState = ParseJpegXLError;
0980             return false;
0981         }
0982 
0983         JxlColorEncoding color_encoding;
0984         JxlColorEncodingSetToSRGB(&color_encoding, JXL_FALSE);
0985         JxlDecoderSetPreferredColorProfile(m_decoder, &color_encoding);
0986     }
0987 
0988     return true;
0989 }
0990 
0991 QImageIOPlugin::Capabilities QJpegXLPlugin::capabilities(QIODevice *device, const QByteArray &format) const
0992 {
0993     if (format == "jxl") {
0994         return Capabilities(CanRead | CanWrite);
0995     }
0996 
0997     if (!format.isEmpty()) {
0998         return {};
0999     }
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 
1016 QImageIOHandler *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"