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