File indexing completed on 2024-04-28 15:25:40
0001 /* 0002 High Efficiency Image File Format (HEIF) support for QImage. 0003 0004 SPDX-FileCopyrightText: 2020 Sirius Bakke <sirius@bakke.co> 0005 SPDX-FileCopyrightText: 2021 Daniel Novomesky <dnovomesky@gmail.com> 0006 0007 SPDX-License-Identifier: LGPL-2.0-or-later 0008 */ 0009 0010 #include "heif_p.h" 0011 #include "util_p.h" 0012 #include <libheif/heif.h> 0013 0014 #include <QColorSpace> 0015 #include <QDebug> 0016 #include <QPointF> 0017 #include <QSysInfo> 0018 #include <limits> 0019 #include <string.h> 0020 0021 size_t HEIFHandler::m_initialized_count = 0; 0022 bool HEIFHandler::m_plugins_queried = false; 0023 bool HEIFHandler::m_heif_decoder_available = false; 0024 bool HEIFHandler::m_heif_encoder_available = false; 0025 bool HEIFHandler::m_hej2_decoder_available = false; 0026 0027 extern "C" { 0028 static struct heif_error heifhandler_write_callback(struct heif_context * /* ctx */, const void *data, size_t size, void *userdata) 0029 { 0030 heif_error error; 0031 error.code = heif_error_Ok; 0032 error.subcode = heif_suberror_Unspecified; 0033 error.message = "Success"; 0034 0035 if (!userdata || !data || size == 0) { 0036 error.code = heif_error_Usage_error; 0037 error.subcode = heif_suberror_Null_pointer_argument; 0038 error.message = "Wrong parameters!"; 0039 return error; 0040 } 0041 0042 QIODevice *ioDevice = static_cast<QIODevice *>(userdata); 0043 qint64 bytesWritten = ioDevice->write(static_cast<const char *>(data), size); 0044 0045 if (bytesWritten < static_cast<qint64>(size)) { 0046 error.code = heif_error_Encoding_error; 0047 error.message = "Bytes written to QIODevice are smaller than input data size"; 0048 error.subcode = heif_suberror_Cannot_write_output_data; 0049 } 0050 0051 return error; 0052 } 0053 } 0054 0055 HEIFHandler::HEIFHandler() 0056 : m_parseState(ParseHeicNotParsed) 0057 , m_quality(100) 0058 { 0059 } 0060 0061 bool HEIFHandler::canRead() const 0062 { 0063 if (m_parseState == ParseHeicNotParsed) { 0064 QIODevice *dev = device(); 0065 if (dev) { 0066 const QByteArray header = dev->peek(28); 0067 0068 if (HEIFHandler::isSupportedBMFFType(header)) { 0069 setFormat("heif"); 0070 return true; 0071 } 0072 0073 if (HEIFHandler::isSupportedHEJ2(header)) { 0074 setFormat("hej2"); 0075 return true; 0076 } 0077 } 0078 return false; 0079 } 0080 0081 if (m_parseState != ParseHeicError) { 0082 return true; 0083 } 0084 return false; 0085 } 0086 0087 bool HEIFHandler::read(QImage *outImage) 0088 { 0089 if (!ensureParsed()) { 0090 return false; 0091 } 0092 0093 *outImage = m_current_image; 0094 return true; 0095 } 0096 0097 bool HEIFHandler::write(const QImage &image) 0098 { 0099 if (image.format() == QImage::Format_Invalid || image.isNull()) { 0100 qWarning("No image data to save"); 0101 return false; 0102 } 0103 0104 #if LIBHEIF_HAVE_VERSION(1, 13, 0) 0105 startHeifLib(); 0106 #endif 0107 0108 bool success = write_helper(image); 0109 0110 #if LIBHEIF_HAVE_VERSION(1, 13, 0) 0111 finishHeifLib(); 0112 #endif 0113 0114 return success; 0115 } 0116 0117 bool HEIFHandler::write_helper(const QImage &image) 0118 { 0119 int save_depth; // 8 or 10bit per channel 0120 QImage::Format tmpformat; // format for temporary image 0121 const bool save_alpha = image.hasAlphaChannel(); 0122 0123 switch (image.format()) { 0124 case QImage::Format_BGR30: 0125 case QImage::Format_A2BGR30_Premultiplied: 0126 case QImage::Format_RGB30: 0127 case QImage::Format_A2RGB30_Premultiplied: 0128 case QImage::Format_Grayscale16: 0129 case QImage::Format_RGBX64: 0130 case QImage::Format_RGBA64: 0131 case QImage::Format_RGBA64_Premultiplied: 0132 save_depth = 10; 0133 break; 0134 default: 0135 if (image.depth() > 32) { 0136 save_depth = 10; 0137 } else { 0138 save_depth = 8; 0139 } 0140 break; 0141 } 0142 0143 heif_chroma chroma; 0144 if (save_depth > 8) { 0145 if (save_alpha) { 0146 tmpformat = QImage::Format_RGBA64; 0147 chroma = (QSysInfo::ByteOrder == QSysInfo::LittleEndian) ? heif_chroma_interleaved_RRGGBBAA_LE : heif_chroma_interleaved_RRGGBBAA_BE; 0148 } else { 0149 tmpformat = QImage::Format_RGBX64; 0150 chroma = (QSysInfo::ByteOrder == QSysInfo::LittleEndian) ? heif_chroma_interleaved_RRGGBB_LE : heif_chroma_interleaved_RRGGBB_BE; 0151 } 0152 } else { 0153 if (save_alpha) { 0154 tmpformat = QImage::Format_RGBA8888; 0155 chroma = heif_chroma_interleaved_RGBA; 0156 } else { 0157 tmpformat = QImage::Format_RGB888; 0158 chroma = heif_chroma_interleaved_RGB; 0159 } 0160 } 0161 0162 const QImage tmpimage = image.convertToFormat(tmpformat); 0163 0164 struct heif_context *context = heif_context_alloc(); 0165 struct heif_error err; 0166 struct heif_image *h_image = nullptr; 0167 0168 err = heif_image_create(tmpimage.width(), tmpimage.height(), heif_colorspace_RGB, chroma, &h_image); 0169 if (err.code) { 0170 qWarning() << "heif_image_create error:" << err.message; 0171 heif_context_free(context); 0172 return false; 0173 } 0174 0175 QByteArray iccprofile = tmpimage.colorSpace().iccProfile(); 0176 if (iccprofile.size() > 0) { 0177 heif_image_set_raw_color_profile(h_image, "prof", iccprofile.constData(), iccprofile.size()); 0178 } 0179 0180 heif_image_add_plane(h_image, heif_channel_interleaved, image.width(), image.height(), save_depth); 0181 int stride = 0; 0182 uint8_t *const dst = heif_image_get_plane(h_image, heif_channel_interleaved, &stride); 0183 size_t rowbytes; 0184 0185 switch (save_depth) { 0186 case 10: 0187 if (save_alpha) { 0188 for (int y = 0; y < tmpimage.height(); y++) { 0189 const uint16_t *src_word = reinterpret_cast<const uint16_t *>(tmpimage.constScanLine(y)); 0190 uint16_t *dest_word = reinterpret_cast<uint16_t *>(dst + (y * stride)); 0191 for (int x = 0; x < tmpimage.width(); x++) { 0192 int tmp_pixelval; 0193 // R 0194 tmp_pixelval = (int)(((float)(*src_word) / 65535.0f) * 1023.0f + 0.5f); 0195 *dest_word = qBound(0, tmp_pixelval, 1023); 0196 src_word++; 0197 dest_word++; 0198 // G 0199 tmp_pixelval = (int)(((float)(*src_word) / 65535.0f) * 1023.0f + 0.5f); 0200 *dest_word = qBound(0, tmp_pixelval, 1023); 0201 src_word++; 0202 dest_word++; 0203 // B 0204 tmp_pixelval = (int)(((float)(*src_word) / 65535.0f) * 1023.0f + 0.5f); 0205 *dest_word = qBound(0, tmp_pixelval, 1023); 0206 src_word++; 0207 dest_word++; 0208 // A 0209 tmp_pixelval = (int)(((float)(*src_word) / 65535.0f) * 1023.0f + 0.5f); 0210 *dest_word = qBound(0, tmp_pixelval, 1023); 0211 src_word++; 0212 dest_word++; 0213 } 0214 } 0215 } else { // no alpha channel 0216 for (int y = 0; y < tmpimage.height(); y++) { 0217 const uint16_t *src_word = reinterpret_cast<const uint16_t *>(tmpimage.constScanLine(y)); 0218 uint16_t *dest_word = reinterpret_cast<uint16_t *>(dst + (y * stride)); 0219 for (int x = 0; x < tmpimage.width(); x++) { 0220 int tmp_pixelval; 0221 // R 0222 tmp_pixelval = (int)(((float)(*src_word) / 65535.0f) * 1023.0f + 0.5f); 0223 *dest_word = qBound(0, tmp_pixelval, 1023); 0224 src_word++; 0225 dest_word++; 0226 // G 0227 tmp_pixelval = (int)(((float)(*src_word) / 65535.0f) * 1023.0f + 0.5f); 0228 *dest_word = qBound(0, tmp_pixelval, 1023); 0229 src_word++; 0230 dest_word++; 0231 // B 0232 tmp_pixelval = (int)(((float)(*src_word) / 65535.0f) * 1023.0f + 0.5f); 0233 *dest_word = qBound(0, tmp_pixelval, 1023); 0234 src_word++; 0235 dest_word++; 0236 // X 0237 src_word++; 0238 } 0239 } 0240 } 0241 break; 0242 case 8: 0243 rowbytes = save_alpha ? (tmpimage.width() * 4) : (tmpimage.width() * 3); 0244 for (int y = 0; y < tmpimage.height(); y++) { 0245 memcpy(dst + (y * stride), tmpimage.constScanLine(y), rowbytes); 0246 } 0247 break; 0248 default: 0249 qWarning() << "Unsupported depth:" << save_depth; 0250 heif_image_release(h_image); 0251 heif_context_free(context); 0252 return false; 0253 break; 0254 } 0255 0256 struct heif_encoder *encoder = nullptr; 0257 err = heif_context_get_encoder_for_format(context, heif_compression_HEVC, &encoder); 0258 if (err.code) { 0259 qWarning() << "Unable to get an encoder instance:" << err.message; 0260 heif_image_release(h_image); 0261 heif_context_free(context); 0262 return false; 0263 } 0264 0265 heif_encoder_set_lossy_quality(encoder, m_quality); 0266 if (m_quality > 90) { 0267 if (m_quality == 100) { 0268 heif_encoder_set_lossless(encoder, true); 0269 } 0270 heif_encoder_set_parameter_string(encoder, "chroma", "444"); 0271 } 0272 0273 struct heif_encoding_options *encoder_options = heif_encoding_options_alloc(); 0274 encoder_options->save_alpha_channel = save_alpha; 0275 0276 if ((tmpimage.width() % 2 == 1) || (tmpimage.height() % 2 == 1)) { 0277 qWarning() << "Image has odd dimension!\nUse even-numbered dimension(s) for better compatibility with other HEIF implementations."; 0278 if (save_alpha) { 0279 // This helps to save alpha channel when image has odd dimension 0280 encoder_options->macOS_compatibility_workaround = 0; 0281 } 0282 } 0283 0284 err = heif_context_encode_image(context, h_image, encoder, encoder_options, nullptr); 0285 0286 if (encoder_options) { 0287 heif_encoding_options_free(encoder_options); 0288 } 0289 0290 if (err.code) { 0291 qWarning() << "heif_context_encode_image failed:" << err.message; 0292 heif_encoder_release(encoder); 0293 heif_image_release(h_image); 0294 heif_context_free(context); 0295 return false; 0296 } 0297 0298 struct heif_writer writer; 0299 writer.writer_api_version = 1; 0300 writer.write = heifhandler_write_callback; 0301 0302 err = heif_context_write(context, &writer, device()); 0303 0304 heif_encoder_release(encoder); 0305 heif_image_release(h_image); 0306 0307 if (err.code) { 0308 qWarning() << "Writing HEIF image failed:" << err.message; 0309 heif_context_free(context); 0310 return false; 0311 } 0312 0313 heif_context_free(context); 0314 return true; 0315 } 0316 0317 bool HEIFHandler::isSupportedBMFFType(const QByteArray &header) 0318 { 0319 if (header.size() < 28) { 0320 return false; 0321 } 0322 0323 const char *buffer = header.constData(); 0324 if (qstrncmp(buffer + 4, "ftyp", 4) == 0) { 0325 if (qstrncmp(buffer + 8, "heic", 4) == 0) { 0326 return true; 0327 } 0328 if (qstrncmp(buffer + 8, "heis", 4) == 0) { 0329 return true; 0330 } 0331 if (qstrncmp(buffer + 8, "heix", 4) == 0) { 0332 return true; 0333 } 0334 0335 /* we want to avoid loading AVIF files via this plugin */ 0336 if (qstrncmp(buffer + 8, "mif1", 4) == 0) { 0337 for (int offset = 16; offset <= 24; offset += 4) { 0338 if (qstrncmp(buffer + offset, "avif", 4) == 0) { 0339 return false; 0340 } 0341 } 0342 return true; 0343 } 0344 0345 if (qstrncmp(buffer + 8, "mif2", 4) == 0) { 0346 return true; 0347 } 0348 if (qstrncmp(buffer + 8, "msf1", 4) == 0) { 0349 return true; 0350 } 0351 } 0352 0353 return false; 0354 } 0355 0356 bool HEIFHandler::isSupportedHEJ2(const QByteArray &header) 0357 { 0358 if (header.size() < 28) { 0359 return false; 0360 } 0361 0362 const char *buffer = header.constData(); 0363 if (qstrncmp(buffer + 4, "ftyp", 4) == 0) { 0364 if (qstrncmp(buffer + 8, "j2ki", 4) == 0) { 0365 return true; 0366 } 0367 } 0368 0369 return false; 0370 } 0371 0372 QVariant HEIFHandler::option(ImageOption option) const 0373 { 0374 if (option == Quality) { 0375 return m_quality; 0376 } 0377 0378 if (!supportsOption(option) || !ensureParsed()) { 0379 return QVariant(); 0380 } 0381 0382 switch (option) { 0383 case Size: 0384 return m_current_image.size(); 0385 break; 0386 default: 0387 return QVariant(); 0388 break; 0389 } 0390 } 0391 0392 void HEIFHandler::setOption(ImageOption option, const QVariant &value) 0393 { 0394 switch (option) { 0395 case Quality: 0396 m_quality = value.toInt(); 0397 if (m_quality > 100) { 0398 m_quality = 100; 0399 } else if (m_quality < 0) { 0400 m_quality = 100; 0401 } 0402 break; 0403 default: 0404 QImageIOHandler::setOption(option, value); 0405 break; 0406 } 0407 } 0408 0409 bool HEIFHandler::supportsOption(ImageOption option) const 0410 { 0411 return option == Quality || option == Size; 0412 } 0413 0414 bool HEIFHandler::ensureParsed() const 0415 { 0416 if (m_parseState == ParseHeicSuccess) { 0417 return true; 0418 } 0419 if (m_parseState == ParseHeicError) { 0420 return false; 0421 } 0422 0423 HEIFHandler *that = const_cast<HEIFHandler *>(this); 0424 0425 #if LIBHEIF_HAVE_VERSION(1, 13, 0) 0426 startHeifLib(); 0427 #endif 0428 0429 bool success = that->ensureDecoder(); 0430 0431 #if LIBHEIF_HAVE_VERSION(1, 13, 0) 0432 finishHeifLib(); 0433 #endif 0434 return success; 0435 } 0436 0437 bool HEIFHandler::ensureDecoder() 0438 { 0439 if (m_parseState != ParseHeicNotParsed) { 0440 if (m_parseState == ParseHeicSuccess) { 0441 return true; 0442 } 0443 return false; 0444 } 0445 0446 const QByteArray buffer = device()->readAll(); 0447 if (!HEIFHandler::isSupportedBMFFType(buffer) && !HEIFHandler::isSupportedHEJ2(buffer)) { 0448 m_parseState = ParseHeicError; 0449 return false; 0450 } 0451 0452 struct heif_context *ctx = heif_context_alloc(); 0453 struct heif_error err = heif_context_read_from_memory(ctx, static_cast<const void *>(buffer.constData()), buffer.size(), nullptr); 0454 0455 if (err.code) { 0456 qWarning() << "heif_context_read_from_memory error:" << err.message; 0457 heif_context_free(ctx); 0458 m_parseState = ParseHeicError; 0459 return false; 0460 } 0461 0462 struct heif_image_handle *handle = nullptr; 0463 err = heif_context_get_primary_image_handle(ctx, &handle); 0464 if (err.code) { 0465 qWarning() << "heif_context_get_primary_image_handle error:" << err.message; 0466 heif_context_free(ctx); 0467 m_parseState = ParseHeicError; 0468 return false; 0469 } 0470 0471 if ((heif_image_handle_get_width(handle) == 0) || (heif_image_handle_get_height(handle) == 0)) { 0472 m_parseState = ParseHeicError; 0473 heif_image_handle_release(handle); 0474 heif_context_free(ctx); 0475 qWarning() << "HEIC image has zero dimension"; 0476 return false; 0477 } 0478 0479 const bool hasAlphaChannel = heif_image_handle_has_alpha_channel(handle); 0480 const int bit_depth = heif_image_handle_get_luma_bits_per_pixel(handle); 0481 heif_chroma chroma; 0482 0483 QImage::Format target_image_format; 0484 0485 if (bit_depth == 10 || bit_depth == 12) { 0486 if (hasAlphaChannel) { 0487 chroma = (QSysInfo::ByteOrder == QSysInfo::LittleEndian) ? heif_chroma_interleaved_RRGGBBAA_LE : heif_chroma_interleaved_RRGGBBAA_BE; 0488 target_image_format = QImage::Format_RGBA64; 0489 } else { 0490 chroma = (QSysInfo::ByteOrder == QSysInfo::LittleEndian) ? heif_chroma_interleaved_RRGGBB_LE : heif_chroma_interleaved_RRGGBB_BE; 0491 target_image_format = QImage::Format_RGBX64; 0492 } 0493 } else if (bit_depth == 8) { 0494 if (hasAlphaChannel) { 0495 chroma = heif_chroma_interleaved_RGBA; 0496 target_image_format = QImage::Format_ARGB32; 0497 } else { 0498 chroma = heif_chroma_interleaved_RGB; 0499 target_image_format = QImage::Format_RGB32; 0500 } 0501 } else { 0502 m_parseState = ParseHeicError; 0503 heif_image_handle_release(handle); 0504 heif_context_free(ctx); 0505 if (bit_depth > 0) { 0506 qWarning() << "Unsupported bit depth:" << bit_depth; 0507 } else { 0508 qWarning() << "Undefined bit depth."; 0509 } 0510 return false; 0511 } 0512 0513 struct heif_decoding_options *decoder_option = heif_decoding_options_alloc(); 0514 0515 #if LIBHEIF_HAVE_VERSION(1, 13, 0) 0516 decoder_option->strict_decoding = 1; 0517 #endif 0518 0519 struct heif_image *img = nullptr; 0520 err = heif_decode_image(handle, &img, heif_colorspace_RGB, chroma, decoder_option); 0521 0522 if (decoder_option) { 0523 heif_decoding_options_free(decoder_option); 0524 } 0525 0526 if (err.code) { 0527 qWarning() << "heif_decode_image error:" << err.message; 0528 heif_image_handle_release(handle); 0529 heif_context_free(ctx); 0530 m_parseState = ParseHeicError; 0531 return false; 0532 } 0533 0534 const int imageWidth = heif_image_get_width(img, heif_channel_interleaved); 0535 const int imageHeight = heif_image_get_height(img, heif_channel_interleaved); 0536 0537 QSize imageSize(imageWidth, imageHeight); 0538 0539 if (!imageSize.isValid()) { 0540 heif_image_release(img); 0541 heif_image_handle_release(handle); 0542 heif_context_free(ctx); 0543 m_parseState = ParseHeicError; 0544 qWarning() << "HEIC image size invalid:" << imageSize; 0545 return false; 0546 } 0547 0548 int stride = 0; 0549 const uint8_t *const src = heif_image_get_plane_readonly(img, heif_channel_interleaved, &stride); 0550 0551 if (!src || stride <= 0) { 0552 heif_image_release(img); 0553 heif_image_handle_release(handle); 0554 heif_context_free(ctx); 0555 m_parseState = ParseHeicError; 0556 qWarning() << "HEIC data pixels information not valid!"; 0557 return false; 0558 } 0559 0560 m_current_image = imageAlloc(imageSize, target_image_format); 0561 if (m_current_image.isNull()) { 0562 heif_image_release(img); 0563 heif_image_handle_release(handle); 0564 heif_context_free(ctx); 0565 m_parseState = ParseHeicError; 0566 qWarning() << "Unable to allocate memory!"; 0567 return false; 0568 } 0569 0570 switch (bit_depth) { 0571 case 12: 0572 if (hasAlphaChannel) { 0573 for (int y = 0; y < imageHeight; y++) { 0574 const uint16_t *src_word = reinterpret_cast<const uint16_t *>(src + (y * stride)); 0575 uint16_t *dest_data = reinterpret_cast<uint16_t *>(m_current_image.scanLine(y)); 0576 for (int x = 0; x < imageWidth; x++) { 0577 int tmpvalue; 0578 // R 0579 tmpvalue = (int)(((float)(0x0fff & (*src_word)) / 4095.0f) * 65535.0f + 0.5f); 0580 tmpvalue = qBound(0, tmpvalue, 65535); 0581 *dest_data = (uint16_t)tmpvalue; 0582 src_word++; 0583 dest_data++; 0584 // G 0585 tmpvalue = (int)(((float)(0x0fff & (*src_word)) / 4095.0f) * 65535.0f + 0.5f); 0586 tmpvalue = qBound(0, tmpvalue, 65535); 0587 *dest_data = (uint16_t)tmpvalue; 0588 src_word++; 0589 dest_data++; 0590 // B 0591 tmpvalue = (int)(((float)(0x0fff & (*src_word)) / 4095.0f) * 65535.0f + 0.5f); 0592 tmpvalue = qBound(0, tmpvalue, 65535); 0593 *dest_data = (uint16_t)tmpvalue; 0594 src_word++; 0595 dest_data++; 0596 // A 0597 tmpvalue = (int)(((float)(0x0fff & (*src_word)) / 4095.0f) * 65535.0f + 0.5f); 0598 tmpvalue = qBound(0, tmpvalue, 65535); 0599 *dest_data = (uint16_t)tmpvalue; 0600 src_word++; 0601 dest_data++; 0602 } 0603 } 0604 } else { // no alpha channel 0605 for (int y = 0; y < imageHeight; y++) { 0606 const uint16_t *src_word = reinterpret_cast<const uint16_t *>(src + (y * stride)); 0607 uint16_t *dest_data = reinterpret_cast<uint16_t *>(m_current_image.scanLine(y)); 0608 for (int x = 0; x < imageWidth; x++) { 0609 int tmpvalue; 0610 // R 0611 tmpvalue = (int)(((float)(0x0fff & (*src_word)) / 4095.0f) * 65535.0f + 0.5f); 0612 tmpvalue = qBound(0, tmpvalue, 65535); 0613 *dest_data = (uint16_t)tmpvalue; 0614 src_word++; 0615 dest_data++; 0616 // G 0617 tmpvalue = (int)(((float)(0x0fff & (*src_word)) / 4095.0f) * 65535.0f + 0.5f); 0618 tmpvalue = qBound(0, tmpvalue, 65535); 0619 *dest_data = (uint16_t)tmpvalue; 0620 src_word++; 0621 dest_data++; 0622 // B 0623 tmpvalue = (int)(((float)(0x0fff & (*src_word)) / 4095.0f) * 65535.0f + 0.5f); 0624 tmpvalue = qBound(0, tmpvalue, 65535); 0625 *dest_data = (uint16_t)tmpvalue; 0626 src_word++; 0627 dest_data++; 0628 // X = 0xffff 0629 *dest_data = 0xffff; 0630 dest_data++; 0631 } 0632 } 0633 } 0634 break; 0635 case 10: 0636 if (hasAlphaChannel) { 0637 for (int y = 0; y < imageHeight; y++) { 0638 const uint16_t *src_word = reinterpret_cast<const uint16_t *>(src + (y * stride)); 0639 uint16_t *dest_data = reinterpret_cast<uint16_t *>(m_current_image.scanLine(y)); 0640 for (int x = 0; x < imageWidth; x++) { 0641 int tmpvalue; 0642 // R 0643 tmpvalue = (int)(((float)(0x03ff & (*src_word)) / 1023.0f) * 65535.0f + 0.5f); 0644 tmpvalue = qBound(0, tmpvalue, 65535); 0645 *dest_data = (uint16_t)tmpvalue; 0646 src_word++; 0647 dest_data++; 0648 // G 0649 tmpvalue = (int)(((float)(0x03ff & (*src_word)) / 1023.0f) * 65535.0f + 0.5f); 0650 tmpvalue = qBound(0, tmpvalue, 65535); 0651 *dest_data = (uint16_t)tmpvalue; 0652 src_word++; 0653 dest_data++; 0654 // B 0655 tmpvalue = (int)(((float)(0x03ff & (*src_word)) / 1023.0f) * 65535.0f + 0.5f); 0656 tmpvalue = qBound(0, tmpvalue, 65535); 0657 *dest_data = (uint16_t)tmpvalue; 0658 src_word++; 0659 dest_data++; 0660 // A 0661 tmpvalue = (int)(((float)(0x03ff & (*src_word)) / 1023.0f) * 65535.0f + 0.5f); 0662 tmpvalue = qBound(0, tmpvalue, 65535); 0663 *dest_data = (uint16_t)tmpvalue; 0664 src_word++; 0665 dest_data++; 0666 } 0667 } 0668 } else { // no alpha channel 0669 for (int y = 0; y < imageHeight; y++) { 0670 const uint16_t *src_word = reinterpret_cast<const uint16_t *>(src + (y * stride)); 0671 uint16_t *dest_data = reinterpret_cast<uint16_t *>(m_current_image.scanLine(y)); 0672 for (int x = 0; x < imageWidth; x++) { 0673 int tmpvalue; 0674 // R 0675 tmpvalue = (int)(((float)(0x03ff & (*src_word)) / 1023.0f) * 65535.0f + 0.5f); 0676 tmpvalue = qBound(0, tmpvalue, 65535); 0677 *dest_data = (uint16_t)tmpvalue; 0678 src_word++; 0679 dest_data++; 0680 // G 0681 tmpvalue = (int)(((float)(0x03ff & (*src_word)) / 1023.0f) * 65535.0f + 0.5f); 0682 tmpvalue = qBound(0, tmpvalue, 65535); 0683 *dest_data = (uint16_t)tmpvalue; 0684 src_word++; 0685 dest_data++; 0686 // B 0687 tmpvalue = (int)(((float)(0x03ff & (*src_word)) / 1023.0f) * 65535.0f + 0.5f); 0688 tmpvalue = qBound(0, tmpvalue, 65535); 0689 *dest_data = (uint16_t)tmpvalue; 0690 src_word++; 0691 dest_data++; 0692 // X = 0xffff 0693 *dest_data = 0xffff; 0694 dest_data++; 0695 } 0696 } 0697 } 0698 break; 0699 case 8: 0700 if (hasAlphaChannel) { 0701 for (int y = 0; y < imageHeight; y++) { 0702 const uint8_t *src_byte = src + (y * stride); 0703 uint32_t *dest_pixel = reinterpret_cast<uint32_t *>(m_current_image.scanLine(y)); 0704 for (int x = 0; x < imageWidth; x++) { 0705 int red = *src_byte++; 0706 int green = *src_byte++; 0707 int blue = *src_byte++; 0708 int alpha = *src_byte++; 0709 *dest_pixel = qRgba(red, green, blue, alpha); 0710 dest_pixel++; 0711 } 0712 } 0713 } else { // no alpha channel 0714 for (int y = 0; y < imageHeight; y++) { 0715 const uint8_t *src_byte = src + (y * stride); 0716 uint32_t *dest_pixel = reinterpret_cast<uint32_t *>(m_current_image.scanLine(y)); 0717 for (int x = 0; x < imageWidth; x++) { 0718 int red = *src_byte++; 0719 int green = *src_byte++; 0720 int blue = *src_byte++; 0721 *dest_pixel = qRgb(red, green, blue); 0722 dest_pixel++; 0723 } 0724 } 0725 } 0726 break; 0727 default: 0728 heif_image_release(img); 0729 heif_image_handle_release(handle); 0730 heif_context_free(ctx); 0731 m_parseState = ParseHeicError; 0732 qWarning() << "Unsupported bit depth:" << bit_depth; 0733 return false; 0734 break; 0735 } 0736 0737 heif_color_profile_type profileType = heif_image_handle_get_color_profile_type(handle); 0738 if (profileType == heif_color_profile_type_prof || profileType == heif_color_profile_type_rICC) { 0739 size_t rawProfileSize = heif_image_handle_get_raw_color_profile_size(handle); 0740 if (rawProfileSize > 0 && rawProfileSize < std::numeric_limits<int>::max()) { 0741 QByteArray ba(rawProfileSize, 0); 0742 err = heif_image_handle_get_raw_color_profile(handle, ba.data()); 0743 if (err.code) { 0744 qWarning() << "icc profile loading failed"; 0745 } else { 0746 m_current_image.setColorSpace(QColorSpace::fromIccProfile(ba)); 0747 if (!m_current_image.colorSpace().isValid()) { 0748 qWarning() << "HEIC image has Qt-unsupported or invalid ICC profile!"; 0749 } 0750 } 0751 } else { 0752 qWarning() << "icc profile is empty or above limits"; 0753 } 0754 0755 } else if (profileType == heif_color_profile_type_nclx) { 0756 struct heif_color_profile_nclx *nclx = nullptr; 0757 err = heif_image_handle_get_nclx_color_profile(handle, &nclx); 0758 if (err.code || !nclx) { 0759 qWarning() << "nclx profile loading failed"; 0760 } else { 0761 const QPointF redPoint(nclx->color_primary_red_x, nclx->color_primary_red_y); 0762 const QPointF greenPoint(nclx->color_primary_green_x, nclx->color_primary_green_y); 0763 const QPointF bluePoint(nclx->color_primary_blue_x, nclx->color_primary_blue_y); 0764 const QPointF whitePoint(nclx->color_primary_white_x, nclx->color_primary_white_y); 0765 0766 QColorSpace::TransferFunction q_trc = QColorSpace::TransferFunction::Custom; 0767 float q_trc_gamma = 0.0f; 0768 0769 switch (nclx->transfer_characteristics) { 0770 case 4: 0771 q_trc = QColorSpace::TransferFunction::Gamma; 0772 q_trc_gamma = 2.2f; 0773 break; 0774 case 5: 0775 q_trc = QColorSpace::TransferFunction::Gamma; 0776 q_trc_gamma = 2.8f; 0777 break; 0778 case 8: 0779 q_trc = QColorSpace::TransferFunction::Linear; 0780 break; 0781 case 2: 0782 case 13: 0783 q_trc = QColorSpace::TransferFunction::SRgb; 0784 break; 0785 default: 0786 qWarning("CICP color_primaries: %d, transfer_characteristics: %d\nThe colorspace is unsupported by this plug-in yet.", 0787 nclx->color_primaries, 0788 nclx->transfer_characteristics); 0789 q_trc = QColorSpace::TransferFunction::SRgb; 0790 break; 0791 } 0792 0793 if (q_trc != QColorSpace::TransferFunction::Custom) { // we create new colorspace using Qt 0794 switch (nclx->color_primaries) { 0795 case 1: 0796 case 2: 0797 m_current_image.setColorSpace(QColorSpace(QColorSpace::Primaries::SRgb, q_trc, q_trc_gamma)); 0798 break; 0799 case 12: 0800 m_current_image.setColorSpace(QColorSpace(QColorSpace::Primaries::DciP3D65, q_trc, q_trc_gamma)); 0801 break; 0802 default: 0803 m_current_image.setColorSpace(QColorSpace(whitePoint, redPoint, greenPoint, bluePoint, q_trc, q_trc_gamma)); 0804 break; 0805 } 0806 } 0807 heif_nclx_color_profile_free(nclx); 0808 0809 if (!m_current_image.colorSpace().isValid()) { 0810 qWarning() << "HEIC plugin created invalid QColorSpace from NCLX!"; 0811 } 0812 } 0813 0814 } else { 0815 m_current_image.setColorSpace(QColorSpace(QColorSpace::SRgb)); 0816 } 0817 0818 heif_image_release(img); 0819 heif_image_handle_release(handle); 0820 heif_context_free(ctx); 0821 m_parseState = ParseHeicSuccess; 0822 return true; 0823 } 0824 0825 bool HEIFHandler::isHeifDecoderAvailable() 0826 { 0827 QMutexLocker locker(&getHEIFHandlerMutex()); 0828 0829 if (!m_plugins_queried) { 0830 #if LIBHEIF_HAVE_VERSION(1, 13, 0) 0831 if (m_initialized_count == 0) { 0832 heif_init(nullptr); 0833 } 0834 #endif 0835 0836 #if LIBHEIF_HAVE_VERSION(1, 13, 0) 0837 m_hej2_decoder_available = heif_have_decoder_for_format(heif_compression_JPEG2000); 0838 #endif 0839 m_heif_encoder_available = heif_have_encoder_for_format(heif_compression_HEVC); 0840 m_heif_decoder_available = heif_have_decoder_for_format(heif_compression_HEVC); 0841 m_plugins_queried = true; 0842 0843 #if LIBHEIF_HAVE_VERSION(1, 13, 0) 0844 if (m_initialized_count == 0) { 0845 heif_deinit(); 0846 } 0847 #endif 0848 } 0849 0850 return m_heif_decoder_available; 0851 } 0852 0853 bool HEIFHandler::isHeifEncoderAvailable() 0854 { 0855 QMutexLocker locker(&getHEIFHandlerMutex()); 0856 0857 if (!m_plugins_queried) { 0858 #if LIBHEIF_HAVE_VERSION(1, 13, 0) 0859 if (m_initialized_count == 0) { 0860 heif_init(nullptr); 0861 } 0862 #endif 0863 0864 #if LIBHEIF_HAVE_VERSION(1, 13, 0) 0865 m_hej2_decoder_available = heif_have_decoder_for_format(heif_compression_JPEG2000); 0866 #endif 0867 m_heif_decoder_available = heif_have_decoder_for_format(heif_compression_HEVC); 0868 m_heif_encoder_available = heif_have_encoder_for_format(heif_compression_HEVC); 0869 m_plugins_queried = true; 0870 0871 #if LIBHEIF_HAVE_VERSION(1, 13, 0) 0872 if (m_initialized_count == 0) { 0873 heif_deinit(); 0874 } 0875 #endif 0876 } 0877 0878 return m_heif_encoder_available; 0879 } 0880 0881 bool HEIFHandler::isHej2DecoderAvailable() 0882 { 0883 QMutexLocker locker(&getHEIFHandlerMutex()); 0884 0885 if (!m_plugins_queried) { 0886 #if LIBHEIF_HAVE_VERSION(1, 13, 0) 0887 if (m_initialized_count == 0) { 0888 heif_init(nullptr); 0889 } 0890 #endif 0891 0892 m_heif_encoder_available = heif_have_encoder_for_format(heif_compression_HEVC); 0893 m_heif_decoder_available = heif_have_decoder_for_format(heif_compression_HEVC); 0894 #if LIBHEIF_HAVE_VERSION(1, 13, 0) 0895 m_hej2_decoder_available = heif_have_decoder_for_format(heif_compression_JPEG2000); 0896 #endif 0897 m_plugins_queried = true; 0898 0899 #if LIBHEIF_HAVE_VERSION(1, 13, 0) 0900 if (m_initialized_count == 0) { 0901 heif_deinit(); 0902 } 0903 #endif 0904 } 0905 0906 return m_hej2_decoder_available; 0907 } 0908 0909 void HEIFHandler::startHeifLib() 0910 { 0911 #if LIBHEIF_HAVE_VERSION(1, 13, 0) 0912 QMutexLocker locker(&getHEIFHandlerMutex()); 0913 0914 if (m_initialized_count == 0) { 0915 heif_init(nullptr); 0916 } 0917 0918 m_initialized_count++; 0919 #endif 0920 } 0921 0922 void HEIFHandler::finishHeifLib() 0923 { 0924 #if LIBHEIF_HAVE_VERSION(1, 13, 0) 0925 QMutexLocker locker(&getHEIFHandlerMutex()); 0926 0927 if (m_initialized_count == 0) { 0928 return; 0929 } 0930 0931 m_initialized_count--; 0932 if (m_initialized_count == 0) { 0933 heif_deinit(); 0934 } 0935 0936 #endif 0937 } 0938 0939 QMutex &HEIFHandler::getHEIFHandlerMutex() 0940 { 0941 static QMutex heif_handler_mutex; 0942 return heif_handler_mutex; 0943 } 0944 0945 QImageIOPlugin::Capabilities HEIFPlugin::capabilities(QIODevice *device, const QByteArray &format) const 0946 { 0947 if (format == "heif" || format == "heic") { 0948 Capabilities format_cap; 0949 if (HEIFHandler::isHeifDecoderAvailable()) { 0950 format_cap |= CanRead; 0951 } 0952 if (HEIFHandler::isHeifEncoderAvailable()) { 0953 format_cap |= CanWrite; 0954 } 0955 return format_cap; 0956 } 0957 0958 if (format == "hej2") { 0959 Capabilities format_cap; 0960 if (HEIFHandler::isHej2DecoderAvailable()) { 0961 format_cap |= CanRead; 0962 } 0963 return format_cap; 0964 } 0965 0966 if (!format.isEmpty()) { 0967 return {}; 0968 } 0969 if (!device->isOpen()) { 0970 return {}; 0971 } 0972 0973 Capabilities cap; 0974 if (device->isReadable()) { 0975 const QByteArray header = device->peek(28); 0976 0977 if (HEIFHandler::isSupportedBMFFType(header) && HEIFHandler::isHeifDecoderAvailable()) { 0978 cap |= CanRead; 0979 } 0980 0981 if (HEIFHandler::isSupportedHEJ2(header) && HEIFHandler::isHej2DecoderAvailable()) { 0982 cap |= CanRead; 0983 } 0984 } 0985 0986 if (device->isWritable() && HEIFHandler::isHeifEncoderAvailable()) { 0987 cap |= CanWrite; 0988 } 0989 return cap; 0990 } 0991 0992 QImageIOHandler *HEIFPlugin::create(QIODevice *device, const QByteArray &format) const 0993 { 0994 QImageIOHandler *handler = new HEIFHandler; 0995 handler->setDevice(device); 0996 handler->setFormat(format); 0997 return handler; 0998 } 0999 1000 #include "moc_heif_p.cpp"