#include "wallpaperimage.hpp" #include #include #include #include #include #include #include namespace ZShell::internal { WallpaperImage::WallpaperImage(QQuickItem* parent) : QQuickItem(parent) { setFlag(ItemHasContents, true); connect( &m_imageWatcher, &QFutureWatcher::finished, this, &WallpaperImage::handleImageLoaded); } WallpaperImage::~WallpaperImage() { if (m_texture) delete m_texture; } void WallpaperImage::setStatus(const Status s) { if (m_status == s) return; m_status = s; emit statusChanged(); } void WallpaperImage::setSource(const QUrl& source) { if (m_source == source) return; m_source = source; emit sourceChanged(); setStatus(Loading); loadImage(); } void WallpaperImage::setScreenResolution(const QSize& screenResolution) { if (m_screenResolution == screenResolution) return; m_screenResolution = screenResolution; emit screenResolutionChanged(); loadImage(); } void WallpaperImage::setZoom(qreal zoom) { if (qFuzzyCompare(m_zoom, zoom)) return; m_zoom = zoom; emit zoomChanged(); update(); } void WallpaperImage::setCropX(qreal x) { if (qFuzzyCompare(m_cropX, x)) return; m_cropX = x; emit cropXChanged(); update(); } void WallpaperImage::setCropY(qreal y) { if (qFuzzyCompare(m_cropY, y)) return; m_cropY = y; emit cropYChanged(); update(); } void WallpaperImage::setCropWidth(qreal w) { if (w <= 0.0) w = 1.0; if (qFuzzyCompare(m_cropWidth, w)) return; m_cropWidth = w; emit cropWidthChanged(); update(); } void WallpaperImage::setCropHeight(qreal h) { if (h <= 0.0) h = 1.0; if (qFuzzyCompare(m_cropHeight, h)) return; m_cropHeight = h; emit cropHeightChanged(); update(); } QString WallpaperImage::getCacheFilePath() const { if (m_source.isEmpty() || m_screenResolution.isEmpty()) return QString(); QString cachePath = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + "/zshell/imagecache"; QDir().mkpath(cachePath); QString id = m_source.toString() + "_" + QString::number(m_screenResolution.width()) + "x" + QString::number(m_screenResolution.height()); QByteArray hash = QCryptographicHash::hash(id.toUtf8(), QCryptographicHash::Md5).toHex(); return cachePath + "/" + hash + ".png"; } void WallpaperImage::loadImage() { if (m_source.isEmpty()) { setStatus(Null); return; } QString cacheFile = getCacheFilePath(); QString sourceFile = m_source.isLocalFile() ? m_source.toLocalFile() : m_source.toString(); if (sourceFile.startsWith("qrc:/")) { sourceFile = sourceFile.mid(3); } QSize targetRes = m_screenResolution; QFuture future = QtConcurrent::run([sourceFile, cacheFile, targetRes]() -> QImage { if (!targetRes.isEmpty() && !cacheFile.isEmpty() && QFileInfo::exists(cacheFile)) { QImage cached(cacheFile); if (!cached.isNull()) return cached; } QImage original(sourceFile); if (original.isNull()) return QImage(); if (targetRes.isEmpty()) { return original; } if (original.width() > targetRes.width() || original.height() > targetRes.height()) { QImage scaled = original.scaled( targetRes, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation); if (!cacheFile.isEmpty()) scaled.save(cacheFile, "PNG"); return scaled; } if (!cacheFile.isEmpty()) original.save(cacheFile, "PNG"); return original; }); m_imageWatcher.setFuture(future); } void WallpaperImage::handleImageLoaded() { m_image = m_imageWatcher.result(); setStatus(m_image.isNull() ? Error : Ready); m_textureDirty = true; update(); } QSGNode* WallpaperImage::updatePaintNode( QSGNode* oldNode, UpdatePaintNodeData*) { auto* node = static_cast(oldNode); if (m_image.isNull()) { delete node; return nullptr; } if (!node) { node = window()->createImageNode(); } if (m_textureDirty) { if (m_texture) delete m_texture; m_texture = window()->createTextureFromImage( m_image, QQuickWindow::TextureHasAlphaChannel); m_textureDirty = false; } if (m_texture) { node->setTexture(m_texture); node->setRect(boundingRect()); node->setFiltering(QSGTexture::Linear); qreal cW = m_cropWidth / m_zoom; qreal cH = m_cropHeight / m_zoom; QRectF reqRect( m_cropX * m_texture->textureSize().width(), m_cropY * m_texture->textureSize().height(), cW * m_texture->textureSize().width(), cH * m_texture->textureSize().height()); QRectF bounds = boundingRect(); if (bounds.isEmpty() || reqRect.isEmpty()) return node; qreal targetRatio = bounds.width() / bounds.height(); qreal reqRatio = reqRect.width() / reqRect.height(); QRectF sourceRect = reqRect; if (reqRatio > targetRatio) { qreal newWidth = reqRect.height() * targetRatio; qreal xOffset = (reqRect.width() - newWidth) / 2.0; sourceRect.setX(reqRect.x() + xOffset); sourceRect.setWidth(newWidth); } else if (reqRatio < targetRatio) { qreal newHeight = reqRect.width() / targetRatio; qreal yOffset = (reqRect.height() - newHeight) / 2.0; sourceRect.setY(reqRect.y() + yOffset); sourceRect.setHeight(newHeight); } QRectF normalizedActual( sourceRect.x() / m_texture->textureSize().width(), sourceRect.y() / m_texture->textureSize().height(), sourceRect.width() / m_texture->textureSize().width(), sourceRect.height() / m_texture->textureSize().height()); bool changed = false; auto updateIfChanged = [&](qreal& dst, qreal value) { if (!qFuzzyCompare(dst, value)) { dst = value; changed = true; } }; updateIfChanged(m_actualCropX, normalizedActual.x()); updateIfChanged(m_actualCropY, normalizedActual.y()); updateIfChanged(m_actualCropWidth, normalizedActual.width()); updateIfChanged(m_actualCropHeight, normalizedActual.height()); if (changed) emit actualCropChanged(); node->setSourceRect(sourceRect); } return node; } } // namespace ZShell::internal