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 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 ThumbnailIdentifier::ThumbnailIdentifier() 0023 : id(0) 0024 { 0025 } 0026 0027 ThumbnailIdentifier::ThumbnailIdentifier(const QString& filePath) 0028 : filePath(filePath), 0029 id (0) 0030 { 0031 } 0032 0033 ThumbnailInfo::ThumbnailInfo() 0034 : fileSize (0), 0035 isAccessible (false), 0036 orientationHint(DMetadata::ORIENTATION_UNSPECIFIED) 0037 { 0038 } 0039 0040 ThumbnailCreator::ThumbnailCreator(StorageMethod method) 0041 : d(new Private) 0042 { 0043 d->thumbnailStorage = method; 0044 initialize(); 0045 } 0046 0047 ThumbnailCreator::ThumbnailCreator(int thumbnailSize, StorageMethod method) 0048 : d(new Private) 0049 { 0050 setThumbnailSize(thumbnailSize); 0051 d->thumbnailStorage = method; 0052 initialize(); 0053 } 0054 0055 ThumbnailCreator::~ThumbnailCreator() 0056 { 0057 delete d; 0058 } 0059 0060 void ThumbnailCreator::initialize() 0061 { 0062 QString alphaPath = QStandardPaths::locate(QStandardPaths::AppDataLocation, 0063 QLatin1String("thumbnail/background.png")); 0064 0065 if (QFile::exists(alphaPath)) 0066 { 0067 if (d->alphaImage.load(alphaPath, "PNG")) 0068 { 0069 int max = qMax(d->alphaImage.width(), d->alphaImage.height()); 0070 int min = qMin(d->alphaImage.width(), d->alphaImage.height()); 0071 0072 if ((max > ThumbnailSize::MAX) || (min < 10)) 0073 { 0074 d->alphaImage = QImage(); 0075 } 0076 } 0077 } 0078 0079 if (d->alphaImage.isNull()) 0080 { 0081 d->alphaImage = QImage(20, 20, QImage::Format_RGB32); 0082 0083 // create checkerboard image 0084 0085 QPainter p(&d->alphaImage); 0086 p.fillRect( 0, 0, 20, 20, Qt::white); 0087 p.fillRect( 0, 10 ,10, 10, Qt::lightGray); 0088 p.fillRect(10, 0, 10, 10, Qt::lightGray); 0089 p.end(); 0090 } 0091 0092 if (d->thumbnailStorage == FreeDesktopStandard) 0093 { 0094 initThumbnailDirs(); 0095 } 0096 } 0097 0098 int ThumbnailCreator::Private::storageSize() const 0099 { 0100 // on-disk thumbnail sizes according to freedesktop spec 0101 // for thumbnail db it's always max size 0102 0103 double dpr = qApp->devicePixelRatio(); 0104 0105 if (onlyLargeThumbnails) 0106 { 0107 if ((dpr > 1.0) && (thumbnailStorage == ThumbnailDatabase)) 0108 { 0109 return ThumbnailSize::getUseLargeThumbs() ? ThumbnailSize::MAX 0110 : ThumbnailSize::HD; 0111 } 0112 else 0113 { 0114 return ThumbnailSize::maxThumbsSize(); 0115 } 0116 } 0117 else 0118 { 0119 if ((dpr > 1.0) && (thumbnailStorage == ThumbnailDatabase)) 0120 { 0121 return (thumbnailSize <= ThumbnailSize::Small) ? ThumbnailSize::Huge 0122 : ThumbnailSize::HD; 0123 } 0124 else 0125 { 0126 return (thumbnailSize <= ThumbnailSize::Medium) ? ThumbnailSize::Medium 0127 : ThumbnailSize::Huge; 0128 } 0129 } 0130 } 0131 0132 void ThumbnailCreator::setThumbnailSize(int thumbnailSize) 0133 { 0134 d->thumbnailSize = thumbnailSize; 0135 } 0136 0137 void ThumbnailCreator::setExifRotate(bool rotate) 0138 { 0139 d->exifRotate = rotate; 0140 } 0141 0142 void ThumbnailCreator::setOnlyLargeThumbnails(bool onlyLarge) 0143 { 0144 d->onlyLargeThumbnails = onlyLarge; 0145 } 0146 0147 void ThumbnailCreator::setRemoveAlphaChannel(bool removeAlpha) 0148 { 0149 d->removeAlphaChannel = removeAlpha; 0150 } 0151 0152 void ThumbnailCreator::setLoadingProperties(DImgLoaderObserver* const observer, const DRawDecoding& settings) 0153 { 0154 d->observer = observer; 0155 d->rawSettings = settings; 0156 } 0157 0158 void ThumbnailCreator::setThumbnailInfoProvider(ThumbnailInfoProvider* const provider) 0159 { 0160 d->infoProvider = provider; 0161 } 0162 0163 int ThumbnailCreator::thumbnailSize() const 0164 { 0165 return d->thumbnailSize; 0166 } 0167 0168 int ThumbnailCreator::storedSize() const 0169 { 0170 return d->storageSize(); 0171 } 0172 0173 QString ThumbnailCreator::errorString() const 0174 { 0175 return d->error; 0176 } 0177 0178 QImage ThumbnailCreator::load(const ThumbnailIdentifier& identifier, bool onlyStorage) const 0179 { 0180 return load(identifier, QRect(), false, onlyStorage); 0181 } 0182 0183 QImage ThumbnailCreator::loadDetail(const ThumbnailIdentifier& identifier, 0184 const QRect& rect, bool onlyStorage) const 0185 { 0186 if (!rect.isValid()) 0187 { 0188 qCWarning(DIGIKAM_GENERAL_LOG) << "Invalid rectangle" << rect; 0189 0190 return QImage(); 0191 } 0192 0193 return load(identifier, rect, false, onlyStorage); 0194 } 0195 0196 void ThumbnailCreator::pregenerate(const ThumbnailIdentifier& identifier) const 0197 { 0198 load(identifier, QRect(), true); 0199 } 0200 0201 void ThumbnailCreator::pregenerateDetail(const ThumbnailIdentifier& identifier, const QRect& rect) const 0202 { 0203 if (!rect.isValid()) 0204 { 0205 qCWarning(DIGIKAM_GENERAL_LOG) << "Invalid rectangle" << rect; 0206 0207 return; 0208 } 0209 0210 load(identifier, rect, true); 0211 } 0212 0213 QImage ThumbnailCreator::load(const ThumbnailIdentifier& identifier, 0214 const QRect& rect, bool pregenerate, bool onlyStorage) const 0215 { 0216 if (d->storageSize() <= 0) 0217 { 0218 d->error = i18n("No or invalid size specified"); 0219 qCWarning(DIGIKAM_GENERAL_LOG) << "No or invalid size specified"; 0220 0221 return QImage(); 0222 } 0223 0224 if (d->thumbnailStorage == ThumbnailDatabase) 0225 { 0226 d->dbIdForReplacement = -1; // Just to prevent bugs 0227 } 0228 0229 ThumbnailInfo info; 0230 ThumbnailImage image; 0231 0232 { 0233 FileReadLocker lock(identifier.filePath); 0234 0235 // Get info about path 0236 0237 info = makeThumbnailInfo(identifier, rect); 0238 0239 // Load pregenerated thumbnail 0240 0241 switch (d->thumbnailStorage) 0242 { 0243 case ThumbnailDatabase: 0244 { 0245 if (pregenerate) 0246 { 0247 if (isInDatabase(info)) 0248 { 0249 return QImage(); 0250 } 0251 0252 // Otherwise, fall through and generate 0253 } 0254 else 0255 { 0256 image = loadFromDatabase(info); 0257 } 0258 0259 break; 0260 } 0261 0262 case FreeDesktopStandard: 0263 { 0264 image = loadFreedesktop(info); 0265 break; 0266 } 0267 } 0268 } 0269 0270 // For images in offline collections we can stop here, they are not available on disk 0271 0272 if (image.isNull() && (onlyStorage || info.filePath.isEmpty())) 0273 { 0274 return QImage(); 0275 } 0276 0277 // If pre-generated thumbnail is not available, generate 0278 0279 if (image.isNull()) 0280 { 0281 FileWriteLocker lock(identifier.filePath); 0282 0283 switch (d->thumbnailStorage) 0284 { 0285 case ThumbnailDatabase: 0286 { 0287 if (isInDatabase(info)) 0288 { 0289 image = loadFromDatabase(info); 0290 } 0291 else 0292 { 0293 image = createThumbnail(info, rect); 0294 0295 if (!image.isNull()) 0296 { 0297 storeInDatabase(info, image); 0298 } 0299 } 0300 0301 break; 0302 } 0303 0304 case FreeDesktopStandard: 0305 { 0306 image = createThumbnail(info, rect); 0307 0308 if (!image.isNull()) 0309 { 0310 // Image is stored rotated 0311 0312 if (d->exifRotate) 0313 { 0314 image.qimage = exifRotate(image.qimage, image.exifOrientation); 0315 } 0316 0317 storeFreedesktop(info, image); 0318 } 0319 0320 break; 0321 } 0322 } 0323 } 0324 0325 if (image.isNull()) 0326 { 0327 d->error = i18n("Thumbnail is null"); 0328 qCWarning(DIGIKAM_GENERAL_LOG) << "Thumbnail is null for " << identifier.filePath; 0329 0330 return image.qimage; 0331 } 0332 0333 // If we only pregenerate, we have now created and stored in the database 0334 0335 if (pregenerate) 0336 { 0337 return QImage(); 0338 } 0339 0340 // Prepare for usage in digikam 0341 0342 image.qimage = image.qimage.scaled(d->thumbnailSize, 0343 d->thumbnailSize, 0344 Qt::KeepAspectRatio, 0345 Qt::SmoothTransformation); 0346 0347 image.qimage = handleAlphaChannel(image.qimage); 0348 0349 if (d->thumbnailStorage == ThumbnailDatabase) 0350 { 0351 // Image is stored, or created, unrotated, and is now rotated for display 0352 // detail thumbnails are stored readily rotated 0353 0354 if ((d->exifRotate && rect.isNull()) || (info.mimeType == QLatin1String("video"))) 0355 { 0356 image.qimage = exifRotate(image.qimage, image.exifOrientation); 0357 } 0358 } 0359 0360 if (!info.customIdentifier.isNull()) 0361 { 0362 image.qimage.setText(QLatin1String("customIdentifier"), info.customIdentifier); 0363 } 0364 0365 return image.qimage; 0366 } 0367 0368 QImage ThumbnailCreator::scaleForStorage(const QImage& qimage) const 0369 { 0370 if ((qimage.width() > d->storageSize()) || (qimage.height() > d->storageSize())) 0371 { 0372 /* 0373 Cheat scaling is disabled because of quality problems - see bug #224999 0374 0375 // Perform cheat scaling (https://www.qtcentre.org/threads/28415-Creating-thumbnails-efficiently) 0376 0377 int cheatSize = maxSize - (3*(maxSize - d->storageSize()) / 4); 0378 qimage = qimage.scaled(cheatSize, cheatSize, Qt::KeepAspectRatio, Qt::FastTransformation); 0379 */ 0380 QImage scaledThumb = qimage.scaled(d->storageSize(), 0381 d->storageSize(), 0382 Qt::KeepAspectRatio, 0383 Qt::SmoothTransformation); 0384 0385 return scaledThumb; 0386 } 0387 0388 return qimage; 0389 } 0390 0391 QString ThumbnailCreator::identifierForDetail(const ThumbnailInfo& info, const QRect& rect) 0392 { 0393 QUrl url = QUrl::fromLocalFile(info.filePath); 0394 url.setScheme(QLatin1String("detail")); 0395 /* 0396 A scheme to support loading by database id, but this is a hack. Solve cleanly later (schema update) 0397 0398 url.setPath(identifier.fileName); 0399 0400 if (!identifier.uniqueHash.isNull()) 0401 { 0402 url.addQueryItem("hash", identifier.uniqueHash); 0403 url.addQueryItem("filesize", QString::number(identifier.fileSize)); 0404 } 0405 else 0406 { 0407 url.addQueryItem("path", identifier.filePath); 0408 } 0409 */ 0410 QString r = QString::fromLatin1("%1,%2-%3x%4") 0411 .arg(rect.x()) 0412 .arg(rect.y()) 0413 .arg(rect.width()) 0414 .arg(rect.height()); 0415 0416 QUrlQuery q(url); 0417 q.addQueryItem(QLatin1String("rect"), r); 0418 url.setQuery(q); 0419 0420 return url.toString(); 0421 } 0422 0423 ThumbnailInfo ThumbnailCreator::makeThumbnailInfo(const ThumbnailIdentifier& identifier, const QRect& rect) const 0424 { 0425 ThumbnailInfo info; 0426 0427 if (d->infoProvider) 0428 { 0429 info = d->infoProvider->thumbnailInfo(identifier); 0430 } 0431 else 0432 { 0433 info = fileThumbnailInfo(identifier.filePath); 0434 } 0435 0436 if (!rect.isNull()) 0437 { 0438 // Important: Pass the filled info, not the possibly half-filled identifier here because the hash is preferred for the customIdentifier! 0439 0440 info.customIdentifier = identifierForDetail(info, rect); 0441 } 0442 0443 return info; 0444 } 0445 0446 void ThumbnailCreator::store(const QString& path, const QImage& i) const 0447 { 0448 store(path, i, QRect()); 0449 } 0450 0451 void ThumbnailCreator::storeDetailThumbnail(const QString& path, const QRect& detailRect, const QImage& i) const 0452 { 0453 store(path, i, detailRect); 0454 } 0455 0456 void ThumbnailCreator::store(const QString& path, const QImage& i, const QRect& rect) const 0457 { 0458 if (i.isNull()) 0459 { 0460 return; 0461 } 0462 0463 QImage qimage = scaleForStorage(i); 0464 ThumbnailInfo info = makeThumbnailInfo(ThumbnailIdentifier(path), rect); 0465 ThumbnailImage image; 0466 image.qimage = qimage; 0467 0468 switch (d->thumbnailStorage) 0469 { 0470 case ThumbnailDatabase: 0471 { 0472 // We must call isInDatabase or loadFromDatabase before storeInDatabase for d->dbIdForReplacement! 0473 0474 if (!isInDatabase(info)) 0475 { 0476 storeInDatabase(info, image); 0477 } 0478 0479 break; 0480 } 0481 0482 case FreeDesktopStandard: 0483 { 0484 storeFreedesktop(info, image); 0485 break; 0486 } 0487 } 0488 } 0489 0490 void ThumbnailCreator::deleteThumbnailsFromDisk(const QString& filePath) const 0491 { 0492 switch (d->thumbnailStorage) 0493 { 0494 case FreeDesktopStandard: 0495 { 0496 deleteFromDiskFreedesktop(filePath); 0497 break; 0498 } 0499 0500 case ThumbnailDatabase: 0501 { 0502 ThumbnailInfo info; 0503 0504 if (d->infoProvider) 0505 { 0506 info = d->infoProvider->thumbnailInfo(ThumbnailIdentifier(filePath)); 0507 } 0508 else 0509 { 0510 info = fileThumbnailInfo(filePath); 0511 } 0512 0513 deleteFromDatabase(info); 0514 break; 0515 } 0516 } 0517 } 0518 0519 } // namespace Digikam