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"