|
| 1 | +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. |
| 2 | +// |
| 3 | +// SPDX-License-Identifier: LGPL-3.0-or-later |
| 4 | + |
| 5 | +#include "cachedwallpaper.h" |
| 6 | +#include "scaleimagethread.h" |
| 7 | +#include "imageeffectprocessor.h" |
| 8 | + |
| 9 | +#include <QDebug> |
| 10 | +#include <QDir> |
| 11 | +#include <QFile> |
| 12 | +#include <QCryptographicHash> |
| 13 | +#include <QFileInfo> |
| 14 | +#include <QDateTime> |
| 15 | +#include <QImageReader> |
| 16 | + |
| 17 | +CachedWallpaper::CachedWallpaper() |
| 18 | +{ |
| 19 | + QDir().mkpath(kBlurCacheDir); |
| 20 | +} |
| 21 | + |
| 22 | +CachedWallpaper::~CachedWallpaper() |
| 23 | +{ |
| 24 | + m_cachedImages.clear(); |
| 25 | +} |
| 26 | + |
| 27 | +CachedWallpaper *CachedWallpaper::instance() |
| 28 | +{ |
| 29 | + static CachedWallpaper cachedWallpaper; |
| 30 | + return &cachedWallpaper; |
| 31 | +} |
| 32 | + |
| 33 | +QStringList CachedWallpaper::getCachedImagePaths(const QString &originalPath, const QList<QSize> &sizes, bool isMd5Path) |
| 34 | +{ |
| 35 | + QList<QSize> noCachedSizes; |
| 36 | + QStringList results; |
| 37 | + |
| 38 | + QString pathMd5 = ScaleImageThread::pathMd5(originalPath, isMd5Path); |
| 39 | + |
| 40 | + if (m_cachedImages.contains(pathMd5)) { |
| 41 | + const QMap<QString, QString> &map = m_cachedImages[pathMd5]; |
| 42 | + for (const QSize &size : sizes) { |
| 43 | + QString strSize = ScaleImageThread::sizeToString(size); |
| 44 | + if (map.contains(strSize)) { |
| 45 | + results.append(map[strSize]); |
| 46 | + } else { |
| 47 | + noCachedSizes.append(size); |
| 48 | + } |
| 49 | + } |
| 50 | + } else { |
| 51 | + noCachedSizes = sizes; |
| 52 | + } |
| 53 | + |
| 54 | + if (!noCachedSizes.isEmpty()) { |
| 55 | + qDebug() << "need handle image:" << originalPath << " sizes:" << noCachedSizes; |
| 56 | + Q_EMIT needHandleImage(originalPath, noCachedSizes, isMd5Path); |
| 57 | + } |
| 58 | + |
| 59 | + return results; |
| 60 | +} |
| 61 | + |
| 62 | +void CachedWallpaper::cacheImage(const QString &originalPathMd5, const QString &size, const QString &processedPath) |
| 63 | +{ |
| 64 | + qDebug() << "cache Image:" << processedPath; |
| 65 | + m_cachedImages[originalPathMd5].insert(size, processedPath); |
| 66 | +} |
| 67 | + |
| 68 | +QString CachedWallpaper::getBlurImagePath(const QString &originalPath) |
| 69 | +{ |
| 70 | + QString pathMd5 = QCryptographicHash::hash(originalPath.toUtf8(), QCryptographicHash::Md5).toHex(); |
| 71 | + |
| 72 | + QMutexLocker locker(&m_blurGenerateMutex); |
| 73 | + |
| 74 | + auto it = m_blurImageCache.constFind(pathMd5); |
| 75 | + if (it != m_blurImageCache.constEnd() && QFile::exists(it.value())) { |
| 76 | + return it.value(); |
| 77 | + } |
| 78 | + |
| 79 | + QString blurPath = generateBlurImage(pathMd5, originalPath); |
| 80 | + if (!blurPath.isEmpty()) { |
| 81 | + cacheBlurImage(pathMd5, blurPath); |
| 82 | + return blurPath; |
| 83 | + } |
| 84 | + |
| 85 | + return QString(); |
| 86 | +} |
| 87 | + |
| 88 | +void CachedWallpaper::cacheBlurImage(const QString &originalPathMd5, const QString &blurPath) |
| 89 | +{ |
| 90 | + qDebug() << "cache blur image:" << blurPath; |
| 91 | + m_blurImageCache[originalPathMd5] = blurPath; |
| 92 | +} |
| 93 | + |
| 94 | +QStringList CachedWallpaper::getProcessedImageWithBlur(const QString &originalPath, const QList<QSize> &sizes, bool needBlur) |
| 95 | +{ |
| 96 | + QString sourcePath = originalPath; |
| 97 | + |
| 98 | + if (needBlur) { |
| 99 | + QString blurPath = getBlurImagePath(originalPath); |
| 100 | + if (!blurPath.isEmpty()) { |
| 101 | + sourcePath = blurPath; |
| 102 | + } |
| 103 | + } |
| 104 | + |
| 105 | + QStringList results = getCachedImagePaths(sourcePath, sizes); |
| 106 | + if (!results.isEmpty()) { |
| 107 | + return results; |
| 108 | + } |
| 109 | + |
| 110 | + return QStringList() << sourcePath; |
| 111 | +} |
| 112 | + |
| 113 | +QString CachedWallpaper::blurOutputPath(const QString &pathMd5, const QString &originalPath) |
| 114 | +{ |
| 115 | + QFileInfo fi(originalPath); |
| 116 | + QString suffix = fi.suffix(); |
| 117 | + return QString("%1/%2%3").arg(kBlurCacheDir, pathMd5, suffix.isEmpty() ? "" : "." + suffix); |
| 118 | +} |
| 119 | + |
| 120 | +QString CachedWallpaper::generateBlurImage(const QString &pathMd5, const QString &originalPath) |
| 121 | +{ |
| 122 | + QFileInfo originalFileInfo(originalPath); |
| 123 | + QString outputFile = blurOutputPath(pathMd5, originalPath); |
| 124 | + |
| 125 | + // Return cached file if up-to-date |
| 126 | + QFileInfo outputFileInfo(outputFile); |
| 127 | + if (outputFileInfo.exists() |
| 128 | + && outputFileInfo.lastModified() >= originalFileInfo.lastModified() |
| 129 | + && outputFileInfo.size() > 0) { |
| 130 | + qDebug() << "Blur image already exists and up-to-date:" << outputFile; |
| 131 | + return outputFile; |
| 132 | + } |
| 133 | + |
| 134 | + qDebug() << "Generating blur image:" << originalPath << "=>" << outputFile; |
| 135 | + |
| 136 | + QImage blurredImage = ImageEffectProcessor::applyPixmixEffect(originalPath); |
| 137 | + if (blurredImage.isNull()) { |
| 138 | + qWarning() << "Failed to generate blur image:" << originalPath; |
| 139 | + return QString(); |
| 140 | + } |
| 141 | + |
| 142 | + if (!blurredImage.save(outputFile, QImageReader::imageFormat(originalPath), 100)) { |
| 143 | + qWarning() << "Failed to save blur image:" << outputFile; |
| 144 | + return QString(); |
| 145 | + } |
| 146 | + |
| 147 | + // Match output mtime to input for cache freshness check |
| 148 | + QFile timeFile(outputFile); |
| 149 | + if (timeFile.open(QIODevice::ReadWrite)) { |
| 150 | + timeFile.setFileTime(originalFileInfo.lastModified(), QFileDevice::FileModificationTime); |
| 151 | + timeFile.close(); |
| 152 | + } |
| 153 | + |
| 154 | + qDebug() << "Successfully generated blur image:" << outputFile; |
| 155 | + return outputFile; |
| 156 | +} |
| 157 | + |
| 158 | +QStringList CachedWallpaper::getWallpaperListForScreen(const QString &originalPath, const QList<QSize> &sizes, bool needBlur) |
| 159 | +{ |
| 160 | + if (needBlur) { |
| 161 | + QString blurPath = getBlurImagePath(originalPath); |
| 162 | + if (!blurPath.isEmpty()) { |
| 163 | + QStringList results = getCachedImagePaths(blurPath, sizes); |
| 164 | + if (!results.isEmpty()) { |
| 165 | + return results; |
| 166 | + } |
| 167 | + return QStringList() << blurPath; |
| 168 | + } |
| 169 | + qWarning() << "Blur processing failed for:" << originalPath << ", fallback to original"; |
| 170 | + } |
| 171 | + |
| 172 | + QStringList results = getCachedImagePaths(originalPath, sizes); |
| 173 | + if (!results.isEmpty()) { |
| 174 | + return results; |
| 175 | + } |
| 176 | + |
| 177 | + return QStringList() << originalPath; |
| 178 | +} |
| 179 | + |
| 180 | +bool CachedWallpaper::deleteBlurImage(const QString &originalPath) |
| 181 | +{ |
| 182 | + QString pathMd5 = QCryptographicHash::hash(originalPath.toUtf8(), QCryptographicHash::Md5).toHex(); |
| 183 | + |
| 184 | + auto it = m_blurImageCache.find(pathMd5); |
| 185 | + if (it != m_blurImageCache.end()) { |
| 186 | + QString blurPath = it.value(); |
| 187 | + m_blurImageCache.erase(it); |
| 188 | + if (QFile::remove(blurPath)) { |
| 189 | + qDebug() << "Deleted blur image:" << blurPath; |
| 190 | + return true; |
| 191 | + } |
| 192 | + qWarning() << "Failed to delete blur image file:" << blurPath; |
| 193 | + return false; |
| 194 | + } |
| 195 | + |
| 196 | + // Try to delete on-disk file even if not in memory cache |
| 197 | + QString outputFile = blurOutputPath(pathMd5, originalPath); |
| 198 | + if (QFile::remove(outputFile)) { |
| 199 | + qDebug() << "Deleted blur image file:" << outputFile; |
| 200 | + } |
| 201 | + return true; |
| 202 | +} |
| 203 | + |
| 204 | +void CachedWallpaper::clearBlurCache() |
| 205 | +{ |
| 206 | + m_blurImageCache.clear(); |
| 207 | + |
| 208 | + QDir cacheDir(kBlurCacheDir); |
| 209 | + if (!cacheDir.exists()) { |
| 210 | + return; |
| 211 | + } |
| 212 | + |
| 213 | + QFileInfoList files = cacheDir.entryInfoList(QDir::Files | QDir::NoDotAndDotDot); |
| 214 | + int deletedCount = 0; |
| 215 | + for (const QFileInfo &fileInfo : files) { |
| 216 | + if (QFile::remove(fileInfo.absoluteFilePath())) { |
| 217 | + deletedCount++; |
| 218 | + } else { |
| 219 | + qWarning() << "Failed to delete blur cache file:" << fileInfo.absoluteFilePath(); |
| 220 | + } |
| 221 | + } |
| 222 | + qDebug() << "Cleared blur cache, deleted" << deletedCount << "files"; |
| 223 | +} |
| 224 | + |
| 225 | +bool CachedWallpaper::deleteEffectImage(const QString &originalPath, const QString &effect) |
| 226 | +{ |
| 227 | + if (effect.isEmpty() || effect == "pixmix") { |
| 228 | + return deleteBlurImage(originalPath); |
| 229 | + } |
| 230 | + qWarning() << "Unsupported effect for deletion:" << effect; |
| 231 | + return false; |
| 232 | +} |
| 233 | + |
| 234 | +void CachedWallpaper::clearEffectCache(const QString &effect) |
| 235 | +{ |
| 236 | + if (effect == "all") { |
| 237 | + clearBlurCache(); |
| 238 | + return; |
| 239 | + } |
| 240 | + |
| 241 | + QString effectName = effect.isEmpty() ? "pixmix" : effect; |
| 242 | + if (effectName == "pixmix") { |
| 243 | + clearBlurCache(); |
| 244 | + } else { |
| 245 | + qWarning() << "Unsupported effect for cache clearing:" << effectName; |
| 246 | + } |
| 247 | +} |
0 commit comments