QmlDesigner: Change ImageCache to use new qml-renderer puppet

ImageCacheCollector now uses new qml-renderer puppet to generate
preview images.

Removed -W and -H args from qml-renderer and added --minW, --minH,
--maxW and --maxH args to specify acceptable range for final scaled
size of the image. If natural size of the rendered image fits within
this range, then the image is not scaled.

Added --libIcon arg to qml-renderer to suppress background on 3D nodes.

Also changed item library icons to respect aspect ratio.

Fixes: QDS-14692
Change-Id: I88f3461626f69191770c0c5e7d79c14d7e82e609
Reviewed-by: Mahmoud Badri <mahmoud.badri@qt.io>
Reviewed-by: Marco Bubke <marco.bubke@qt.io>
This commit is contained in:
Miikka Heikkinen
2025-02-13 17:07:09 +02:00
parent 538c199cf0
commit fcf8d2bde4
22 changed files with 193 additions and 402 deletions

View File

@@ -38,6 +38,7 @@ Item {
width: ItemLibraryBackend.itemLibraryIconWidth // to be set in Qml context width: ItemLibraryBackend.itemLibraryIconWidth // to be set in Qml context
height: ItemLibraryBackend.itemLibraryIconHeight // to be set in Qml context height: ItemLibraryBackend.itemLibraryIconHeight // to be set in Qml context
source: itemLibraryIconPath // to be set by model source: itemLibraryIconPath // to be set by model
fillMode: Image.PreserveAspectFit
// Icons generated for components can change if the component is edited, // Icons generated for components can change if the component is edited,
// so don't cache them locally at Image level. // so don't cache them locally at Image level.

View File

@@ -36,8 +36,6 @@ public:
const QUrl &resourceUrl, const QUrl &resourceUrl,
const QHash<QString, QVariantMap> &edit3dToolStates, const QHash<QString, QVariantMap> &edit3dToolStates,
const QString &language, const QString &language,
QSize captureImageMinimumSize,
QSize captureImageMaximumSize,
qint32 stateInstanceId) qint32 stateInstanceId)
: instances(instanceContainer) : instances(instanceContainer)
, reparentInstances(reparentContainer) , reparentInstances(reparentContainer)
@@ -51,8 +49,6 @@ public:
, resourceUrl(resourceUrl) , resourceUrl(resourceUrl)
, edit3dToolStates(edit3dToolStates) , edit3dToolStates(edit3dToolStates)
, language(language) , language(language)
, captureImageMinimumSize(captureImageMinimumSize)
, captureImageMaximumSize(captureImageMaximumSize)
, stateInstanceId{stateInstanceId} , stateInstanceId{stateInstanceId}
{} {}
@@ -71,8 +67,6 @@ public:
out << command.edit3dToolStates; out << command.edit3dToolStates;
out << command.language; out << command.language;
out << command.stateInstanceId; out << command.stateInstanceId;
out << command.captureImageMinimumSize;
out << command.captureImageMaximumSize;
return out; return out;
} }
@@ -92,8 +86,6 @@ public:
in >> command.edit3dToolStates; in >> command.edit3dToolStates;
in >> command.language; in >> command.language;
in >> command.stateInstanceId; in >> command.stateInstanceId;
in >> command.captureImageMinimumSize;
in >> command.captureImageMaximumSize;
return in; return in;
} }
@@ -111,8 +103,6 @@ public:
QUrl resourceUrl; QUrl resourceUrl;
QHash<QString, QVariantMap> edit3dToolStates; QHash<QString, QVariantMap> edit3dToolStates;
QString language; QString language;
QSize captureImageMinimumSize;
QSize captureImageMaximumSize;
qint32 stateInstanceId = 0; qint32 stateInstanceId = 0;
}; };

View File

