File indexing completed on 2025-01-19 03:51:00

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 - save 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 // Qt includes
0018 
0019 #include <QFile>
0020 #include <QVariant>
0021 #include <QByteArray>
0022 #include <QTextStream>
0023 #include <QElapsedTimer>
0024 #include <QDataStream>
0025 #include <qplatformdefs.h>
0026 #include <QScopedPointer>
0027 
0028 // Local includes
0029 
0030 #include "digikam_config.h"
0031 #include "digikam_debug.h"
0032 #include "dimg.h"
0033 #include "dimgloaderobserver.h"
0034 #include "metaengine.h"
0035 
0036 // libx265 includes
0037 
0038 #if defined(__clang__)
0039 #   pragma clang diagnostic push
0040 #   pragma clang diagnostic ignored "-Wundef"
0041 #   pragma clang diagnostic ignored "-Wgnu-anonymous-struct"
0042 #   pragma clang diagnostic ignored "-Wnested-anon-types"
0043 #endif
0044 
0045 #ifdef HAVE_X265
0046 #   include <x265.h>
0047 #endif
0048 
0049 #if defined(__clang__)
0050 #   pragma clang diagnostic pop
0051 #endif
0052 
0053 namespace Digikam
0054 {
0055 
0056 static struct heif_error heifQIODeviceWriter(struct heif_context* /* ctx */,
0057                                              const void* data, size_t size, void* userdata)
0058 {
0059     QFile saveFile(QString::fromUtf8(static_cast<const char*>(userdata)));
0060     heif_error error;
0061 
0062     if (!saveFile.open(QIODevice::WriteOnly))
0063     {
0064         qCWarning(DIGIKAM_DIMG_LOG_HEIF) << "Cannot open target image file:"
0065                                          << saveFile.fileName();
0066 
0067         error.code    = heif_error_Encoding_error;
0068         error.subcode = heif_suberror_Cannot_write_output_data;
0069         error.message = QByteArray("File open error").constData();
0070 
0071         return error;
0072     }
0073 
0074     error.code          = heif_error_Ok;
0075     error.subcode       = heif_suberror_Unspecified;
0076     error.message       = QByteArray("Success").constData();
0077 
0078     qint64 bytesWritten = saveFile.write((const char*)data, size);
0079 
0080     if (bytesWritten < (qint64)size)
0081     {
0082         error.code    = heif_error_Encoding_error;
0083         error.subcode = heif_suberror_Cannot_write_output_data;
0084         error.message = QByteArray("File write error").constData();
0085     }
0086 
0087     saveFile.close();
0088 
0089     return error;
0090 }
0091 
0092 int DImgHEIFLoader::x265MaxBitsDepth()
0093 {
0094     int maxOutputBitsDepth = -1;
0095 
0096 #ifdef HAVE_X265
0097 
0098     qCDebug(DIGIKAM_DIMG_LOG_HEIF) << "HEVC encoder max bit depth:" << x265_max_bit_depth;
0099     const x265_api* api = x265_api_get(x265_max_bit_depth);
0100 
0101     if (api)
0102     {
0103         maxOutputBitsDepth = x265_max_bit_depth;
0104         qCDebug(DIGIKAM_DIMG_LOG_HEIF) << "HEVC encoder max bits depth:" << maxOutputBitsDepth;
0105     }
0106     else
0107     {
0108         api = x265_api_get(8); // Try to failback to default 8 bits
0109 
0110         if (api)
0111         {
0112             maxOutputBitsDepth = 8;
0113             qCDebug(DIGIKAM_DIMG_LOG_HEIF) << "HEVC encoder max bits depth: 8 (default failback value)";
0114         }
0115     }
0116 
0117 #endif
0118 
0119     // cppcheck-suppress knownConditionTrueFalse
0120     if (maxOutputBitsDepth == -1)
0121     {
0122         qCWarning(DIGIKAM_DIMG_LOG_HEIF) << "Cannot get max supported HEVC encoder bits depth!";
0123     }
0124 
0125     return maxOutputBitsDepth;
0126 }
0127 
0128 bool DImgHEIFLoader::save(const QString& filePath, DImgLoaderObserver* const observer)
0129 {
0130     m_observer             = observer;
0131 
0132     QVariant qualityAttr   = imageGetAttribute(QLatin1String("quality"));
0133     int quality            = qualityAttr.isValid() ? qualityAttr.toInt() : 75;
0134     bool lossless          = (quality == 0);
0135 
0136     // --- Determine libx265 encoder bits depth capability: 8=standard, 10, 12, or later 16.
0137 
0138     int maxOutputBitsDepth = x265MaxBitsDepth();
0139 
0140     if (maxOutputBitsDepth == -1)
0141     {
0142         return false;
0143     }
0144 
0145     heif_chroma chroma;
0146 
0147     if (maxOutputBitsDepth > 8)          // 16 bits image.
0148     {
0149         chroma = imageHasAlpha() ? heif_chroma_interleaved_RRGGBBAA_BE
0150                                  : heif_chroma_interleaved_RRGGBB_BE;
0151     }
0152     else
0153     {
0154         chroma = imageHasAlpha() ? heif_chroma_interleaved_RGBA
0155                                  : heif_chroma_interleaved_RGB;
0156     }
0157 
0158     // --- use standard HEVC encoder
0159 
0160     QElapsedTimer timer;
0161     timer.start();
0162 
0163     qCDebug(DIGIKAM_DIMG_LOG_HEIF) << "HEVC encoder setup...";
0164 
0165 #if LIBHEIF_NUMERIC_VERSION >= 0x010d0000
0166 
0167     heif_init(nullptr);
0168 
0169 #endif
0170 
0171     struct heif_context* const ctx = heif_context_alloc();
0172 
0173     if (!ctx)
0174     {
0175         qCWarning(DIGIKAM_DIMG_LOG_HEIF) << "Cannot create HEIF context!";
0176 
0177         return false;
0178     }
0179 
0180     struct heif_encoder* encoder   = nullptr;
0181     struct heif_error error        = heif_context_get_encoder_for_format(ctx,
0182                                                                          heif_compression_HEVC,
0183                                                                          &encoder);
0184     if (!isHeifSuccess(&error))
0185     {
0186         heif_context_free(ctx);
0187 
0188 #if LIBHEIF_NUMERIC_VERSION >= 0x010d0000
0189 
0190         heif_deinit();
0191 
0192 #endif
0193 
0194         return false;
0195     }
0196 
0197     heif_encoder_set_lossy_quality(encoder, quality);
0198     heif_encoder_set_lossless(encoder, lossless);
0199 
0200     struct heif_image* image = nullptr;
0201     error                    = heif_image_create(imageWidth(),
0202                                                  imageHeight(),
0203                                                  heif_colorspace_RGB,
0204                                                  chroma,
0205                                                  &image);
0206     if (!isHeifSuccess(&error))
0207     {
0208         heif_encoder_release(encoder);
0209         heif_context_free(ctx);
0210 
0211 #if LIBHEIF_NUMERIC_VERSION >= 0x010d0000
0212 
0213         heif_deinit();
0214 
0215 #endif
0216 
0217         return false;
0218     }
0219 
0220     // --- Save color profile before to create image data, as converting to color space can be processed at this stage.
0221 
0222     qCDebug(DIGIKAM_DIMG_LOG_HEIF) << "HEIF set color profile...";
0223 
0224     saveHEICColorProfile(image);
0225 
0226     // --- Add image data
0227 
0228     qCDebug(DIGIKAM_DIMG_LOG_HEIF) << "HEIF setup data plane...";
0229 
0230     error = heif_image_add_plane(image,
0231                                  heif_channel_interleaved,
0232                                  imageWidth(),
0233                                  imageHeight(),
0234                                  maxOutputBitsDepth);
0235 
0236     if (!isHeifSuccess(&error))
0237     {
0238         heif_encoder_release(encoder);
0239         heif_context_free(ctx);
0240 
0241 #if LIBHEIF_NUMERIC_VERSION >= 0x010d0000
0242 
0243         heif_deinit();
0244 
0245 #endif
0246 
0247         return false;
0248     }
0249 
0250     int stride          = 0;
0251     uint8_t* const data = heif_image_get_plane(image,
0252                                                heif_channel_interleaved,
0253                                                &stride);
0254 
0255     if (!data || (stride <= 0))
0256     {
0257         qCWarning(DIGIKAM_DIMG_LOG_HEIF) << "HEIF data pixels information not valid!";
0258         heif_encoder_release(encoder);
0259         heif_context_free(ctx);
0260 
0261 #if LIBHEIF_NUMERIC_VERSION >= 0x010d0000
0262 
0263         heif_deinit();
0264 
0265 #endif
0266 
0267         return false;
0268     }
0269 
0270     qCDebug(DIGIKAM_DIMG_LOG_HEIF) << "HEIF data container:" << data;
0271     qCDebug(DIGIKAM_DIMG_LOG_HEIF) << "HEIF bytes per line:" << stride;
0272 
0273     uint checkpoint           = 0;
0274     unsigned char r           = 0;
0275     unsigned char g           = 0;
0276     unsigned char b           = 0;
0277     unsigned char a           = 0;
0278     unsigned char* src        = nullptr;
0279     unsigned char* dst        = nullptr;
0280     unsigned short r16        = 0;
0281     unsigned short g16        = 0;
0282     unsigned short b16        = 0;
0283     unsigned short a16        = 0;
0284     unsigned short* src16     = nullptr;
0285     unsigned short* dst16     = nullptr;
0286     int div16                 = 16 - maxOutputBitsDepth;
0287     int mul8                  = maxOutputBitsDepth - 8;
0288     int nbOutputBytesPerColor = (maxOutputBitsDepth > 8) ? (imageHasAlpha() ? 4 * 2 : 3 * 2)  // output data stored on 16 bits
0289                                                          : (imageHasAlpha() ? 4     : 3    ); // output data stored on 8 bits
0290 
0291     qCDebug(DIGIKAM_DIMG_LOG_HEIF) << "HEIF output bytes per color:" << nbOutputBytesPerColor;
0292     qCDebug(DIGIKAM_DIMG_LOG_HEIF) << "HEIF 16 to 8 bits coeff.   :" << div16;
0293     qCDebug(DIGIKAM_DIMG_LOG_HEIF) << "HEIF 8 to 16 bits coeff.   :" << mul8;
0294 
0295     for (unsigned int y = 0 ; y < imageHeight() ; ++y)
0296     {
0297         src   = &imageData()[(y * imageWidth()) * imageBytesDepth()];
0298         src16 = reinterpret_cast<unsigned short*>(src);
0299         dst   = reinterpret_cast<unsigned char*>(data + (y * stride));
0300         dst16 = reinterpret_cast<unsigned short*>(dst);
0301 
0302         for (unsigned int x = 0 ; x < imageWidth() ; ++x)
0303         {
0304             if (imageSixteenBit())          // 16 bits source image.
0305             {
0306                 b16 = src16[0];
0307                 g16 = src16[1];
0308                 r16 = src16[2];
0309 
0310                 if (imageHasAlpha())
0311                 {
0312                     a16 = src16[3];
0313                 }
0314 
0315                 if (maxOutputBitsDepth > 8) // From 16 bits to 10 bits or more.
0316                 {
0317                     dst16[0] = (unsigned short)(r16 >> div16);
0318                     dst16[1] = (unsigned short)(g16 >> div16);
0319                     dst16[2] = (unsigned short)(b16 >> div16);
0320 
0321                     if (imageHasAlpha())
0322                     {
0323                         dst16[3] = (unsigned short)(a16 >> div16);
0324                         dst16   += 4;
0325                     }
0326                     else
0327                     {
0328                         dst16 += 3;
0329                     }
0330                 }
0331                 else                        // From 16 bits to 8 bits.
0332                 {
0333                     dst[0] = (unsigned char)(r16 >> div16);
0334                     dst[1] = (unsigned char)(g16 >> div16);
0335                     dst[2] = (unsigned char)(b16 >> div16);
0336 
0337                     if (imageHasAlpha())
0338                     {
0339                         dst[3] = (unsigned char)(a16 >> div16);
0340                         dst   += 4;
0341                     }
0342                     else
0343                     {
0344                         dst += 3;
0345                     }
0346                 }
0347 
0348                 src16 += 4;
0349             }
0350             else                            // 8 bits source image.
0351             {
0352                 b = src[0];
0353                 g = src[1];
0354                 r = src[2];
0355 
0356                 if (imageHasAlpha())
0357                 {
0358                     a = src[3];
0359                 }
0360 
0361                 if (maxOutputBitsDepth > 8) // From 8 bits to 10 bits or more.
0362                 {
0363                     dst16[0] = (unsigned short)(r << mul8);
0364                     dst16[1] = (unsigned short)(g << mul8);
0365                     dst16[2] = (unsigned short)(b << mul8);
0366 
0367                     if (imageHasAlpha())
0368                     {
0369                         dst16[3] = (unsigned short)(a << mul8);
0370                         dst16   += 4;
0371                     }
0372                     else
0373                     {
0374                         dst16 += 3;
0375                     }
0376                 }
0377                 else                        // From 8 bits to 8 bits.
0378                 {
0379                     dst[0] = r;
0380                     dst[1] = g;
0381                     dst[2] = b;
0382 
0383                     if (imageHasAlpha())
0384                     {
0385                         dst[3] = a;
0386                         dst   += 4;
0387                     }
0388                     else
0389                     {
0390                         dst += 3;
0391                     }
0392                 }
0393 
0394                 src += 4;
0395             }
0396         }
0397 
0398         if (m_observer && (y == (unsigned int)checkpoint))
0399         {
0400             checkpoint += granularity(m_observer, imageHeight(), 0.8F);
0401 
0402             if (!m_observer->continueQuery())
0403             {
0404                 heif_encoder_release(encoder);
0405                 heif_context_free(ctx);
0406 
0407 #if LIBHEIF_NUMERIC_VERSION >= 0x010d0000
0408 
0409                 heif_deinit();
0410 
0411 #endif
0412                 return false;
0413             }
0414 
0415             m_observer->progressInfo(0.1F + (0.8F * (((float)y) / ((float)imageHeight()))));
0416         }
0417     }
0418 
0419     qCDebug(DIGIKAM_DIMG_LOG_HEIF) << "HEIF master image encoding...";
0420 
0421     // --- encode and write master image
0422 
0423     struct heif_encoding_options* const options = heif_encoding_options_alloc();
0424     options->save_alpha_channel                 = imageHasAlpha() ? 1 : 0;
0425     struct heif_image_handle* image_handle      = nullptr;
0426     error                                       = heif_context_encode_image(ctx,
0427                                                                             image,
0428                                                                             encoder,
0429                                                                             options,
0430                                                                             &image_handle);
0431 
0432     if (!isHeifSuccess(&error))
0433     {
0434         heif_encoding_options_free(options);
0435         heif_image_handle_release(image_handle);
0436         heif_encoder_release(encoder);
0437         heif_context_free(ctx);
0438 
0439 #if LIBHEIF_NUMERIC_VERSION >= 0x010d0000
0440 
0441         heif_deinit();
0442 
0443 #endif
0444 
0445         return false;
0446     }
0447 
0448     // --- encode thumbnail
0449 
0450     // Note: Only encode preview for large image.
0451     // We will use the same preview size than DImg::prepareMetadataToSave()
0452 
0453     const int previewSize = 1280;
0454 
0455     if (qMin(imageWidth(), imageHeight()) > previewSize)
0456     {
0457         qCDebug(DIGIKAM_DIMG_LOG_HEIF) << "HEIF preview storage in thumbnail chunk...";
0458 
0459         struct heif_image_handle* thumbnail_handle = nullptr;
0460 
0461         error = heif_context_encode_thumbnail(ctx,
0462                                               image,
0463                                               image_handle,
0464                                               encoder,
0465                                               options,
0466                                               previewSize,
0467                                               &thumbnail_handle);
0468         if (!isHeifSuccess(&error))
0469         {
0470             heif_encoding_options_free(options);
0471             heif_image_handle_release(image_handle);
0472             heif_encoder_release(encoder);
0473             heif_context_free(ctx);
0474 
0475 #if LIBHEIF_NUMERIC_VERSION >= 0x010d0000
0476 
0477             heif_deinit();
0478 
0479 #endif
0480 
0481             return false;
0482         }
0483 
0484         heif_image_handle_release(thumbnail_handle);
0485     }
0486 
0487     heif_encoding_options_free(options);
0488     heif_encoder_release(encoder);
0489 
0490     // --- Add Exif and XMP metadata
0491 
0492     qCDebug(DIGIKAM_DIMG_LOG_HEIF) << "HEIF metadata storage...";
0493 
0494     saveHEICMetadata(ctx, image_handle);
0495 
0496     heif_image_handle_release(image_handle);
0497 
0498     // --- write HEIF file
0499 
0500     qCDebug(DIGIKAM_DIMG_LOG_HEIF) << "HEIF flush to file...";
0501     qCDebug(DIGIKAM_DIMG_LOG_HEIF) << "HEIF encoding took:" << timer.elapsed() << "ms";
0502 
0503     heif_writer writer;
0504     writer.writer_api_version = 1;
0505     writer.write              = heifQIODeviceWriter;
0506 
0507     error                     = heif_context_write(ctx, &writer, (void*)filePath.toUtf8().constData());
0508 
0509     if (!isHeifSuccess(&error))
0510     {
0511         heif_context_free(ctx);
0512 
0513 #if LIBHEIF_NUMERIC_VERSION >= 0x010d0000
0514 
0515         heif_deinit();
0516 
0517 #endif
0518 
0519         return false;
0520     }
0521 
0522     heif_context_free(ctx);
0523 
0524     imageSetAttribute(QLatin1String("savedFormat"), QLatin1String("HEIF"));
0525     saveMetadata(filePath);
0526 
0527 #if LIBHEIF_NUMERIC_VERSION >= 0x010d0000
0528 
0529     heif_deinit();
0530 
0531 #endif
0532 
0533     return true;
0534 }
0535 
0536 bool DImgHEIFLoader::saveHEICColorProfile(struct heif_image* const image)
0537 {
0538 
0539 #if LIBHEIF_NUMERIC_VERSION >= 0x01040000
0540 
0541     QByteArray profile = m_image->getIccProfile().data();
0542 
0543     if (!profile.isEmpty())
0544     {
0545         // Save color profile.
0546 
0547         struct heif_error error = heif_image_set_raw_color_profile(image,
0548                                                                    "prof",           // FIXME: detect string in profile data
0549                                                                    profile.data(),
0550                                                                    profile.size());
0551 
0552         if (error.code != 0)
0553         {
0554             qCWarning(DIGIKAM_DIMG_LOG_HEIF) << "Cannot set HEIF color profile!";
0555             return false;
0556         }
0557 
0558         qCDebug(DIGIKAM_DIMG_LOG_HEIF) << "Stored HEIF color profile size:" << profile.size();
0559     }
0560 
0561 #else
0562 
0563     Q_UNUSED(image_handle);
0564 
0565 #endif
0566 
0567     return true;
0568 }
0569 
0570 bool DImgHEIFLoader::saveHEICMetadata(struct heif_context* const heif_context,
0571                                       struct heif_image_handle* const image_handle)
0572 {
0573     QScopedPointer<MetaEngine> meta(new MetaEngine(m_image->getMetadata()));
0574 
0575     if (!meta->hasExif() && !meta->hasIptc() && !meta->hasXmp())
0576     {
0577         return false;
0578     }
0579 
0580     QByteArray exif = meta->getExifEncoded();
0581     QByteArray iptc = meta->getIptc();
0582     QByteArray xmp  = meta->getXmp();
0583     struct heif_error error;
0584 
0585     if (!exif.isEmpty())
0586     {
0587         error = heif_context_add_exif_metadata(heif_context,
0588                                                image_handle,
0589                                                exif.data(),
0590                                                exif.size());
0591 
0592         if (error.code != 0)
0593         {
0594             qCWarning(DIGIKAM_DIMG_LOG_HEIF) << "Cannot store HEIF Exif metadata!";
0595             return false;
0596         }
0597 
0598         qCDebug(DIGIKAM_DIMG_LOG_HEIF) << "Stored HEIF Exif data size:" << exif.size();
0599     }
0600 
0601     if (!iptc.isEmpty())
0602     {
0603         error = heif_context_add_generic_metadata(heif_context,
0604                                                   image_handle,
0605                                                   iptc.data(),
0606                                                   iptc.size(),
0607                                                   "iptc",
0608                                                   nullptr);
0609 
0610         if (error.code != 0)
0611         {
0612             qCWarning(DIGIKAM_DIMG_LOG_HEIF) << "Cannot store HEIF Iptc metadata!";
0613 
0614             return false;
0615         }
0616 
0617         qCDebug(DIGIKAM_DIMG_LOG_HEIF) << "Stored HEIF Iptc data size:" << iptc.size();
0618     }
0619 
0620     if (!xmp.isEmpty())
0621     {
0622         error = heif_context_add_XMP_metadata(heif_context,
0623                                               image_handle,
0624                                               xmp.data(),
0625                                               xmp.size());
0626 
0627         if (error.code != 0)
0628         {
0629             qCWarning(DIGIKAM_DIMG_LOG_HEIF) << "Cannot store HEIF Xmp metadata!";
0630 
0631             return false;
0632         }
0633 
0634         qCDebug(DIGIKAM_DIMG_LOG_HEIF) << "Stored HEIF Xmp data size:" << xmp.size();
0635     }
0636 
0637     return true;
0638 }
0639 
0640 } // namespace Digikam