Merge branch 'main' into zshell-img-tools
This commit is contained in:
@@ -8,6 +8,7 @@ qml_module(ZShell-internal
|
||||
circularbuffer.hpp circularbuffer.cpp
|
||||
sparklineitem.hpp sparklineitem.cpp
|
||||
arcgauge.hpp arcgauge.cpp
|
||||
wallpaperimage.hpp wallpaperimage.cpp
|
||||
LIBRARIES
|
||||
Qt::Gui
|
||||
Qt::Quick
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#include <qpainter.h>
|
||||
#include <qpen.h>
|
||||
|
||||
namespace caelestia::internal {
|
||||
namespace ZShell::internal {
|
||||
|
||||
ArcGauge::ArcGauge(QQuickItem* parent)
|
||||
: QQuickPaintedItem(parent) {
|
||||
@@ -116,4 +116,4 @@ void ArcGauge::setLineWidth(qreal width) {
|
||||
update();
|
||||
}
|
||||
|
||||
} // namespace caelestia::internal
|
||||
} // namespace ZShell::internal
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
#include <qqmlintegration.h>
|
||||
#include <qquickpainteditem.h>
|
||||
|
||||
namespace caelestia::internal {
|
||||
namespace ZShell::internal {
|
||||
|
||||
class ArcGauge : public QQuickPaintedItem {
|
||||
Q_OBJECT
|
||||
@@ -58,4 +58,4 @@ qreal m_sweepAngle = 1.5 * M_PI;
|
||||
qreal m_lineWidth = 10.0;
|
||||
};
|
||||
|
||||
} // namespace caelestia::internal
|
||||
} // namespace ZShell::internal
|
||||
|
||||
@@ -0,0 +1,199 @@
|
||||
#include "wallpaperimage.hpp"
|
||||
|
||||
#include <QStandardPaths>
|
||||
#include <QCryptographicHash>
|
||||
#include <QDir>
|
||||
#include <QFileInfo>
|
||||
#include <QtConcurrent>
|
||||
#include <QSGImageNode>
|
||||
#include <QQuickWindow>
|
||||
|
||||
namespace ZShell::internal {
|
||||
|
||||
WallpaperImage::WallpaperImage(QQuickItem *parent)
|
||||
: QQuickItem(parent)
|
||||
{
|
||||
setFlag(ItemHasContents, true);
|
||||
connect(&m_imageWatcher, &QFutureWatcher<QImage>::finished, this, &WallpaperImage::handleImageLoaded);
|
||||
}
|
||||
|
||||
WallpaperImage::~WallpaperImage() {
|
||||
if (m_texture) delete m_texture;
|
||||
}
|
||||
|
||||
void WallpaperImage::setSource(const QUrl &source) {
|
||||
if (m_source == source) return;
|
||||
m_source = source;
|
||||
emit sourceChanged();
|
||||
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);
|
||||
|
||||
// Hash the source URL + resolution
|
||||
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()) return;
|
||||
|
||||
QString cacheFile = getCacheFilePath();
|
||||
QString sourceFile = m_source.isLocalFile() ? m_source.toLocalFile() : m_source.toString();
|
||||
|
||||
// Qt resource path correction if passed as a standard URL string
|
||||
if (sourceFile.startsWith("qrc:/")) {
|
||||
sourceFile = sourceFile.mid(3); // Converts "qrc:/" to ":/"
|
||||
}
|
||||
|
||||
QSize targetRes = m_screenResolution;
|
||||
|
||||
// Run off the main thread to avoid blocking the UI
|
||||
QFuture<QImage> 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()) {
|
||||
// Screen resolution not set yet by QML, return the unscaled original for now to prevent a black screen
|
||||
return original;
|
||||
}
|
||||
|
||||
// Check if original is strictly larger than screen resolution
|
||||
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;
|
||||
}
|
||||
|
||||
// Otherwise just cache and return the original
|
||||
if (!cacheFile.isEmpty()) original.save(cacheFile, "PNG");
|
||||
return original;
|
||||
});
|
||||
|
||||
m_imageWatcher.setFuture(future);
|
||||
}
|
||||
|
||||
void WallpaperImage::handleImageLoaded() {
|
||||
m_image = m_imageWatcher.result();
|
||||
m_textureDirty = true;
|
||||
update(); // Request redraw
|
||||
}
|
||||
|
||||
QSGNode *WallpaperImage::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *) {
|
||||
auto *node = static_cast<QSGImageNode *>(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;
|
||||
|
||||
// Force 'PreserveAspectCrop' behavior on the requested region
|
||||
if (reqRatio > targetRatio) {
|
||||
// Requested region is too wide, center-crop the sides
|
||||
qreal newWidth = reqRect.height() * targetRatio;
|
||||
qreal xOffset = (reqRect.width() - newWidth) / 2.0;
|
||||
sourceRect.setX(reqRect.x() + xOffset);
|
||||
sourceRect.setWidth(newWidth);
|
||||
} else if (reqRatio < targetRatio) {
|
||||
// Requested region is too tall, center-crop the top/bottom
|
||||
qreal newHeight = reqRect.width() / targetRatio;
|
||||
qreal yOffset = (reqRect.height() - newHeight) / 2.0;
|
||||
sourceRect.setY(reqRect.y() + yOffset);
|
||||
sourceRect.setHeight(newHeight);
|
||||
}
|
||||
|
||||
node->setSourceRect(sourceRect);
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
} // namespace ZShell::internal
|
||||
@@ -0,0 +1,95 @@
|
||||
#pragma once
|
||||
|
||||
#include <QQuickItem>
|
||||
#include <QImage>
|
||||
#include <QUrl>
|
||||
#include <QSGTexture>
|
||||
#include <QFutureWatcher>
|
||||
#include <QtQml/qqml.h>
|
||||
|
||||
namespace ZShell::internal {
|
||||
|
||||
class WallpaperImage : public QQuickItem {
|
||||
Q_OBJECT
|
||||
QML_NAMED_ELEMENT(WallpaperImage)
|
||||
Q_PROPERTY(QUrl source READ source WRITE setSource NOTIFY sourceChanged)
|
||||
Q_PROPERTY(QSize screenResolution READ screenResolution WRITE setScreenResolution NOTIFY screenResolutionChanged)
|
||||
Q_PROPERTY(qreal zoom READ zoom WRITE setZoom NOTIFY zoomChanged)
|
||||
|
||||
Q_PROPERTY(qreal cropX READ cropX WRITE setCropX NOTIFY cropXChanged)
|
||||
Q_PROPERTY(qreal cropY READ cropY WRITE setCropY NOTIFY cropYChanged)
|
||||
Q_PROPERTY(qreal cropWidth READ cropWidth WRITE setCropWidth NOTIFY cropWidthChanged)
|
||||
Q_PROPERTY(qreal cropHeight READ cropHeight WRITE setCropHeight NOTIFY cropHeightChanged)
|
||||
|
||||
public:
|
||||
explicit WallpaperImage(QQuickItem *parent = nullptr);
|
||||
~WallpaperImage() override;
|
||||
|
||||
QUrl source() const {
|
||||
return m_source;
|
||||
}
|
||||
void setSource(const QUrl &source);
|
||||
|
||||
QSize screenResolution() const {
|
||||
return m_screenResolution;
|
||||
}
|
||||
void setScreenResolution(const QSize &screenResolution);
|
||||
|
||||
qreal zoom() const {
|
||||
return m_zoom;
|
||||
}
|
||||
void setZoom(qreal zoom);
|
||||
|
||||
qreal cropX() const {
|
||||
return m_cropX;
|
||||
}
|
||||
void setCropX(qreal x);
|
||||
|
||||
qreal cropY() const {
|
||||
return m_cropY;
|
||||
}
|
||||
void setCropY(qreal y);
|
||||
|
||||
qreal cropWidth() const {
|
||||
return m_cropWidth;
|
||||
}
|
||||
void setCropWidth(qreal w);
|
||||
|
||||
qreal cropHeight() const {
|
||||
return m_cropHeight;
|
||||
}
|
||||
void setCropHeight(qreal h);
|
||||
|
||||
protected:
|
||||
QSGNode *updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *updatePaintNodeData) override;
|
||||
|
||||
signals:
|
||||
void sourceChanged();
|
||||
void screenResolutionChanged();
|
||||
void zoomChanged();
|
||||
void cropXChanged();
|
||||
void cropYChanged();
|
||||
void cropWidthChanged();
|
||||
void cropHeightChanged();
|
||||
|
||||
private:
|
||||
void loadImage();
|
||||
void handleImageLoaded();
|
||||
QString getCacheFilePath() const;
|
||||
|
||||
QUrl m_source;
|
||||
QSize m_screenResolution;
|
||||
qreal m_zoom = 1.0;
|
||||
|
||||
qreal m_cropX = 0.0;
|
||||
qreal m_cropY = 0.0;
|
||||
qreal m_cropWidth = 1.0;
|
||||
qreal m_cropHeight = 1.0;
|
||||
|
||||
QImage m_image;
|
||||
QSGTexture *m_texture = nullptr;
|
||||
bool m_textureDirty = false;
|
||||
QFutureWatcher<QImage> m_imageWatcher;
|
||||
};
|
||||
|
||||
} // namespace ZShell::internal
|
||||
+141
-155
@@ -1,17 +1,22 @@
|
||||
#include "writefile.hpp"
|
||||
|
||||
#include <QtConcurrent/qtconcurrentrun.h>
|
||||
#include <QtCore/QCryptographicHash>
|
||||
#include <QtCore/QSaveFile>
|
||||
#include <QtGui/QImageReader>
|
||||
#include <QtQuick/qquickimageprovider.h>
|
||||
#include <QtQuick/qquickitemgrabresult.h>
|
||||
#include <QtQuick/qquickwindow.h>
|
||||
|
||||
#include <qdir.h>
|
||||
#include <qfile.h>
|
||||
#include <qfileinfo.h>
|
||||
#include <qfuturewatcher.h>
|
||||
#include <qimage.h>
|
||||
#include <qjsengine.h>
|
||||
#include <qqmlengine.h>
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QFileInfo>
|
||||
#include <QFutureWatcher>
|
||||
#include <QImage>
|
||||
#include <QJSValue>
|
||||
#include <QQmlEngine>
|
||||
#include <QSize>
|
||||
#include <QVariant>
|
||||
|
||||
namespace ZShell {
|
||||
|
||||
@@ -84,54 +89,51 @@ void ZShellIo::saveItem(
|
||||
&QQuickItemGrabResult::ready,
|
||||
this,
|
||||
[grabResult, scaledRect, path, onSaved, onFailed, this]() {
|
||||
const auto future = QtConcurrent::run([grabResult, scaledRect, path]() {
|
||||
QImage image = grabResult->image();
|
||||
|
||||
QImage image = grabResult->image();
|
||||
|
||||
if (scaledRect.isValid()) {
|
||||
image = image.copy(scaledRect);
|
||||
}
|
||||
|
||||
const auto future = QtConcurrent::run([image, path]() {
|
||||
if (scaledRect.isValid()) {
|
||||
image = image.copy(scaledRect);
|
||||
}
|
||||
|
||||
const QString file = path.toLocalFile();
|
||||
const QString parent = QFileInfo(file).absolutePath();
|
||||
|
||||
return QDir().mkpath(parent) && image.save(file);
|
||||
QDir().mkpath(parent);
|
||||
|
||||
QSaveFile out(file);
|
||||
if (!out.open(QIODevice::WriteOnly)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!image.save(&out, "PNG")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return out.commit();
|
||||
});
|
||||
|
||||
auto* watcher = new QFutureWatcher<bool>(this);
|
||||
auto* engine = qmlEngine(this);
|
||||
|
||||
QObject::connect(
|
||||
watcher,
|
||||
&QFutureWatcher<bool>::finished,
|
||||
this,
|
||||
[=]() {
|
||||
|
||||
QObject::connect(watcher, &QFutureWatcher<bool>::finished, this, [=]() {
|
||||
if (watcher->result()) {
|
||||
|
||||
if (onSaved.isCallable()) {
|
||||
if (onSaved.isCallable() && engine) {
|
||||
onSaved.call({
|
||||
QJSValue(path.toLocalFile()),
|
||||
engine->toScriptValue(QVariant::fromValue(path))
|
||||
engine->toScriptValue(path.toLocalFile()),
|
||||
engine->toScriptValue(path)
|
||||
});
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
qWarning() << "ZShellIo::saveItem: failed to save"
|
||||
<< path;
|
||||
|
||||
if (onFailed.isCallable()) {
|
||||
qWarning() << "ZShellIo::saveItem: failed to save" << path;
|
||||
if (onFailed.isCallable() && engine) {
|
||||
onFailed.call({
|
||||
engine->toScriptValue(QVariant::fromValue(path))
|
||||
engine->toScriptValue(path)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
watcher->deleteLater();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
watcher->setFuture(future);
|
||||
}
|
||||
@@ -139,106 +141,130 @@ void ZShellIo::saveItem(
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// saveImage
|
||||
// cacheImage
|
||||
// ============================================================
|
||||
|
||||
void ZShellIo::saveImage(const QUrl& source, const QUrl& target) {
|
||||
this->saveImage(source, target, QJSValue(), QJSValue());
|
||||
void ZShellIo::cacheImage(const QUrl& source, const QString& cacheDir) {
|
||||
this->cacheImage(source, cacheDir, QJSValue(), QJSValue());
|
||||
}
|
||||
|
||||
void ZShellIo::saveImage(const QUrl& source, const QUrl& target, QJSValue onSaved) {
|
||||
this->saveImage(source, target, onSaved, QJSValue());
|
||||
void ZShellIo::cacheImage(const QUrl& source, const QString& cacheDir, QJSValue onSaved) {
|
||||
this->cacheImage(source, cacheDir, onSaved, QJSValue());
|
||||
}
|
||||
|
||||
void ZShellIo::saveImage(
|
||||
void ZShellIo::cacheImage(
|
||||
const QUrl& source,
|
||||
const QUrl& target,
|
||||
const QString& cacheDir,
|
||||
QJSValue onSaved,
|
||||
QJSValue onFailed
|
||||
) {
|
||||
auto* engine = qmlEngine(this);
|
||||
if (cacheDir.isEmpty()) {
|
||||
qWarning() << "ZShellIo::cacheImage: cacheDir is empty";
|
||||
return;
|
||||
}
|
||||
|
||||
const auto future = QtConcurrent::run([this, source, target]() {
|
||||
return this->saveImageInternal(source, target);
|
||||
QImage image;
|
||||
if (!loadSourceImage(source, image)) {
|
||||
qWarning() << "ZShellIo::cacheImage: failed to load source image" << source;
|
||||
auto* engine = qmlEngine(this);
|
||||
if (onFailed.isCallable() && engine) {
|
||||
onFailed.call({
|
||||
engine->toScriptValue(source),
|
||||
engine->toScriptValue(cacheDir)
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const auto future = QtConcurrent::run([image, cacheDir]() -> QString {
|
||||
if (image.isNull()) {
|
||||
return QString();
|
||||
}
|
||||
|
||||
const QImage normalized = image.convertToFormat(QImage::Format_RGBA8888);
|
||||
|
||||
const QByteArray bytes(
|
||||
reinterpret_cast<const char*>(normalized.constBits()),
|
||||
qsizetype(normalized.sizeInBytes())
|
||||
);
|
||||
|
||||
const QByteArray digest =
|
||||
QCryptographicHash::hash(bytes, QCryptographicHash::Sha256).toHex();
|
||||
|
||||
QDir dir(cacheDir);
|
||||
if (!dir.exists() && !QDir().mkpath(cacheDir)) {
|
||||
return QString();
|
||||
}
|
||||
|
||||
const QString finalPath = dir.filePath(QString::fromLatin1(digest) + ".png");
|
||||
|
||||
if (QFile::exists(finalPath)) {
|
||||
return finalPath;
|
||||
}
|
||||
|
||||
QSaveFile out(finalPath);
|
||||
if (!out.open(QIODevice::WriteOnly)) {
|
||||
return QString();
|
||||
}
|
||||
|
||||
if (!normalized.save(&out, "PNG")) {
|
||||
return QString();
|
||||
}
|
||||
|
||||
if (!out.commit()) {
|
||||
return QString();
|
||||
}
|
||||
|
||||
return finalPath;
|
||||
});
|
||||
|
||||
auto* watcher = new QFutureWatcher<bool>(this);
|
||||
auto* watcher = new QFutureWatcher<QString>(this);
|
||||
auto* engine = qmlEngine(this);
|
||||
|
||||
QObject::connect(
|
||||
watcher,
|
||||
&QFutureWatcher<bool>::finished,
|
||||
this,
|
||||
[=]() {
|
||||
QObject::connect(watcher, &QFutureWatcher<QString>::finished, this, [=]() {
|
||||
const QString finalPath = watcher->result();
|
||||
|
||||
if (watcher->result()) {
|
||||
|
||||
if (onSaved.isCallable()) {
|
||||
if (!finalPath.isEmpty()) {
|
||||
if (onSaved.isCallable() && engine) {
|
||||
onSaved.call({
|
||||
QJSValue(target.toLocalFile()),
|
||||
engine->toScriptValue(QVariant::fromValue(target))
|
||||
engine->toScriptValue(finalPath),
|
||||
engine->toScriptValue(QUrl::fromLocalFile(finalPath))
|
||||
});
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
qWarning() << "ZShellIo::saveImage: failed to save"
|
||||
<< source
|
||||
<< "to"
|
||||
<< target;
|
||||
|
||||
if (onFailed.isCallable()) {
|
||||
qWarning() << "ZShellIo::cacheImage: failed to cache" << source;
|
||||
if (onFailed.isCallable() && engine) {
|
||||
onFailed.call({
|
||||
engine->toScriptValue(QVariant::fromValue(target))
|
||||
engine->toScriptValue(source),
|
||||
engine->toScriptValue(cacheDir)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
watcher->deleteLater();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
watcher->setFuture(future);
|
||||
}
|
||||
|
||||
bool ZShellIo::saveImageInternal(const QUrl& source, const QUrl& target) const {
|
||||
// ============================================================
|
||||
// loadSourceImage
|
||||
// ============================================================
|
||||
|
||||
if (!target.isLocalFile()) {
|
||||
qWarning() << "ZShellIo::saveImage: target"
|
||||
<< target
|
||||
<< "is not a local file";
|
||||
return false;
|
||||
}
|
||||
|
||||
const QString targetFile = target.toLocalFile();
|
||||
|
||||
if (!QDir().mkpath(QFileInfo(targetFile).absolutePath())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// ========================================================
|
||||
// Local file path
|
||||
// ========================================================
|
||||
bool ZShellIo::loadSourceImage(const QUrl& source, QImage& image) const {
|
||||
image = QImage();
|
||||
|
||||
if (source.isLocalFile()) {
|
||||
|
||||
QFile::remove(targetFile);
|
||||
|
||||
return QFile::copy(
|
||||
source.toLocalFile(),
|
||||
targetFile
|
||||
);
|
||||
QImageReader reader(source.toLocalFile());
|
||||
reader.setAutoTransform(true);
|
||||
image = reader.read();
|
||||
return !image.isNull();
|
||||
}
|
||||
|
||||
// ========================================================
|
||||
// image:// provider path
|
||||
// ========================================================
|
||||
|
||||
if (source.scheme() == "image") {
|
||||
|
||||
auto* engine = qmlEngine(const_cast<ZShellIo*>(this));
|
||||
|
||||
if (!engine) {
|
||||
qWarning() << "ZShellIo::saveImage: no QQmlEngine";
|
||||
qWarning() << "ZShellIo::loadSourceImage: no QQmlEngine";
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -246,70 +272,44 @@ bool ZShellIo::saveImageInternal(const QUrl& source, const QUrl& target) const {
|
||||
|
||||
const QString imageId =
|
||||
source.path().startsWith('/')
|
||||
? source.path().mid(1)
|
||||
: source.path();
|
||||
|
||||
auto* providerBase =
|
||||
engine->imageProvider(providerId);
|
||||
? source.path().mid(1)
|
||||
: source.path();
|
||||
|
||||
auto* providerBase = engine->imageProvider(providerId);
|
||||
if (!providerBase) {
|
||||
qWarning() << "ZShellIo::saveImage: provider not found"
|
||||
qWarning() << "ZShellIo::loadSourceImage: provider not found"
|
||||
<< providerId;
|
||||
return false;
|
||||
}
|
||||
|
||||
auto* provider =
|
||||
dynamic_cast<QQuickImageProvider*>(providerBase);
|
||||
|
||||
auto* provider = dynamic_cast<QQuickImageProvider*>(providerBase);
|
||||
if (!provider) {
|
||||
qWarning() << "ZShellIo::saveImage: provider is not a QQuickImageProvider"
|
||||
<< providerId;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!provider) {
|
||||
qWarning() << "ZShellIo::saveImage: provider not found"
|
||||
qWarning() << "ZShellIo::loadSourceImage: provider is not a QQuickImageProvider"
|
||||
<< providerId;
|
||||
return false;
|
||||
}
|
||||
|
||||
QSize size;
|
||||
QImage image;
|
||||
|
||||
switch (provider->imageType()) {
|
||||
|
||||
case QQuickImageProvider::Image:
|
||||
image = provider->requestImage(
|
||||
imageId,
|
||||
&size,
|
||||
QSize()
|
||||
);
|
||||
image = provider->requestImage(imageId, &size, QSize());
|
||||
break;
|
||||
|
||||
case QQuickImageProvider::Pixmap:
|
||||
image = provider->requestPixmap(
|
||||
imageId,
|
||||
&size,
|
||||
QSize()
|
||||
).toImage();
|
||||
image = provider->requestPixmap(imageId, &size, QSize()).toImage();
|
||||
break;
|
||||
|
||||
default:
|
||||
qWarning() << "ZShellIo::saveImage: unsupported provider type";
|
||||
qWarning() << "ZShellIo::loadSourceImage: unsupported provider type"
|
||||
<< providerId;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (image.isNull()) {
|
||||
qWarning() << "ZShellIo::saveImage: provider returned null image";
|
||||
return false;
|
||||
}
|
||||
|
||||
return image.save(targetFile);
|
||||
return !image.isNull();
|
||||
}
|
||||
|
||||
qWarning() << "ZShellIo::saveImage: unsupported source"
|
||||
<< source;
|
||||
|
||||
qWarning() << "ZShellIo::loadSourceImage: unsupported source" << source;
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -318,18 +318,12 @@ bool ZShellIo::saveImageInternal(const QUrl& source, const QUrl& target) const {
|
||||
// ============================================================
|
||||
|
||||
bool ZShellIo::copyFile(const QUrl& source, const QUrl& target, bool overwrite) const {
|
||||
|
||||
if (!source.isLocalFile()) {
|
||||
qWarning() << "ZShellIo::copyFile: source"
|
||||
<< source
|
||||
<< "is not a local file";
|
||||
qWarning() << "ZShellIo::copyFile: source" << source << "is not a local file";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!target.isLocalFile()) {
|
||||
qWarning() << "ZShellIo::copyFile: target"
|
||||
<< target
|
||||
<< "is not a local file";
|
||||
qWarning() << "ZShellIo::copyFile: target" << target << "is not a local file";
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -337,18 +331,12 @@ bool ZShellIo::copyFile(const QUrl& source, const QUrl& target, bool overwrite)
|
||||
QFile::remove(target.toLocalFile());
|
||||
}
|
||||
|
||||
return QFile::copy(
|
||||
source.toLocalFile(),
|
||||
target.toLocalFile()
|
||||
);
|
||||
return QFile::copy(source.toLocalFile(), target.toLocalFile());
|
||||
}
|
||||
|
||||
bool ZShellIo::deleteFile(const QUrl& path) const {
|
||||
|
||||
if (!path.isLocalFile()) {
|
||||
qWarning() << "ZShellIo::deleteFile: path"
|
||||
<< path
|
||||
<< "is not a local file";
|
||||
qWarning() << "ZShellIo::deleteFile: path" << path << "is not a local file";
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -356,10 +344,8 @@ bool ZShellIo::deleteFile(const QUrl& path) const {
|
||||
}
|
||||
|
||||
QString ZShellIo::toLocalFile(const QUrl& url) const {
|
||||
|
||||
if (!url.isLocalFile()) {
|
||||
qWarning() << "ZShellIo::toLocalFile: given url is not a local file"
|
||||
<< url;
|
||||
qWarning() << "ZShellIo::toLocalFile: given url is not a local file" << url;
|
||||
return QString();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include <QtQuick/qquickitem.h>
|
||||
#include <qjsvalue.h>
|
||||
#include <qobject.h>
|
||||
#include <QImage>
|
||||
#include <QJSValue>
|
||||
#include <QObject>
|
||||
#include <qqmlintegration.h>
|
||||
#include <qurl.h>
|
||||
#include <QUrl>
|
||||
|
||||
namespace ZShell {
|
||||
|
||||
@@ -23,9 +24,9 @@ Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, QJSValue onSaved
|
||||
Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, const QRect& rect, QJSValue onSaved);
|
||||
Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, const QRect& rect, QJSValue onSaved, QJSValue onFailed);
|
||||
|
||||
Q_INVOKABLE void saveImage(const QUrl& source, const QUrl& target);
|
||||
Q_INVOKABLE void saveImage(const QUrl& source, const QUrl& target, QJSValue onSaved);
|
||||
Q_INVOKABLE void saveImage(const QUrl& source, const QUrl& target, QJSValue onSaved, QJSValue onFailed);
|
||||
Q_INVOKABLE void cacheImage(const QUrl& source, const QString& cacheDir);
|
||||
Q_INVOKABLE void cacheImage(const QUrl& source, const QString& cacheDir, QJSValue onSaved);
|
||||
Q_INVOKABLE void cacheImage(const QUrl& source, const QString& cacheDir, QJSValue onSaved, QJSValue onFailed);
|
||||
// clang-format on
|
||||
|
||||
Q_INVOKABLE bool copyFile(const QUrl& source, const QUrl& target, bool overwrite = true) const;
|
||||
@@ -33,7 +34,7 @@ Q_INVOKABLE bool deleteFile(const QUrl& path) const;
|
||||
Q_INVOKABLE QString toLocalFile(const QUrl& url) const;
|
||||
|
||||
private:
|
||||
bool saveImageInternal(const QUrl& source, const QUrl& target) const;
|
||||
bool loadSourceImage(const QUrl& source, QImage& image) const;
|
||||
};
|
||||
|
||||
} // namespace ZShell
|
||||
|
||||
Reference in New Issue
Block a user