@@ -517,8 +517,6 @@ extend_qtc_plugin(QmlDesigner
SOURCES SOURCES
imagecachecollector.cpp imagecachecollector.cpp
imagecachecollector.h imagecachecollector.h
imagecacheconnectionmanager.cpp
imagecacheconnectionmanager.h
imagecachefontcollector.cpp imagecachefontcollector.cpp
imagecachefontcollector.h imagecachefontcollector.h
meshimagecachecollector.cpp meshimagecachecollector.cpp

View File

@@ -13,7 +13,6 @@
#include <imagecache/imagecachestorage.h> #include <imagecache/imagecachestorage.h>
#include <imagecache/timestampprovider.h> #include <imagecache/timestampprovider.h>
#include <imagecachecollectors/imagecachecollector.h> #include <imagecachecollectors/imagecachecollector.h>
#include <imagecachecollectors/imagecacheconnectionmanager.h>
#include <imagecachecollectors/imagecachefontcollector.h> #include <imagecachecollectors/imagecachefontcollector.h>
#include <modelnodeoperations.h> #include <modelnodeoperations.h>
#include <nodelistproperty.h> #include <nodelistproperty.h>

View File

@@ -12,7 +12,6 @@
#include <imagecache/imagecachestorage.h> #include <imagecache/imagecachestorage.h>
#include <imagecache/timestampprovider.h> #include <imagecache/timestampprovider.h>
#include <imagecachecollectors/imagecachecollector.h> #include <imagecachecollectors/imagecachecollector.h>
#include <imagecachecollectors/imagecacheconnectionmanager.h>
#include <sqlitedatabase.h> #include <sqlitedatabase.h>

View File

@@ -2,50 +2,31 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "imagecachecollector.h" #include "imagecachecollector.h"
#include "imagecacheconnectionmanager.h"
#include <model.h> #include <externaldependenciesinterface.h>
#include <nodeinstanceview.h> #include <qmlpuppetpaths.h>
#include <nodemetainfo.h> #include <qprocessuniqueptr.h>
#include <plaintexteditmodifier.h>
#include <rewriterview.h>
#include <projectexplorer/project.h> #include <projectexplorer/project.h>
#include <projectexplorer/target.h> #include <projectexplorer/target.h>
#include <utils/fileutils.h> #include <utils/fileutils.h>
#include <QDir>
#include <QGuiApplication> #include <QGuiApplication>
#include <QPlainTextEdit> #include <QProcess>
#include <QTemporaryDir>
namespace QmlDesigner { namespace QmlDesigner {
namespace { namespace {
QByteArray fileToByteArray(const QString &filename)
{
QFile file(filename);
QFileInfo fleInfo(file);
if (fleInfo.exists() && file.open(QFile::ReadOnly))
return file.readAll();
return {};
}
QString fileToString(const QString &filename)
{
return QString::fromUtf8(fileToByteArray(filename));
}
} // namespace } // namespace
ImageCacheCollector::ImageCacheCollector(ImageCacheConnectionManager &connectionManager, ImageCacheCollector::ImageCacheCollector(QSize captureImageMinimumSize,
QSize captureImageMinimumSize,
QSize captureImageMaximumSize, QSize captureImageMaximumSize,
ExternalDependenciesInterface &externalDependencies, ExternalDependenciesInterface &externalDependencies,
ImageCacheCollectorNullImageHandling nullImageHandling) ImageCacheCollectorNullImageHandling nullImageHandling)
: m_connectionManager{connectionManager} : captureImageMinimumSize{captureImageMinimumSize}
, captureImageMinimumSize{captureImageMinimumSize}
, captureImageMaximumSize{captureImageMaximumSize} , captureImageMaximumSize{captureImageMaximumSize}
, m_externalDependencies{externalDependencies} , m_externalDependencies{externalDependencies}
, nullImageHandling{nullImageHandling} , nullImageHandling{nullImageHandling}
@@ -65,108 +46,41 @@ QImage scaleImage(const QImage &image, QSize targetSize)
QSize scaledImageSize = image.size().scaled(targetSize.boundedTo(image.size()), QSize scaledImageSize = image.size().scaled(targetSize.boundedTo(image.size()),
Qt::KeepAspectRatio); Qt::KeepAspectRatio);
return image.scaled(scaledImageSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); return image.scaled(scaledImageSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
} }
} // namespace } // namespace
void ImageCacheCollector::start(Utils::SmallStringView name, void ImageCacheCollector::start(Utils::SmallStringView name,
Utils::SmallStringView state, Utils::SmallStringView,
const ImageCache::AuxiliaryData &auxiliaryData, const ImageCache::AuxiliaryData &auxiliaryData,
CaptureCallback captureCallback, CaptureCallback captureCallback,
AbortCallback abortCallback, AbortCallback abortCallback,
ImageCache::TraceToken traceToken) ImageCache::TraceToken traceToken)
{ {
#ifdef QDS_USE_PROJECTSTORAGE
if (!m_projectStorage || !m_pathCache)
return;
#endif
using namespace NanotraceHR::Literals; using namespace NanotraceHR::Literals;
auto [collectorTraceToken, flowtoken] = traceToken.beginDurationWithFlow( auto [collectorTraceToken, flowtoken] = traceToken.beginDurationWithFlow(
"generate image in standard collector"); "generate image in standard collector");
RewriterView rewriterView{m_externalDependencies, RewriterView::Amend}; QTemporaryDir outDir(QDir::tempPath() + "/qds_imagecache_XXXXXX");
rewriterView.setPossibleImportsEnabled(false); QString outFile = outDir.filePath("capture.png");
NodeInstanceView nodeInstanceView{m_connectionManager, m_externalDependencies};
nodeInstanceView.setCaptureImageMinimumAndMaximumSize(captureImageMinimumSize,
captureImageMaximumSize);
const QString filePath{name}; QImage captureImage;
#ifdef QDS_USE_PROJECTSTORAGE if (runProcess(createArguments(name, outFile, auxiliaryData))) {
auto model = QmlDesigner::Model::create({*m_projectStorage, *m_pathCache}, captureImage.load(outFile);
"Item", } else {
{},
QUrl::fromLocalFile(filePath));
#else
auto model = QmlDesigner::Model::create("QtQuick/Item", 2, 1);
model->setFileUrl(QUrl::fromLocalFile(filePath));
#endif
auto textDocument = std::make_unique<QTextDocument>(fileToString(filePath));
auto modifier = std::make_unique<NotIndentingTextEditModifier>(textDocument.get(),
QTextCursor{textDocument.get()});
rewriterView.setTextModifier(modifier.get());
model->setRewriterView(&rewriterView);
auto rootModelNodeMetaInfo = rewriterView.rootModelNode().metaInfo();
bool is3DRoot = rewriterView.errors().isEmpty()
&& (rootModelNodeMetaInfo.isQtQuick3DNode()
|| rootModelNodeMetaInfo.isQtQuick3DMaterial());
if (!rewriterView.errors().isEmpty() || (!rewriterView.rootModelNode().metaInfo().isGraphicalItem()
&& !is3DRoot)) {
if (abortCallback) if (abortCallback)
abortCallback(ImageCache::AbortReason::Failed, std::move(flowtoken)); abortCallback(ImageCache::AbortReason::Failed, std::move(flowtoken));
return; return;
} }
if (is3DRoot) {
if (auto libIcon = std::get_if<ImageCache::LibraryIconAuxiliaryData>(&auxiliaryData))
rewriterView.rootModelNode().setAuxiliaryData(AuxiliaryDataType::NodeInstancePropertyOverwrite,
"isLibraryIcon",
libIcon->enable);
}
ModelNode stateNode = rewriterView.modelNodeForId(QString{state});
if (stateNode.isValid())
rewriterView.setCurrentStateNode(stateNode);
QImage captureImage;
auto callback = [&](const QImage &image) { captureImage = image; };
if (!m_target)
return;
nodeInstanceView.setTarget(m_target.data());
m_connectionManager.setCallback(std::move(callback));
bool isCrashed = false;
nodeInstanceView.setCrashCallback([&] { isCrashed = true; });
model->setNodeInstanceView(&nodeInstanceView);
bool capturedDataArrived = m_connectionManager.waitForCapturedData();
m_connectionManager.setCallback({});
m_connectionManager.setCrashCallback({});
model->setNodeInstanceView({});
model->setRewriterView({});
if (isCrashed)
abortCallback(ImageCache::AbortReason::Failed, std::move(flowtoken));
if (!capturedDataArrived && abortCallback)
abortCallback(ImageCache::AbortReason::Failed, std::move(flowtoken));
if (nullImageHandling == ImageCacheCollectorNullImageHandling::CaptureNullImage if (nullImageHandling == ImageCacheCollectorNullImageHandling::CaptureNullImage
|| !captureImage.isNull()) { || !captureImage.isNull()) {
QImage midSizeImage = scaleImage(captureImage, QSize{300, 300}); QImage midSizeImage = scaleImage(captureImage, QSize{300, 300});
QImage smallImage = scaleImage(midSizeImage, QSize{96, 96}); QImage smallImage = scaleImage(midSizeImage, QSize{96, 96});
captureCallback(captureImage, midSizeImage, smallImage, std::move(flowtoken)); captureCallback(captureImage, midSizeImage, smallImage, std::move(flowtoken));
} else {
if (abortCallback)
abortCallback(ImageCache::AbortReason::Failed, std::move(flowtoken));
return;
} }
} }
@@ -193,4 +107,72 @@ ProjectExplorer::Target *ImageCacheCollector::target() const
return m_target; return m_target;
} }
QStringList ImageCacheCollector::createArguments(Utils::SmallStringView name,
const QString &outFile,
const ImageCache::AuxiliaryData &auxiliaryData) const
{
QStringList arguments;
const QString filePath{name};
arguments.append("--qml-renderer");
arguments.append(filePath);
if (m_target && m_target->project()) {
arguments.append("-i");
arguments.append(m_target->project()->projectDirectory().toFSPathString());
}
arguments.append("-o");
arguments.append(outFile);
if (std::holds_alternative<ImageCache::LibraryIconAuxiliaryData>(auxiliaryData))
arguments.append("--libIcon");
if (captureImageMinimumSize.isValid()) {
arguments.append("--minW");
arguments.append(QString::number(captureImageMinimumSize.width()));
arguments.append("--minH");
arguments.append(QString::number(captureImageMinimumSize.height()));
}
if (captureImageMaximumSize.isValid()) {
arguments.append("--maxW");
arguments.append(QString::number(captureImageMaximumSize.width()));
arguments.append("--maxH");
arguments.append(QString::number(captureImageMaximumSize.height()));
}
return arguments;
}
bool ImageCacheCollector::runProcess(const QStringList &arguments) const
{
if (!m_target)
return false;
auto [workingDirectoryPath, puppetPath] = QmlDesigner::QmlPuppetPaths::qmlPuppetPaths(
target(), m_externalDependencies.designerSettings());
if (puppetPath.isEmpty())
return false;
QProcessUniquePointer puppetProcess{new QProcess};
QObject::connect(QCoreApplication::instance(),
&QCoreApplication::aboutToQuit,
puppetProcess.get(),
&QProcess::kill);
puppetProcess->setWorkingDirectory(workingDirectoryPath.toFSPathString());
puppetProcess->setProcessChannelMode(QProcess::ForwardedChannels);
puppetProcess->start(puppetPath.toFSPathString(), arguments);
if (puppetProcess->waitForFinished(30000)) {
return puppetProcess->exitStatus() == QProcess::ExitStatus::NormalExit
&& puppetProcess->exitCode() == 0;
}
return false;
}
} // namespace QmlDesigner } // namespace QmlDesigner

