File indexing completed on 2025-01-19 03:50:59

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2019-09-26
0007  * Description : A HEIF IO file for DImg framework - load operations
0008  *
0009  * SPDX-FileCopyrightText: 2019-2024 by Gilles Caulier <caulier dot gilles at gmail dot com>
0010  *
0011  * SPDX-License-Identifier: GPL-2.0-or-later
0012  *
0013  * ============================================================ */
0014 
0015 #include "dimgheifloader.h"
0016 
0017 // C++ includes
0018 
0019 #include <cstdint>
0020 
0021 // Qt includes
0022 
0023 #include <QFile>
0024 #include <QVariant>
0025 #include <QByteArray>
0026 #include <QTextStream>
0027 #include <QDataStream>
0028 #include <qplatformdefs.h>
0029 
0030 // Local includes
0031 
0032 #include "digikam_debug.h"
0033 #include "digikam_config.h"
0034 #include "dimg.h"
0035 #include "dimgloaderobserver.h"
0036 #include "metaengine.h"
0037 
0038 namespace Digikam
0039 {
0040 
0041 static int64_t heifQIODeviceDImgGetPosition(void* userdata)                                     // krazy:exclude=typedefs
0042 {
0043     QFile* const file = static_cast<QFile*>(userdata);
0044 
0045     return (int64_t)file->pos();
0046 }
0047 
0048 static int heifQIODeviceDImgRead(void* data, size_t size, void* userdata)
0049 {
0050     QFile* const file = static_cast<QFile*>(userdata);
0051 
0052     if ((file->pos() + (qint64)size) > file->size())
0053     {
0054         return 0;
0055     }
0056 
0057     qint64 bytes = file->read((char*)data, size);
0058 
0059     return (int)((file->error() != QFileDevice::NoError) || (bytes != (qint64)size));
0060 }
0061 
0062 static int heifQIODeviceDImgSeek(int64_t position, void* userdata)                              // krazy:exclude=typedefs
0063 {
0064     QFile* const file = static_cast<QFile*>(userdata);
0065 
0066     return (int)!file->seek(position);
0067 }
0068 
0069 static heif_reader_grow_status heifQIODeviceDImgWait(int64_t target_size, void* userdata)       // krazy:exclude=typedefs
0070 {
0071     QFile* const file = static_cast<QFile*>(userdata);
0072 
0073     if ((qint64)target_size > file->size())
0074     {
0075         return heif_reader_grow_status_size_beyond_eof;
0076     }
0077 
0078     return heif_reader_grow_status_size_reached;
0079 }
0080 
0081 bool DImgHEIFLoader::load(const QString& filePath, DImgLoaderObserver* const observer)
0082 {
0083     m_observer = observer;
0084 
0085     readMetadata(filePath);
0086 
0087     QFile readFile(filePath);
0088 
0089     if (!readFile.open(QIODevice::ReadOnly))
0090     {
0091         qCWarning(DIGIKAM_DIMG_LOG_HEIF) << "Error: Could not open source file.";
0092         loadingFailed();
0093 
0094         return false;
0095     }
0096 
0097     const qint64 headerLen = 12;
0098 
0099     QByteArray header(headerLen, '\0');
0100 
0101     if (readFile.read((char*)header.data(), headerLen) != headerLen)
0102     {
0103         qCWarning(DIGIKAM_DIMG_LOG_HEIF) << "Error: Could not parse magic identifier.";
0104         loadingFailed();
0105 
0106         return false;
0107     }
0108 
0109     if ((memcmp(&header.data()[4], "ftyp", 4) != 0) &&
0110         (memcmp(&header.data()[8], "heic", 4) != 0) &&
0111         (memcmp(&header.data()[8], "heix", 4) != 0) &&
0112         (memcmp(&header.data()[8], "mif1", 4) != 0))
0113     {
0114         qCWarning(DIGIKAM_DIMG_LOG_HEIF) << "Error: source file is not HEIF image.";
0115         loadingFailed();
0116 
0117         return false;
0118     }
0119 
0120     readFile.reset();
0121 
0122     if (observer)
0123     {
0124         observer->progressInfo(0.1F);
0125     }
0126 
0127     // -------------------------------------------------------------------
0128     // Initialize HEIF API.
0129 
0130 #if LIBHEIF_NUMERIC_VERSION >= 0x010d0000
0131 
0132     heif_init(nullptr);
0133 
0134 #endif
0135 
0136     heif_item_id primary_image_id;
0137 
0138     struct heif_context* const heif_context = heif_context_alloc();
0139 
0140     heif_reader reader;
0141     reader.reader_api_version = 1;
0142     reader.get_position       = heifQIODeviceDImgGetPosition;
0143     reader.read               = heifQIODeviceDImgRead;
0144     reader.seek               = heifQIODeviceDImgSeek;
0145     reader.wait_for_file_size = heifQIODeviceDImgWait;
0146 
0147     struct heif_error error   = heif_context_read_from_reader(heif_context,
0148                                                               &reader,
0149                                                               (void*)&readFile,
0150                                                               nullptr);
0151 
0152     if (!isHeifSuccess(&error))
0153     {
0154         qCWarning(DIGIKAM_DIMG_LOG_HEIF) << "Error: Could not read source file.";
0155         heif_context_free(heif_context);
0156         loadingFailed();
0157 
0158 #if LIBHEIF_NUMERIC_VERSION >= 0x010d0000
0159 
0160         heif_deinit();
0161 
0162 #endif
0163 
0164         return false;
0165     }
0166 
0167     error = heif_context_get_primary_image_ID(heif_context, &primary_image_id);
0168 
0169     if (!isHeifSuccess(&error))
0170     {
0171         qCWarning(DIGIKAM_DIMG_LOG_HEIF) << "Error: Could not load image data.";
0172         heif_context_free(heif_context);
0173         loadingFailed();
0174 
0175 #if LIBHEIF_NUMERIC_VERSION >= 0x010d0000
0176 
0177         heif_deinit();
0178 
0179 #endif
0180 
0181         return false;
0182     }
0183 
0184     bool ret = readHEICImageByID(heif_context, primary_image_id);
0185     heif_context_free(heif_context);
0186 
0187 #if LIBHEIF_NUMERIC_VERSION >= 0x010d0000
0188 
0189     heif_deinit();
0190 
0191 #endif
0192 
0193     return ret;
0194 }
0195 
0196 bool DImgHEIFLoader::readHEICColorProfile(struct heif_image_handle* const image_handle)
0197 {
0198 
0199 #if LIBHEIF_NUMERIC_VERSION >= 0x01040000
0200 
0201     switch (heif_image_handle_get_color_profile_type(image_handle))
0202     {
0203         case heif_color_profile_type_not_present:
0204         {
0205             break;
0206         }
0207 
0208         case heif_color_profile_type_rICC:
0209         case heif_color_profile_type_prof:
0210         {
0211             size_t length = heif_image_handle_get_raw_color_profile_size(image_handle);
0212 
0213             if (length > 0)
0214             {
0215                 // Read color profile.
0216 
0217                 QByteArray profile;
0218                 profile.resize((int)length);
0219 
0220                 struct heif_error error = heif_image_handle_get_raw_color_profile(image_handle,
0221                                                                                   profile.data());
0222 
0223                 if (error.code == 0)
0224                 {
0225                     qCDebug(DIGIKAM_DIMG_LOG_HEIF) << "HEIF color profile found with size:" << length;
0226                     imageSetIccProfile(IccProfile(profile));
0227 
0228                     return true;
0229                 }
0230             }
0231 
0232             break;
0233         }
0234 
0235         default: // heif_color_profile_type_nclx
0236         {
0237             qCWarning(DIGIKAM_DIMG_LOG_HEIF) << "Unknown HEIF color profile type discarded";
0238             break;
0239         }
0240     }
0241 
0242 #else
0243 
0244     Q_UNUSED(image_handle);
0245 
0246 #endif
0247 
0248     // If ICC profile is null, check Exif metadata.
0249 
0250     if (checkExifWorkingColorSpace())
0251     {
0252         return true;
0253     }
0254 
0255     return false;
0256 }
0257 
0258 bool DImgHEIFLoader::readHEICImageByID(struct heif_context* const heif_context,
0259                                        heif_item_id image_id)
0260 {
0261     struct heif_image*        heif_image   = nullptr;
0262     struct heif_image_handle* image_handle = nullptr;
0263     struct heif_error error                = heif_context_get_image_handle(heif_context,
0264                                                                            image_id,
0265                                                                            &image_handle);
0266 
0267     if (!isHeifSuccess(&error))
0268     {
0269         loadingFailed();
0270 
0271         return false;
0272     }
0273 
0274     // NOTE: An HEIC image without ICC color profile or without metadata still valid.
0275 
0276     if (m_loadFlags & LoadICCData)
0277     {
0278         readHEICColorProfile(image_handle);
0279     }
0280 
0281     if (m_observer)
0282     {
0283         m_observer->progressInfo(0.2F);
0284     }
0285 
0286     if (m_loadFlags & LoadPreview)
0287     {
0288         heif_item_id thumbnail_ID = 0;
0289         int nThumbnails           = heif_image_handle_get_list_of_thumbnail_IDs(image_handle, &thumbnail_ID, 1);
0290 
0291         if (nThumbnails > 0)
0292         {
0293             struct heif_image_handle* thumbnail_handle = nullptr;
0294             error                                      = heif_image_handle_get_thumbnail(image_handle, thumbnail_ID, &thumbnail_handle);
0295 
0296             if (!isHeifSuccess(&error))
0297             {
0298                 loadingFailed();
0299                 heif_image_handle_release(image_handle);
0300 
0301                 return false;
0302             }
0303 
0304             // Save the original size, the size
0305             // may differ from the decoded image size.
0306 
0307             QSize originalSize(heif_image_handle_get_width(image_handle),
0308                                heif_image_handle_get_height(image_handle));
0309 
0310             heif_image_handle_release(image_handle);
0311 
0312             qCDebug(DIGIKAM_DIMG_LOG_HEIF) << "HEIF preview found in thumbnail chunk";
0313 
0314             bool ret = readHEICImageByHandle(thumbnail_handle, heif_image, true);
0315 
0316             // Restore original size.
0317 
0318             imageSetAttribute(QLatin1String("originalSize"), originalSize);
0319 
0320             return ret;
0321         }
0322 
0323         // Image has no preview, load image normally
0324 
0325         return readHEICImageByHandle(image_handle, heif_image, true);
0326     }
0327 
0328     // Load image data or only image metadata
0329 
0330     return readHEICImageByHandle(image_handle, heif_image, (m_loadFlags & LoadImageData));
0331 }
0332 
0333 bool DImgHEIFLoader::readHEICImageByHandle(struct heif_image_handle* image_handle,
0334                                            struct heif_image* heif_image, bool loadImageData)
0335 {
0336     int colorDepth = heif_image_handle_get_luma_bits_per_pixel(image_handle);
0337     int chromaBits = heif_image_handle_get_chroma_bits_per_pixel(image_handle);
0338 
0339     if (chromaBits == -1)
0340     {
0341         qCWarning(DIGIKAM_DIMG_LOG_HEIF) << "HEIC chroma bits information not valid!";
0342         loadingFailed();
0343         heif_image_release(heif_image);
0344         heif_image_handle_release(image_handle);
0345 
0346         return false;
0347     }
0348 
0349     // Copy HEIF image into data structures.
0350 
0351     struct heif_error error;
0352     struct heif_decoding_options* const decode_options = heif_decoding_options_alloc();
0353     decode_options->ignore_transformations             = 1;
0354     m_hasAlpha                                         = heif_image_handle_has_alpha_channel(image_handle);
0355 
0356     heif_chroma chroma;
0357 
0358     if      (colorDepth == 8)
0359     {
0360         chroma = m_hasAlpha ? heif_chroma_interleaved_RGBA
0361                             : heif_chroma_interleaved_RGB;
0362 
0363         m_sixteenBit = false;
0364     }
0365     else if ((colorDepth > 8) && (colorDepth <= 16))
0366     {
0367         chroma = m_hasAlpha ? heif_chroma_interleaved_RRGGBBAA_LE
0368                             : heif_chroma_interleaved_RRGGBB_LE;
0369 
0370         m_sixteenBit = true;
0371     }
0372     else
0373     {
0374         qCWarning(DIGIKAM_DIMG_LOG_HEIF) << "HEIF undefined bit depth:" << colorDepth;
0375         loadingFailed();
0376         heif_image_release(heif_image);
0377         heif_image_handle_release(image_handle);
0378         heif_decoding_options_free(decode_options);
0379 
0380         return false;
0381     }
0382 
0383     // Trace to check image size properties before decoding, as these values can be different.
0384 
0385     qCDebug(DIGIKAM_DIMG_LOG_HEIF) << "HEIF image size: ("
0386                                    << heif_image_handle_get_width(image_handle)
0387                                    << "x"
0388                                    << heif_image_handle_get_height(image_handle)
0389                                    << ")";
0390 
0391     error = heif_decode_image(image_handle,
0392                               &heif_image,
0393                               heif_colorspace_RGB,
0394                               chroma,
0395                               decode_options);
0396 
0397     if (!isHeifSuccess(&error))
0398     {
0399         loadingFailed();
0400         heif_image_release(heif_image);
0401         heif_image_handle_release(image_handle);
0402         heif_decoding_options_free(decode_options);
0403 
0404         return false;
0405     }
0406 
0407     if (m_observer)
0408     {
0409         m_observer->progressInfo(0.3F);
0410     }
0411 
0412     heif_decoding_options_free(decode_options);
0413 
0414     imageWidth()  = heif_image_get_width(heif_image, heif_channel_interleaved);
0415     imageHeight() = heif_image_get_height(heif_image, heif_channel_interleaved);
0416 
0417     qCDebug(DIGIKAM_DIMG_LOG_HEIF) << "Decoded HEIF image properties: size("
0418                                    << imageWidth() << "x" << imageHeight()
0419                                    << "), Alpha:" << m_hasAlpha
0420                                    << ", Color depth :" << colorDepth;
0421 
0422     if (!QSize(imageWidth(), imageHeight()).isValid())
0423     {
0424         loadingFailed();
0425         heif_image_release(heif_image);
0426         heif_image_handle_release(image_handle);
0427 
0428         return false;
0429     }
0430 
0431     int stride         = 0;
0432     uint8_t* const ptr = heif_image_get_plane(heif_image, heif_channel_interleaved, &stride);
0433 
0434     qCDebug(DIGIKAM_DIMG_LOG_HEIF) << "HEIF data container:" << ptr;
0435     qCDebug(DIGIKAM_DIMG_LOG_HEIF) << "HEIC bytes per line:" << stride;
0436     qCDebug(DIGIKAM_DIMG_LOG_HEIF) << "Color bytes depth:" << (m_sixteenBit ? 16 : 8);
0437 
0438     if (!ptr || (stride <= 0))
0439     {
0440         qCWarning(DIGIKAM_DIMG_LOG_HEIF) << "HEIC data pixels information not valid!";
0441         loadingFailed();
0442         heif_image_release(heif_image);
0443         heif_image_handle_release(image_handle);
0444 
0445         return false;
0446     }
0447 
0448     uchar* data    = nullptr;
0449     int colorModel = DImg::RGB;
0450     int colorMul   = (colorDepth > 8) ? (16 - colorDepth)
0451                                       : 1;   // color multiplier
0452 
0453     qCDebug(DIGIKAM_DIMG_LOG_HEIF) << "Color multiplier:" << colorMul;
0454 
0455     if (loadImageData)
0456     {
0457         if (m_sixteenBit)
0458         {
0459             data = new_failureTolerant(imageWidth(), imageHeight(), 8); // 16 bits/color/pixel
0460         }
0461         else
0462         {
0463             data = new_failureTolerant(imageWidth(), imageHeight(), 4); // 8 bits/color/pixel
0464         }
0465 
0466         if (!data)
0467         {
0468             qCWarning(DIGIKAM_DIMG_LOG_HEIF) << "Cannot allocate memory!";
0469             loadingFailed();
0470             heif_image_release(heif_image);
0471             heif_image_handle_release(image_handle);
0472 
0473             return false;
0474         }
0475 
0476         if (m_observer)
0477         {
0478             m_observer->progressInfo(0.4F);
0479         }
0480 
0481         uchar* dst              = data;
0482         unsigned short* dst16   = reinterpret_cast<unsigned short*>(data);
0483         uchar* src              = nullptr;
0484         unsigned short* src16   = nullptr;
0485         unsigned int checkPoint = 0;
0486 
0487         for (unsigned int y = 0 ; y < imageHeight() ; ++y)
0488         {
0489             src   = reinterpret_cast<unsigned char*>(ptr + (y * stride));
0490             src16 = reinterpret_cast<unsigned short*>(src);
0491 
0492             for (unsigned int x = 0 ; x < imageWidth() ; ++x)
0493             {
0494                 if (!m_sixteenBit)   // 8 bits image.
0495                 {
0496                     // Blue
0497 
0498                     dst[0] = src[2];
0499 
0500                     // Green
0501 
0502                     dst[1] = src[1];
0503 
0504                     // Red
0505 
0506                     dst[2] = src[0];
0507 
0508                     // Alpha
0509 
0510                     if (m_hasAlpha)
0511                     {
0512                         dst[3] = src[3];
0513                         src   += 4;
0514                     }
0515                     else
0516                     {
0517                         dst[3] = 0xFF;
0518                         src   += 3;
0519                     }
0520 
0521                     dst += 4;
0522                 }
0523                 else                // 16 bits image.
0524                 {
0525                     // Blue
0526 
0527                     dst16[0] = (unsigned short)(src16[2] << colorMul);
0528 
0529                     // Green
0530 
0531                     dst16[1] = (unsigned short)(src16[1] << colorMul);
0532 
0533                     // Red
0534 
0535                     dst16[2] = (unsigned short)(src16[0] << colorMul);
0536 
0537                     // Alpha
0538 
0539                     if (m_hasAlpha)
0540                     {
0541                         dst16[3] = (unsigned short)(src16[3] << colorMul);
0542                         src16   += 4;
0543                     }
0544                     else
0545                     {
0546                         dst16[3] = 0xFFFF;
0547                         src16   += 3;
0548                     }
0549 
0550                         dst16 += 4;
0551                 }
0552             }
0553 
0554             if (m_observer && y >= checkPoint)
0555             {
0556                 checkPoint += granularity(m_observer, y, 0.8F);
0557 
0558                 if (!m_observer->continueQuery())
0559                 {
0560                     loadingFailed();
0561                     heif_image_release(heif_image);
0562                     heif_image_handle_release(image_handle);
0563 
0564                     return false;
0565                 }
0566 
0567                 m_observer->progressInfo(0.4F + (0.8F * (((float)y) / ((float)imageHeight()))));
0568             }
0569         }
0570     }
0571 
0572     imageData() = data;
0573     imageSetAttribute(QLatin1String("format"),             QLatin1String("HEIF"));
0574     imageSetAttribute(QLatin1String("originalColorModel"), colorModel);
0575     imageSetAttribute(QLatin1String("originalBitDepth"),   m_sixteenBit ? 16 : 8);
0576     imageSetAttribute(QLatin1String("originalSize"),       QSize(imageWidth(), imageHeight()));
0577 
0578     if (m_observer)
0579     {
0580         m_observer->progressInfo(0.9F);
0581     }
0582 
0583     heif_image_release(heif_image);
0584     heif_image_handle_release(image_handle);
0585 
0586     return true;
0587 }
0588 
0589 } // namespace Digikam