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"