View File

@@ -5,25 +5,14 @@
#include <imagecache/imagecachecollectorinterface.h> #include <imagecache/imagecachecollectorinterface.h>
#include <modelfwd.h>
#include <QPointer> #include <QPointer>
QT_BEGIN_NAMESPACE
class QTextDocument;
QT_END_NAMESPACE
namespace ProjectExplorer { namespace ProjectExplorer {
class Target; class Target;
} }
namespace QmlDesigner { namespace QmlDesigner {
class Model;
class NotIndentingTextEditModifier;
class ImageCacheConnectionManager;
class RewriterView;
class NodeInstanceView;
class ExternalDependenciesInterface; class ExternalDependenciesInterface;
enum class ImageCacheCollectorNullImageHandling { CaptureNullImage, DontCaptureNullImage }; enum class ImageCacheCollectorNullImageHandling { CaptureNullImage, DontCaptureNullImage };
@@ -31,8 +20,7 @@ enum class ImageCacheCollectorNullImageHandling { CaptureNullImage, DontCaptureN
class ImageCacheCollector final : public ImageCacheCollectorInterface class ImageCacheCollector final : public ImageCacheCollectorInterface
{ {
public: public:
ImageCacheCollector(ImageCacheConnectionManager &connectionManager, ImageCacheCollector(QSize captureImageMinimumSize,
QSize captureImageMinimumSize,
QSize captureImageMaximumSize, QSize captureImageMaximumSize,
ExternalDependenciesInterface &externalDependencies, ExternalDependenciesInterface &externalDependencies,
ImageCacheCollectorNullImageHandling nullImageHandling = {}); ImageCacheCollectorNullImageHandling nullImageHandling = {});
@@ -58,16 +46,17 @@ public:
ProjectExplorer::Target *target() const; ProjectExplorer::Target *target() const;
private: private:
ImageCacheConnectionManager &m_connectionManager; bool runProcess(const QStringList &arguments) const;
QStringList createArguments(Utils::SmallStringView name,
const QString &outFile,
const ImageCache::AuxiliaryData &auxiliaryData) const;
private:
QPointer<ProjectExplorer::Target> m_target; QPointer<ProjectExplorer::Target> m_target;
QSize captureImageMinimumSize; QSize captureImageMinimumSize;
QSize captureImageMaximumSize; QSize captureImageMaximumSize;
ExternalDependenciesInterface &m_externalDependencies; ExternalDependenciesInterface &m_externalDependencies;
ImageCacheCollectorNullImageHandling nullImageHandling{}; ImageCacheCollectorNullImageHandling nullImageHandling{};
#ifdef QDS_USE_PROJECTSTORAGE
ProjectStorageType *m_projectStorage = nullptr;
PathCacheType *m_pathCache = nullptr;
#endif
}; };
} // namespace QmlDesigner } // namespace QmlDesigner

View File

@@ -1,56 +0,0 @@
// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "imagecacheconnectionmanager.h"
#include <captureddatacommand.h>
#include <QLocalSocket>
namespace QmlDesigner {
ImageCacheConnectionManager::ImageCacheConnectionManager()
{
connections().emplace_back("Capture icon", "captureiconmode");
}
void ImageCacheConnectionManager::setCallback(ImageCacheConnectionManager::Callback callback)
{
m_captureCallback = std::move(callback);
}
bool ImageCacheConnectionManager::waitForCapturedData()
{
if (connections().empty())
return false;
disconnect(connections().front().socket.get(), &QIODevice::readyRead, nullptr, nullptr);
while (!m_capturedDataArrived) {
if (!(connections().front().socket))
return false;
bool dataArrived = connections().front().socket->waitForReadyRead(10000);
if (!dataArrived)
return false;
readDataStream(connections().front());
}
m_capturedDataArrived = false;
return true;
}
void ImageCacheConnectionManager::dispatchCommand(const QVariant &command,
ConnectionManagerInterface::Connection &)
{
static const int capturedDataCommandType = QMetaType::fromName("CapturedDataCommand").id();
if (command.typeId() == capturedDataCommandType) {
m_captureCallback(command.value<CapturedDataCommand>().image);
m_capturedDataArrived = true;
}
}
} // namespace QmlDesigner

View File

@@ -1,31 +0,0 @@
// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#pragma once
#include <instances/connectionmanager.h>
namespace QmlDesigner {
class CapturedDataCommand;
class ImageCacheConnectionManager : public ConnectionManager
{
public:
using Callback = std::function<void(const QImage &)>;
ImageCacheConnectionManager();
void setCallback(Callback captureCallback);
bool waitForCapturedData();
protected:
void dispatchCommand(const QVariant &command, Connection &connection) override;
private:
Callback m_captureCallback;
bool m_capturedDataArrived = false;
};
} // namespace QmlDesigner

View File

@@ -2,7 +2,6 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "meshimagecachecollector.h" #include "meshimagecachecollector.h"
#include "imagecacheconnectionmanager.h"
#include <projectexplorer/target.h> #include <projectexplorer/target.h>
#include <utils/smallstring.h> #include <utils/smallstring.h>
@@ -12,13 +11,11 @@
namespace QmlDesigner { namespace QmlDesigner {
MeshImageCacheCollector::MeshImageCacheCollector(ImageCacheConnectionManager &connectionManager, MeshImageCacheCollector::MeshImageCacheCollector(QSize captureImageMinimumSize,
QSize captureImageMinimumSize,
QSize captureImageMaximumSize, QSize captureImageMaximumSize,
ExternalDependenciesInterface &externalDependencies, ExternalDependenciesInterface &externalDependencies,
ImageCacheCollectorNullImageHandling nullImageHandling) ImageCacheCollectorNullImageHandling nullImageHandling)
: m_imageCacheCollector(connectionManager, : m_imageCacheCollector(captureImageMinimumSize,
captureImageMinimumSize,
captureImageMaximumSize, captureImageMaximumSize,
externalDependencies, externalDependencies,
nullImageHandling) nullImageHandling)

View File

@@ -11,13 +11,10 @@ class Target;
namespace QmlDesigner { namespace QmlDesigner {
class ImageCacheConnectionManager;
class MeshImageCacheCollector final : public ImageCacheCollectorInterface class MeshImageCacheCollector final : public ImageCacheCollectorInterface
{ {
public: public:
MeshImageCacheCollector(ImageCacheConnectionManager &connectionManager, MeshImageCacheCollector(QSize captureImageMinimumSize,
QSize captureImageMinimumSize,
QSize captureImageMaximumSize, QSize captureImageMaximumSize,
ExternalDependenciesInterface &externalDependencies, ExternalDependenciesInterface &externalDependencies,
ImageCacheCollectorNullImageHandling nullImageHandling = {}); ImageCacheCollectorNullImageHandling nullImageHandling = {});

View File

@@ -1272,8 +1272,6 @@ CreateSceneCommand NodeInstanceView::createCreateSceneCommand()
m_externalDependencies.currentResourcePath(), m_externalDependencies.currentResourcePath(),
sceneStates, sceneStates,
lastUsedLanguage, lastUsedLanguage,
m_captureImageMinimumSize,
m_captureImageMaximumSize,
stateInstanceId); stateInstanceId);
} }

View File

@@ -156,13 +156,6 @@ public:
m_crashCallback = std::move(crashCallback); m_crashCallback = std::move(crashCallback);
} }
void setCaptureImageMinimumAndMaximumSize(QSize captureImageMinimumSize,
QSize captureImageMaximumSize)
{
m_captureImageMinimumSize = captureImageMinimumSize;
m_captureImageMaximumSize = captureImageMaximumSize;
}
void startNanotrace(); void startNanotrace();
void endNanotrace(); void endNanotrace();
@@ -313,8 +306,6 @@ private:
QHash<QString, QStringList> m_qsbPathToFilterMap; QHash<QString, QStringList> m_qsbPathToFilterMap;
int m_remainingQsbTargets = 0; int m_remainingQsbTargets = 0;
QTimer m_rotBlockTimer; QTimer m_rotBlockTimer;
QSize m_captureImageMinimumSize{150, 150};
QSize m_captureImageMaximumSize{1000, 1000};
bool m_qsbEnabled = false; bool m_qsbEnabled = false;
}; };

