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