File indexing completed on 2025-04-27 03:58:09
0001 /* ============================================================ 0002 * 0003 * This file is a part of digiKam project 0004 * https://www.digikam.org 0005 * 0006 * Date : 2007-07-20 0007 * Description : Loader for thumbnails - thumbnail generation and image handling 0008 * 0009 * SPDX-FileCopyrightText: 2003-2005 by Renchi Raju <renchi dot raju at gmail dot com> 0010 * SPDX-FileCopyrightText: 2003-2024 by Gilles Caulier <caulier dot gilles at gmail dot com> 0011 * SPDX-FileCopyrightText: 2006-2011 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de> 0012 * 0013 * SPDX-License-Identifier: GPL-2.0-or-later 0014 * 0015 * ============================================================ */ 0016 0017 #include "thumbnailcreator_p.h" 0018 0019 namespace Digikam 0020 { 0021 0022 ThumbnailImage ThumbnailCreator::createThumbnail(const ThumbnailInfo& info, const QRect& detailRect) const 0023 { 0024 const QString path = info.filePath; 0025 QFileInfo fileInfo(path); 0026 0027 if (!info.isAccessible || !fileInfo.exists() || !fileInfo.isFile()) 0028 { 0029 d->error = i18n("File does not exist or is not a file"); 0030 return ThumbnailImage(); 0031 } 0032 0033 QImage qimage; 0034 QScopedPointer<DMetadata> metadata(new DMetadata(path)); 0035 bool fromEmbeddedPreview = false; 0036 bool fromDetail = false; 0037 bool failedAtDImg = false; 0038 bool failedAtJPEGScaled = false; 0039 bool failedAtPGFScaled = false; 0040 0041 // -- Get the image preview -------------------------------- 0042 0043 IccProfile profile; 0044 bool colorManage = IccSettings::instance()->useManagedPreviews(); 0045 0046 if (!detailRect.isNull()) 0047 { 0048 qCDebug(DIGIKAM_GENERAL_LOG) << "Trying to get thumbnail with details for" << path; 0049 0050 // When taking a detail, we have to load the image full size 0051 0052 qimage = loadImageDetail(info, *metadata, detailRect, &profile); 0053 fromDetail = !qimage.isNull(); 0054 } 0055 else 0056 { 0057 qCDebug(DIGIKAM_GENERAL_LOG) << "Trying to get thumbnail from" << path << "(" << info.mimeType << ")"; 0058 QString ext = fileInfo.suffix().toUpper(); 0059 0060 if ( 0061 (info.mimeType == QLatin1String("image")) || 0062 (ext == QLatin1String("AVIF")) || // See bug #109060 0063 (ext == QLatin1String("JPX")) 0064 ) 0065 { 0066 if (qimage.isNull()) 0067 { 0068 // Try to extract Exif/IPTC preview first. 0069 0070 qimage = loadImagePreview(*metadata); 0071 } 0072 0073 if (d->observer && !d->observer->continueQuery()) 0074 { 0075 return ThumbnailImage(); 0076 } 0077 0078 // To speed-up thumb extraction, we now try to load the images by the file extension. 0079 0080 if (qimage.isNull() && !ext.isEmpty()) 0081 { 0082 if ((ext == QLatin1String("JPEG")) || 0083 (ext == QLatin1String("JPG")) || 0084 (ext == QLatin1String("JPE"))) 0085 { 0086 if (colorManage) 0087 { 0088 qimage = loadWithDImgScaled(path, &profile); 0089 } 0090 else 0091 { 0092 // Use jpegutils 0093 0094 JPEGUtils::loadJPEGScaled(qimage, path, d->storageSize()); 0095 } 0096 0097 failedAtJPEGScaled = qimage.isNull(); 0098 } 0099 else if ((ext == QLatin1String("PNG")) || 0100 (ext == QLatin1String("TIFF")) || 0101 (ext == QLatin1String("TIF"))) 0102 { 0103 // Use DImg load scaled mode 0104 0105 qimage = loadWithDImgScaled(path, &profile); 0106 failedAtDImg = qimage.isNull(); 0107 } 0108 else if (ext == QLatin1String("PGF")) 0109 { 0110 // Use pgf library to extract reduced version 0111 0112 PGFUtils::loadPGFScaled(qimage, path, d->storageSize()); 0113 failedAtPGFScaled = qimage.isNull(); 0114 } 0115 0116 if (d->observer && !d->observer->continueQuery()) 0117 { 0118 return ThumbnailImage(); 0119 } 0120 } 0121 0122 // Trying to load with libraw: RAW files. 0123 0124 if (qimage.isNull()) 0125 { 0126 qCDebug(DIGIKAM_GENERAL_LOG) << "Trying to get thumbnail from Embedded preview with libraw for" << path; 0127 0128 if (DRawDecoder::loadEmbeddedPreview(qimage, path)) 0129 { 0130 fromEmbeddedPreview = true; 0131 profile = metadata->getIccProfile(); 0132 } 0133 } 0134 0135 if (qimage.isNull()) 0136 { 0137 qCDebug(DIGIKAM_GENERAL_LOG) << "Trying to get thumbnail from half preview with libraw for" << path; 0138 0139 // TODO: Use DImg based loader instead? 0140 // We store thumbnails unrotated, loading 0141 // half preview from libraw unrotated here. 0142 0143 DRawDecoder::loadHalfPreview(qimage, path, false); 0144 } 0145 0146 // Special case with DNG file. See bug #338081 0147 0148 if (qimage.isNull()) 0149 { 0150 qCDebug(DIGIKAM_GENERAL_LOG) << "Trying to get thumbnail from Embedded preview with Exiv2 for" << path; 0151 0152 MetaEnginePreviews preview(path); 0153 qimage = preview.image(); 0154 } 0155 0156 // DImg-dependent loading methods: TIFF, PNG, everything supported by QImage 0157 0158 if (qimage.isNull() && !failedAtDImg) 0159 { 0160 qimage = loadWithDImgScaled(path, &profile); 0161 0162 if (d->observer && !d->observer->continueQuery()) 0163 { 0164 return ThumbnailImage(); 0165 } 0166 } 0167 0168 // Try JPEG anyway 0169 0170 if (qimage.isNull() && !failedAtJPEGScaled) 0171 { 0172 // Use jpegutils 0173 0174 JPEGUtils::loadJPEGScaled(qimage, path, d->storageSize()); 0175 } 0176 0177 // Try PGF anyway 0178 0179 if (qimage.isNull() && !failedAtPGFScaled) 0180 { 0181 // Use pgfutils 0182 0183 PGFUtils::loadPGFScaled(qimage, path, d->storageSize()); 0184 } 0185 } 0186 else 0187 { 0188 // Try video thumbnail anyway 0189 0190 #ifdef HAVE_MEDIAPLAYER 0191 0192 qCDebug(DIGIKAM_GENERAL_LOG) << "Trying to load video preview with FFmpeg"; 0193 0194 VideoThumbnailer thumbnailer; 0195 VideoStripFilter videoStrip; 0196 0197 thumbnailer.addFilter(&videoStrip); 0198 thumbnailer.setThumbnailSize(d->storageSize()); 0199 thumbnailer.generateThumbnail(path, qimage); 0200 0201 #else 0202 0203 qDebug(DIGIKAM_GENERAL_LOG) << "Cannot load video preview for" << path; 0204 qDebug(DIGIKAM_GENERAL_LOG) << "Video support is not available"; 0205 0206 #endif 0207 0208 } 0209 } 0210 0211 if (qimage.isNull()) 0212 { 0213 d->error = i18n("Cannot create thumbnail for %1", path); 0214 qCWarning(DIGIKAM_GENERAL_LOG) << "Cannot create thumbnail for" << path; 0215 0216 return ThumbnailImage(); 0217 } 0218 0219 qimage = scaleForStorage(qimage); 0220 0221 if (colorManage && !profile.isNull()) 0222 { 0223 IccManager::transformToSRGB(qimage, profile); 0224 } 0225 0226 ThumbnailImage image; 0227 image.qimage = qimage; 0228 image.exifOrientation = exifOrientation(info, *metadata, fromEmbeddedPreview, fromDetail); 0229 0230 return image; 0231 } 0232 0233 QImage ThumbnailCreator::loadWithDImgScaled(const QString& path, IccProfile* const profile) const 0234 { 0235 DImg img; 0236 img.setAttribute(QLatin1String("scaledLoadingSize"), d->storageSize()); 0237 0238 if (!img.load(path, false, profile ? true : false, false, false, d->observer, d->rawSettings)) 0239 { 0240 return QImage(); 0241 } 0242 0243 if (profile) 0244 { 0245 *profile = img.getIccProfile(); 0246 } 0247 0248 return img.copyQImage(); 0249 } 0250 0251 QImage ThumbnailCreator::loadImageDetail(const ThumbnailInfo& info, 0252 const DMetadata& metadata, 0253 const QRect& detailRect, 0254 IccProfile* const profile) const 0255 { 0256 const QString& path = info.filePath; 0257 0258 qDebug(DIGIKAM_GENERAL_LOG) << "Try get thumbnail from Metadata preview for" << path; 0259 0260 // Check the first and largest preview (Raw files) 0261 0262 MetaEnginePreviews previews(path); 0263 0264 if (!previews.isEmpty()) 0265 { 0266 QSize orgSize; 0267 0268 if (metadata.getExifTagString("Exif.Image.Make").toUpper() == QLatin1String("FUJIFILM")) 0269 { 0270 QString sHeight = metadata.getExifTagString("Exif.Fujifilm.RawImageFullHeight"); 0271 QString sWidth = metadata.getExifTagString("Exif.Fujifilm.RawImageFullWidth"); 0272 0273 if (!sWidth.isEmpty() && !sHeight.isEmpty()) 0274 { 0275 orgSize = QSize(sWidth.toInt(), 0276 sHeight.toInt()); 0277 } 0278 } 0279 0280 if (!orgSize.isValid()) 0281 { 0282 orgSize = previews.originalSize(); 0283 } 0284 0285 // Discard if smaller than half preview 0286 0287 QImage qimage = previews.image(); 0288 int acceptableWidth = lround(orgSize.width() * 0.5); 0289 int acceptableHeight = lround(orgSize.height() * 0.5); 0290 0291 if (!qimage.isNull() && (previews.width() >= acceptableWidth) && (previews.height() >= acceptableHeight)) 0292 { 0293 qimage = exifRotate(qimage, exifOrientation(info, metadata, true, false)); 0294 0295 if (((qimage.width() < qimage.height()) && (orgSize.width() > orgSize.height())) || 0296 ((qimage.width() > qimage.height()) && (orgSize.width() < orgSize.height()))) 0297 { 0298 orgSize.transpose(); 0299 } 0300 0301 QRect reducedSizeDetail = TagRegion::mapFromOriginalSize(orgSize, qimage.size(), detailRect); 0302 0303 return qimage.copy(reducedSizeDetail.intersected(qimage.rect())); 0304 } 0305 } 0306 0307 // load DImg 0308 0309 DImg img; 0310 0311 DImgLoader::LoadFlags loadFlags = DImgLoader::LoadItemInfo | 0312 DImgLoader::LoadMetadata | 0313 DImgLoader::LoadICCData | 0314 DImgLoader::LoadPreview; 0315 0316 qDebug(DIGIKAM_GENERAL_LOG) << "Try to get thumbnail from DImg preview for" << path; 0317 0318 if (img.load(path, loadFlags, d->observer, d->fastRawSettings)) 0319 { 0320 // Discard if smaller than half preview 0321 0322 unsigned int acceptableWidth = lround(img.originalRatioSize().width() * 0.5); 0323 unsigned int acceptableHeight = lround(img.originalRatioSize().height() * 0.5); 0324 0325 if ((img.width() < acceptableWidth) && (img.height() < acceptableHeight)) 0326 { 0327 qDebug(DIGIKAM_GENERAL_LOG) << "Preview image is smaller than the accepted size:" 0328 << acceptableWidth << "x" << acceptableHeight; 0329 img.reset(); 0330 } 0331 } 0332 0333 if (img.isNull()) 0334 { 0335 qDebug(DIGIKAM_GENERAL_LOG) << "Try to get thumbnail from DImg scaled for" << path; 0336 0337 // TODO: scaledLoading if detailRect is large 0338 // TODO: use code from PreviewTask, including cache storage 0339 0340 if (!img.load(path, false, profile ? true : false, false, false, d->observer, d->fastRawSettings)) 0341 { 0342 return QImage(); 0343 } 0344 } 0345 0346 if (profile) 0347 { 0348 *profile = img.getIccProfile(); 0349 } 0350 0351 // We must rotate before clipping because the rect refers to the oriented image. 0352 // I do not know currently how to back-rotate the rect for clipping before rotation. 0353 // If someone has the mathematics, have a go. 0354 0355 img.rotateAndFlip(exifOrientation(info, metadata, false, false)); 0356 0357 QRect mappedDetail = TagRegion::mapFromOriginalSize(img, detailRect); 0358 img.crop(mappedDetail.intersected(QRect(0, 0, img.width(), img.height()))); 0359 0360 return img.copyQImage(); 0361 } 0362 0363 QImage ThumbnailCreator::loadImagePreview(const DMetadata& metadata) const 0364 { 0365 QImage image; 0366 0367 qCDebug(DIGIKAM_GENERAL_LOG) << "Trying to get thumbnail with Exiv2 for" << metadata.getFilePath(); 0368 0369 if (metadata.getItemPreview(image)) 0370 { 0371 qCDebug(DIGIKAM_GENERAL_LOG) << "Get thumbnail from Exif/IPTC preview. Size of image: " 0372 << image.width() << "x" << image.height(); 0373 } 0374 else 0375 { 0376 // load DImg preview 0377 0378 qCDebug(DIGIKAM_GENERAL_LOG) << "Trying to get thumbnail with DImg preview for" << metadata.getFilePath(); 0379 0380 DImg img; 0381 DImgLoader::LoadFlags loadFlags = DImgLoader::LoadItemInfo | 0382 DImgLoader::LoadMetadata | 0383 DImgLoader::LoadICCData | 0384 DImgLoader::LoadPreview; 0385 0386 if (img.load(metadata.getFilePath(), loadFlags, d->observer, d->fastRawSettings)) 0387 { 0388 image = img.copyQImage(); 0389 } 0390 } 0391 0392 return image; 0393 } 0394 0395 QImage ThumbnailCreator::handleAlphaChannel(const QImage& qimage) const 0396 { 0397 switch (qimage.format()) 0398 { 0399 case QImage::Format_RGB32: 0400 { 0401 break; 0402 } 0403 0404 case QImage::Format_ARGB32: 0405 case QImage::Format_ARGB32_Premultiplied: 0406 { 0407 QImage newImage(qimage.size(), QImage::Format_RGB32); 0408 newImage.fill(Qt::transparent); 0409 QPainter p(&newImage); 0410 0411 if (d->removeAlphaChannel) 0412 { 0413 QBrush brush(d->alphaImage); 0414 p.fillRect(newImage.rect(), brush); 0415 } 0416 0417 p.drawImage(0, 0, qimage); 0418 p.end(); 0419 0420 return newImage; 0421 } 0422 0423 default: // indexed and monochrome formats 0424 { 0425 return qimage.convertToFormat(QImage::Format_RGB32); 0426 } 0427 } 0428 0429 return qimage; 0430 } 0431 0432 int ThumbnailCreator::exifOrientation(const ThumbnailInfo& info, 0433 const DMetadata& metadata, 0434 bool fromEmbeddedPreview, 0435 bool fromDetail) const 0436 { 0437 if (fromDetail) 0438 { 0439 return DMetadata::ORIENTATION_NORMAL; 0440 } 0441 0442 return LoadSaveThread::exifOrientation(info.filePath, metadata, 0443 DImg::fileFormat(info.filePath) == DImg::RAW, 0444 fromEmbeddedPreview); 0445 } 0446 0447 QImage ThumbnailCreator::exifRotate(const QImage& thumb, int orientation) const 0448 { 0449 if ((orientation == DMetadata::ORIENTATION_NORMAL) || 0450 (orientation == DMetadata::ORIENTATION_UNSPECIFIED)) 0451 { 0452 return thumb; 0453 } 0454 0455 QTransform matrix = MetaEngineRotation::toTransform((MetaEngine::ImageOrientation)orientation); 0456 0457 // Transform accordingly 0458 0459 return thumb.transformed(matrix); 0460 } 0461 0462 } // namespace Digikam