View File

@@ -33,7 +33,6 @@
#include <imagecache/imagecachestorage.h> #include <imagecache/imagecachestorage.h>
#include <imagecache/timestampprovider.h> #include <imagecache/timestampprovider.h>
#include <imagecachecollectors/imagecachecollector.h> #include <imagecachecollectors/imagecachecollector.h>
#include <imagecachecollectors/imagecacheconnectionmanager.h>
#include <imagecachecollectors/meshimagecachecollector.h> #include <imagecachecollectors/meshimagecachecollector.h>
#include <imagecachecollectors/textureimagecachecollector.h> #include <imagecachecollectors/textureimagecachecollector.h>
@@ -124,8 +123,8 @@ class QmlDesignerProjectManager::ImageCacheData
{ {
public: public:
ImageCacheData(ExternalDependenciesInterface &externalDependencies) ImageCacheData(ExternalDependenciesInterface &externalDependencies)
: meshImageCollector{connectionManager, QSize{300, 300}, QSize{600, 600}, externalDependencies} : meshImageCollector{QSize{300, 300}, QSize{600, 600}, externalDependencies}
, nodeInstanceCollector{connectionManager, QSize{300, 300}, QSize{600, 600}, externalDependencies} , nodeInstanceCollector{QSize{300, 300}, QSize{600, 600}, externalDependencies}
{} {}
public: public:
@@ -134,7 +133,6 @@ public:
Sqlite::JournalMode::Wal, Sqlite::JournalMode::Wal,
Sqlite::LockingMode::Normal}; Sqlite::LockingMode::Normal};
ImageCacheStorage<Sqlite::Database> storage{database}; ImageCacheStorage<Sqlite::Database> storage{database};
ImageCacheConnectionManager connectionManager;
MeshImageCacheCollector meshImageCollector; MeshImageCacheCollector meshImageCollector;
TextureImageCacheCollector textureImageCollector; TextureImageCacheCollector textureImageCollector;
ImageCacheCollector nodeInstanceCollector; ImageCacheCollector nodeInstanceCollector;
@@ -153,8 +151,7 @@ class QmlDesignerProjectManager::PreviewImageCacheData
{ {
public: public:
PreviewImageCacheData(ExternalDependenciesInterface &externalDependencies) PreviewImageCacheData(ExternalDependenciesInterface &externalDependencies)
: collector{connectionManager, : collector{QSize{300, 300},
QSize{300, 300},
QSize{1000, 1000}, QSize{1000, 1000},
externalDependencies, externalDependencies,
ImageCacheCollectorNullImageHandling::CaptureNullImage} ImageCacheCollectorNullImageHandling::CaptureNullImage}
@@ -168,7 +165,6 @@ public:
Sqlite::JournalMode::Wal, Sqlite::JournalMode::Wal,
Sqlite::LockingMode::Normal}; Sqlite::LockingMode::Normal};
ImageCacheStorage<Sqlite::Database> storage{database}; ImageCacheStorage<Sqlite::Database> storage{database};
ImageCacheConnectionManager connectionManager;
ImageCacheCollector collector; ImageCacheCollector collector;
PreviewTimeStampProvider timeStampProvider; PreviewTimeStampProvider timeStampProvider;
AsynchronousExplicitImageCache cache{storage}; AsynchronousExplicitImageCache cache{storage};
@@ -255,8 +251,7 @@ public:
::ProjectExplorer::Project *project, ::ProjectExplorer::Project *project,
PathCacheType &pathCache, PathCacheType &pathCache,
ExternalDependenciesInterface &externalDependencies) ExternalDependenciesInterface &externalDependencies)
: collector{connectionManager, : collector{QSize{300, 300},
QSize{300, 300},
QSize{1000, 1000}, QSize{1000, 1000},
externalDependencies, externalDependencies,
ImageCacheCollectorNullImageHandling::CaptureNullImage} ImageCacheCollectorNullImageHandling::CaptureNullImage}
@@ -264,7 +259,6 @@ public:
, projectStorageData{createProjectStorageData(project, pathCache)} , projectStorageData{createProjectStorageData(project, pathCache)}
{} {}
ImageCacheConnectionManager connectionManager;
ImageCacheCollector collector; ImageCacheCollector collector;
PreviewTimeStampProvider timeStampProvider; PreviewTimeStampProvider timeStampProvider;
AsynchronousImageFactory factory; AsynchronousImageFactory factory;

View File

@@ -168,7 +168,6 @@ extend_qtc_executable(qmlpuppet
qt5capturepreviewnodeinstanceserver.cpp qt5capturepreviewnodeinstanceserver.h qt5capturepreviewnodeinstanceserver.cpp qt5capturepreviewnodeinstanceserver.h
nodeinstanceserverdispatcher.cpp nodeinstanceserverdispatcher.h nodeinstanceserverdispatcher.cpp nodeinstanceserverdispatcher.h
capturenodeinstanceserverdispatcher.cpp capturenodeinstanceserverdispatcher.h capturenodeinstanceserverdispatcher.cpp capturenodeinstanceserverdispatcher.h
qt5captureimagenodeinstanceserver.cpp qt5captureimagenodeinstanceserver.h
viewconfig.cpp viewconfig.h viewconfig.cpp viewconfig.h
animationdriver.cpp animationdriver.h animationdriver.cpp animationdriver.h
) )

View File

@@ -22,7 +22,6 @@ versionAtLeast(QT_VERSION, 5.15.0) {
HEADERS += $$PWD/qt5nodeinstanceserver.h \ HEADERS += $$PWD/qt5nodeinstanceserver.h \
$$PWD/capturenodeinstanceserverdispatcher.h \ $$PWD/capturenodeinstanceserverdispatcher.h \
$$PWD/nodeinstanceserverdispatcher.h \ $$PWD/nodeinstanceserverdispatcher.h \
$$PWD/qt5captureimagenodeinstanceserver.h \
$$PWD/qt5capturepreviewnodeinstanceserver.h \ $$PWD/qt5capturepreviewnodeinstanceserver.h \
$$PWD/qt5testnodeinstanceserver.h \ $$PWD/qt5testnodeinstanceserver.h \
$$PWD/qt5bakelightsnodeinstanceserver.h \ $$PWD/qt5bakelightsnodeinstanceserver.h \
@@ -58,7 +57,6 @@ HEADERS += $$PWD/qt5nodeinstanceserver.h \
SOURCES += $$PWD/qt5nodeinstanceserver.cpp \ SOURCES += $$PWD/qt5nodeinstanceserver.cpp \
$$PWD/capturenodeinstanceserverdispatcher.cpp \ $$PWD/capturenodeinstanceserverdispatcher.cpp \
$$PWD/nodeinstanceserverdispatcher.cpp \ $$PWD/nodeinstanceserverdispatcher.cpp \
$$PWD/qt5captureimagenodeinstanceserver.cpp \
$$PWD/qt5capturepreviewnodeinstanceserver.cpp \ $$PWD/qt5capturepreviewnodeinstanceserver.cpp \
$$PWD/qt5testnodeinstanceserver.cpp \ $$PWD/qt5testnodeinstanceserver.cpp \
$$PWD/qt5bakelightsnodeinstanceserver.cpp \ $$PWD/qt5bakelightsnodeinstanceserver.cpp \

View File

@@ -4,7 +4,6 @@
#include "nodeinstanceserverdispatcher.h" #include "nodeinstanceserverdispatcher.h"
#include "qt5bakelightsnodeinstanceserver.h" #include "qt5bakelightsnodeinstanceserver.h"
#include "qt5captureimagenodeinstanceserver.h"
#include "qt5capturepreviewnodeinstanceserver.h" #include "qt5capturepreviewnodeinstanceserver.h"
#include "qt5import3dnodeinstanceserver.h" #include "qt5import3dnodeinstanceserver.h"
#include "qt5informationnodeinstanceserver.h" #include "qt5informationnodeinstanceserver.h"
@@ -164,8 +163,6 @@ std::unique_ptr<NodeInstanceServer> createNodeInstanceServer(
{ {
if (serverName == "capturemode") if (serverName == "capturemode")
return std::make_unique<Qt5CapturePreviewNodeInstanceServer>(nodeInstanceClient); return std::make_unique<Qt5CapturePreviewNodeInstanceServer>(nodeInstanceClient);
else if (serverName == "captureiconmode")
return std::make_unique<Qt5CaptureImageNodeInstanceServer>(nodeInstanceClient);
else if (serverName == "rendermode") else if (serverName == "rendermode")
return std::make_unique<Qt5RenderNodeInstanceServer>(nodeInstanceClient); return std::make_unique<Qt5RenderNodeInstanceServer>(nodeInstanceClient);
else if (serverName == "editormode") else if (serverName == "editormode")

View File

@@ -1,79 +0,0 @@
// Copyright (C) 2020 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "qt5captureimagenodeinstanceserver.h"
#include "servernodeinstance.h"
#include <captureddatacommand.h>
#include <createscenecommand.h>
#include <nodeinstanceclientinterface.h>
#include <QImage>
#include <QQuickItem>
#include <QQuickView>
#include <private/qquickdesignersupport_p.h>
namespace QmlDesigner {
namespace {
QImage renderImage(ServerNodeInstance rootNodeInstance, QSize minimumSize, QSize maximumSize)
{
rootNodeInstance.updateDirtyNodeRecursive();
QSize previewImageSize = rootNodeInstance.boundingRect().size().toSize();
if (previewImageSize.isEmpty()) {
previewImageSize = minimumSize;
} else if (previewImageSize.width() < minimumSize.width()
|| previewImageSize.height() < minimumSize.height()) {
previewImageSize.scale(minimumSize, Qt::KeepAspectRatio);
}
if (previewImageSize.width() > maximumSize.width()
|| previewImageSize.height() > maximumSize.height()) {
previewImageSize.scale(maximumSize, Qt::KeepAspectRatio);
}
QImage previewImage = rootNodeInstance.renderPreviewImage(previewImageSize);
return previewImage;
}
} // namespace
void Qt5CaptureImageNodeInstanceServer::collectItemChangesAndSendChangeCommands()
{
static bool inFunction = false;
if (!rootNodeInstance().holdsGraphical()) {
nodeInstanceClient()->capturedData(CapturedDataCommand{});
return;
}
if (!inFunction) {
inFunction = true;
auto rooNodeInstance = rootNodeInstance();
if (QQuickItem *qitem = rooNodeInstance.rootQuickItem())
qitem->setClip(true);
QQuickDesignerSupport::polishItems(quickWindow());
QImage image = renderImage(rooNodeInstance, m_minimumSize, m_maximumSize);
nodeInstanceClient()->capturedData(CapturedDataCommand{std::move(image)});
slowDownRenderTimer();
inFunction = false;
}
}
void QmlDesigner::Qt5CaptureImageNodeInstanceServer::createScene(const CreateSceneCommand &command)
{
m_minimumSize = command.captureImageMinimumSize;
m_maximumSize = command.captureImageMaximumSize;
Qt5PreviewNodeInstanceServer::createScene(command);
}
} // namespace

View File

@@ -1,27 +0,0 @@
// Copyright (C) 2020 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#pragma once
#include <qt5previewnodeinstanceserver.h>
namespace QmlDesigner {
class Qt5CaptureImageNodeInstanceServer : public Qt5PreviewNodeInstanceServer
{
public:
explicit Qt5CaptureImageNodeInstanceServer(NodeInstanceClientInterface *nodeInstanceClient)
: Qt5PreviewNodeInstanceServer(nodeInstanceClient)
{}
void createScene(const CreateSceneCommand &command) override;
protected:
void collectItemChangesAndSendChangeCommands() override;
private:
QSize m_minimumSize;
QSize m_maximumSize;
};
} // namespace QmlDesigner

View File

@@ -7,7 +7,6 @@
#include "capturenodeinstanceserverdispatcher.h" #include "capturenodeinstanceserverdispatcher.h"
#include "qt5bakelightsnodeinstanceserver.h" #include "qt5bakelightsnodeinstanceserver.h"
#include "qt5captureimagenodeinstanceserver.h"
#include "qt5capturepreviewnodeinstanceserver.h" #include "qt5capturepreviewnodeinstanceserver.h"
#include "qt5informationnodeinstanceserver.h" #include "qt5informationnodeinstanceserver.h"
#include "qt5import3dnodeinstanceserver.h" #include "qt5import3dnodeinstanceserver.h"
@@ -65,9 +64,6 @@ Qt5NodeInstanceClientProxy::Qt5NodeInstanceClientProxy(QObject *parent) :
} else if (QCoreApplication::arguments().at(2) == QLatin1String("capturemode")) { } else if (QCoreApplication::arguments().at(2) == QLatin1String("capturemode")) {
setNodeInstanceServer(std::make_unique<Qt5CapturePreviewNodeInstanceServer>(this)); setNodeInstanceServer(std::make_unique<Qt5CapturePreviewNodeInstanceServer>(this));
initializeSocket(); initializeSocket();
} else if (QCoreApplication::arguments().at(2) == QLatin1String("captureiconmode")) {
setNodeInstanceServer(std::make_unique<Qt5CaptureImageNodeInstanceServer>(this));
initializeSocket();
} else if (QCoreApplication::arguments().at(2) == QLatin1String("bakelightsmode")) { } else if (QCoreApplication::arguments().at(2) == QLatin1String("bakelightsmode")) {
setNodeInstanceServer(std::make_unique<Qt5BakeLightsNodeInstanceServer>(this)); setNodeInstanceServer(std::make_unique<Qt5BakeLightsNodeInstanceServer>(this));
initializeSocket(); initializeSocket();

View File

@@ -16,6 +16,9 @@
#include <QFileInfo> #include <QFileInfo>
#include <QQmlComponent> #include <QQmlComponent>
constexpr int DEFAULT_RENDER_DIM = 300;
constexpr int DEFAULT_MAX_DIM = 1024;
void QmlRenderer::initCoreApp() void QmlRenderer::initCoreApp()
{ {
#if defined QT_WIDGETS_LIB #if defined QT_WIDGETS_LIB
@@ -36,15 +39,29 @@ void QmlRenderer::populateParser()
"Output image file path.", "Output image file path.",
"path"}, "path"},
// "h" is reserved arg for help, so use capital letters for height/width // The qml component is rendered at its preferred size if available
{QStringList{"H", "height"}, // Min/max dimensions specify a range of acceptable final scaled sizes
"Height of the final rendered image.", // If the size of rendered item is outside the min/max range, it is cropped in final scaling
{QStringList{"minW"},
"Minimum width of the final scaled rendered image.",
"pixels"}, "pixels"},
{QStringList{"W", "width"}, {QStringList{"minH"},
"Width of the final rendered image.", "Minimum height of the final scaled rendered image.",
"pixels"}, "pixels"},
{QStringList{"maxW"},
QString("Maximum width of the final scaled rendered image."),
"pixels"},
{QStringList{"maxH"},
"Maximum height of the final scaled rendered image.",
"pixels"},
{QStringList{"libIcon"},
"Render library icon rather than regular preview."
"It zooms 3D objects bit more aggressively and suppresses the background."},
{QStringList{"v", "verbose"}, {QStringList{"v", "verbose"},
"Display additional output."} "Display additional output."}
}); });
@@ -56,10 +73,14 @@ void QmlRenderer::initQmlRunner()
{ {
if (m_argParser.isSet("importpath")) if (m_argParser.isSet("importpath"))
m_importPaths = m_argParser.value("importpath").split(";"); m_importPaths = m_argParser.value("importpath").split(";");
if (m_argParser.isSet("width")) if (m_argParser.isSet("minW"))
m_requestedSize.setWidth(m_argParser.value("width").toInt()); m_reqMinSize.setWidth(m_argParser.value("minW").toInt());
if (m_argParser.isSet("height")) if (m_argParser.isSet("minH"))
m_requestedSize.setHeight(m_argParser.value("height").toInt()); m_reqMinSize.setHeight(m_argParser.value("minH").toInt());
if (m_argParser.isSet("maxW"))
m_reqMaxSize.setWidth(m_argParser.value("maxW").toInt());
if (m_argParser.isSet("maxH"))
m_reqMaxSize.setHeight(m_argParser.value("maxH").toInt());
if (m_argParser.isSet("verbose")) if (m_argParser.isSet("verbose"))
m_verbose = true; m_verbose = true;
@@ -76,17 +97,18 @@ void QmlRenderer::initQmlRunner()
else else
m_outFile = m_sourceFile + ".png"; m_outFile = m_sourceFile + ".png";
if (m_requestedSize.width() <= 0) if (m_argParser.isSet("libIcon"))
m_requestedSize.setWidth(150); m_isLibIcon = true;
if (m_requestedSize.height() <= 0)
m_requestedSize.setHeight(150);
if (m_verbose) { if (m_verbose) {
info(QString("Import path = %1").arg(m_importPaths.join(";"))); info(QString("Import path = %1").arg(m_importPaths.join(";")));
info(QString("Requested size = %1 x %2").arg(m_requestedSize.width()) info(QString("Requested min size = %1 x %2").arg(m_reqMinSize.width())
.arg(m_requestedSize.height())); .arg(m_reqMinSize.height()));
info(QString("Requested max size = %1 x %2").arg(m_reqMaxSize.width())
.arg(m_reqMaxSize.height()));
info(QString("Source file = %1").arg(m_sourceFile)); info(QString("Source file = %1").arg(m_sourceFile));
info(QString("Output file = %1").arg(m_outFile)); info(QString("Output file = %1").arg(m_outFile));
info(QString("Is library icon = %1").arg(m_isLibIcon ? QString("true") : QString("false")));
} }
if (setupRenderer()) { if (setupRenderer()) {
@@ -140,6 +162,9 @@ bool QmlRenderer::setupRenderer()
} }
m_containerItem->setParentItem(m_window->contentItem()); m_containerItem->setParentItem(m_window->contentItem());
if (m_isLibIcon)
QMetaObject::invokeMethod(m_containerItem, "setIconMode", Q_ARG(QVariant, true));
contentItem3D = QQmlProperty::read(m_containerItem, "contentItem").value<QQuickItem *>(); contentItem3D = QQmlProperty::read(m_containerItem, "contentItem").value<QQuickItem *>();
if (qobject_cast<QQuick3DNode *>(renderObj)) { if (qobject_cast<QQuick3DNode *>(renderObj)) {
QMetaObject::invokeMethod( QMetaObject::invokeMethod(
@@ -155,28 +180,25 @@ bool QmlRenderer::setupRenderer()
} }
m_is3D = true; m_is3D = true;
m_renderSize = m_requestedSize; setRenderSize({});
contentItem3D->setSize(m_requestedSize); contentItem3D->setSize(m_renderSize);
} else } else
#endif // QUICK3D_MODULE #endif // QUICK3D_MODULE
if (auto renderItem = qobject_cast<QQuickItem *>(renderObj)) { if (auto renderItem = qobject_cast<QQuickItem *>(renderObj)) {
m_renderSize = renderItem->size().toSize(); setRenderSize(renderItem->size().toSize());
if (m_renderSize.width() <= 0)
m_renderSize.setWidth(m_requestedSize.width());
if (m_renderSize.height() <= 0)
m_renderSize.setHeight(m_requestedSize.height());
renderItem->setSize(m_renderSize); renderItem->setSize(m_renderSize);
renderItem->setParentItem(m_window->contentItem()); renderItem->setParentItem(m_window->contentItem());
// When rendering 2D scenes, we just render the given QML without extra container // When rendering 2D scenes, we just render the given QML without extra container
m_containerItem = renderItem; m_containerItem = renderItem;
} else if (auto renderWindow = qobject_cast<QQuickWindow *>(renderObj)) { } else if (auto renderWindow = qobject_cast<QQuickWindow *>(renderObj)) {
// Hack to render Window items: reparent window content to m_window->contentItem() // Hack to render Window items: reparent window content to m_window->contentItem()
m_renderSize = renderWindow->size(); setRenderSize(renderWindow->size());
m_containerItem = m_window->contentItem(); m_containerItem = m_window->contentItem();
// Suppress the original window. // Suppress the original window.
// Offscreen position ensures we don't get even brief flash of it. // Offscreen position ensures we don't get even brief flash of it.
renderWindow->setPosition(-100000, -100000);
renderWindow->setVisible(false); renderWindow->setVisible(false);
renderWindow->resize(2, 2);
renderWindow->setPosition(-10000, -10000);
const QList<QQuickItem *> childItems = renderWindow->contentItem()->childItems(); const QList<QQuickItem *> childItems = renderWindow->contentItem()->childItems();
for (QQuickItem *item : childItems) { for (QQuickItem *item : childItems) {
item->setParent(m_window->contentItem()); item->setParent(m_window->contentItem());
@@ -274,12 +296,32 @@ void QmlRenderer::render()
QImage wrapperImage(reinterpret_cast<const uchar *>(readResult.data.constData()), QImage wrapperImage(reinterpret_cast<const uchar *>(readResult.data.constData()),
readResult.pixelSize.width(), readResult.pixelSize.height(), readResult.pixelSize.width(), readResult.pixelSize.height(),
QImage::Format_RGBA8888_Premultiplied); QImage::Format_RGBA8888_Premultiplied);
if (m_rhi->isYUpInFramebuffer())
renderImage = wrapperImage.mirrored().scaled(m_requestedSize, Qt::IgnoreAspectRatio, QSize maxSize = m_reqMaxSize;
if (maxSize.width() <= 0) {
m_reqMinSize.width() > DEFAULT_MAX_DIM ? maxSize.setWidth(m_reqMinSize.width())
: maxSize.setWidth(DEFAULT_MAX_DIM);
}
if (maxSize.height() <= 0) {
m_reqMinSize.height() > DEFAULT_MAX_DIM ? maxSize.setHeight(m_reqMinSize.height())
: maxSize.setHeight(DEFAULT_MAX_DIM);
}
QSize scaledSize = m_renderSize.scaled(m_renderSize.expandedTo(m_reqMinSize).boundedTo(maxSize),
Qt::KeepAspectRatio);
info(QString("Rendered size = %1 x %2").arg(m_renderSize.width())
.arg(m_renderSize.height()));
info(QString("Scaled size = %1 x %2").arg(scaledSize.width())
.arg(scaledSize.height()));
if (m_rhi->isYUpInFramebuffer()) {
renderImage = wrapperImage.mirrored().scaled(scaledSize, Qt::IgnoreAspectRatio,
Qt::SmoothTransformation); Qt::SmoothTransformation);
else } else {
renderImage = wrapperImage.copy().scaled(m_requestedSize, Qt::IgnoreAspectRatio, renderImage = wrapperImage.copy().scaled(scaledSize, Qt::IgnoreAspectRatio,
Qt::SmoothTransformation); Qt::SmoothTransformation);
}
}; };
QRhiResourceUpdateBatch *readbackBatch = m_rhi->nextResourceUpdateBatch(); QRhiResourceUpdateBatch *readbackBatch = m_rhi->nextResourceUpdateBatch();
readbackBatch->readBackTexture(m_texture.get(), &readResult); readbackBatch->readBackTexture(m_texture.get(), &readResult);
@@ -316,3 +358,16 @@ void QmlRenderer::asyncQuit(int errorCode)
exit(errorCode); exit(errorCode);
}); });
} }
void QmlRenderer::setRenderSize(QSize size)
{
m_renderSize = size;
if (m_renderSize.width() <= 0) {
m_reqMaxSize.width() > 0 ? m_renderSize.setWidth(m_reqMaxSize.width())
: m_renderSize.setWidth(DEFAULT_RENDER_DIM);
}
if (m_renderSize.height() <= 0) {
m_reqMaxSize.height() > 0 ? m_renderSize.setHeight(m_reqMaxSize.height())
: m_renderSize.setHeight(DEFAULT_RENDER_DIM);
}
}

View File

@@ -34,14 +34,18 @@ private:
void error(const QString &msg); void error(const QString &msg);
void asyncQuit(int errorCode); void asyncQuit(int errorCode);
void setRenderSize(QSize size);
QStringList m_importPaths; QStringList m_importPaths;
QSize m_requestedSize; QSize m_reqMinSize;
QSize m_reqMaxSize;
QSize m_renderSize; QSize m_renderSize;
QString m_sourceFile; QString m_sourceFile;
QString m_outFile; QString m_outFile;
bool m_verbose = false; bool m_verbose = false;
bool m_is3D = false; bool m_is3D = false;
bool m_fit3D = false; bool m_fit3D = false;
bool m_isLibIcon = false;
QQuickItem *m_containerItem = nullptr; QQuickItem *m_containerItem = nullptr;
QRhi *m_rhi = nullptr; QRhi *m_rhi = nullptr;