问题
I'm trying to render individual tiles from a tileset. For example, I want to display the grey tile in the tileset below:
In the real use case, these would be e.g. water, grass, etc. tiles in a game. There are some requirements for rendering these tiles:
- They are 32x32 pixels and will be rendered fullscreen, so performance is important.
- They should not be smoothed when scaled.
None of the built-in Qt Quick types meet these requirements (rendering a section of an image that's not smoothed), as far as I can tell. I've tried QQuickPaintedItem
with various QPainter
render hints (such as SmoothPixmapTransform
set to false
) without success; the image is "blurry" when upscaled. AnimatedSprite supports rendering sections of an image, but has no API to disable smoothing.
My idea was to implement a custom QQuickItem
using the scene graph API.
main.cpp
:
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQuickItem>
#include <QQuickWindow>
#include <QSGImageNode>
static QImage image;
static const int tileSize = 32;
static const int tilesetSize = 8;
class Tile : public QQuickItem
{
Q_OBJECT
Q_PROPERTY(int index READ index WRITE setIndex NOTIFY indexChanged)
public:
Tile() :
mIndex(-1) {
setWidth(tileSize);
setHeight(tileSize);
setFlag(QQuickItem::ItemHasContents);
}
QSGNode *updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *)
{
if (!oldNode) {
oldNode = window()->createImageNode();
}
if (mIndex == -1)
return oldNode;
if (image.isNull()) {
image = QImage("C:/tileset.png");
if (image.isNull())
return oldNode;
}
QSGTexture *texture = window()->createTextureFromImage(image);
qDebug() << "textureSize:" << texture->textureSize();
if (!texture)
return oldNode;
QSGImageNode *imageNode = static_cast<QSGImageNode*>(oldNode);
// imageNode->setOwnsTexture(true);
imageNode->setTexture(texture);
qDebug() << "source rect:" << (mIndex % tileSize) * tileSize << (mIndex / tileSize) * tileSize << tileSize << tileSize;
imageNode->setSourceRect((mIndex % tileSize) * tileSize, (mIndex / tileSize) * tileSize, tileSize, tileSize);
return oldNode;
}
int index() const {
return mIndex;
}
void setIndex(int index) {
if (index == mIndex)
return;
mIndex = index;
emit indexChanged();
}
signals:
void indexChanged();
private:
int mIndex;
};
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
qmlRegisterType<Tile>("App", 1, 0, "Tile");
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
return app.exec();
}
#include "main.moc"
main.qml
:
import QtQuick 2.9
import QtQuick.Controls 2.2
import App 1.0
ApplicationWindow {
id: window
width: 800
height: 800
visible: true
Slider {
id: slider
from: 1
to: 10
}
Tile {
scale: slider.value
index: 1
anchors.centerIn: parent
Rectangle {
anchors.fill: parent
color: "transparent"
border.color: "darkorange"
}
}
}
The output from this application looks fine, but nothing is rendered within the rectangle:
textureSize: QSize(256, 256)
source rect: 32 0 32 32
Judging from the minimal docs, my implementation (in terms of how I create nodes) seems OK. Where am I going wrong?
回答1:
A year late to the party, but I was running into the same problem as you. For the sake of anyone else who is trying to subclass a QQuickItem
and has come across this thread, there's a little nugget that's in the documentation in regards to updatePaintNode
:
The function is called as a result of
QQuickItem::update()
, if the user has set theQQuickItem::ItemHasContents
flag on the item.
When I set that flag, everything rendered.
And I considered myself a detail-oriented person...
EDIT:
After the OP pointed out they had already set the ItemHasContents
flag, I looked at the code again and saw that while the OP had set the sourceRect
on the node, the OP hadn't set the rect
of the node, and that indeed was the problem the OP was running into.
回答2:
I ended up going with a friend's idea of using QQuickImageProvider
:
tileimageprovider.h
:
#ifndef TILEIMAGEPROVIDER_H
#define TILEIMAGEPROVIDER_H
#include <QQuickImageProvider>
#include <QHash>
#include <QString>
#include <QImage>
class TileImageProvider : public QQuickImageProvider
{
public:
TileImageProvider();
QImage requestImage(const QString &id, QSize *size, const QSize &requestedSize) override;
private:
QHash<QString, QImage> mTiles;
};
#endif // TILEIMAGEPROVIDER_H
tileimageprovider.cpp
:
#include "tileimageprovider.h"
#include <QImage>
#include <QDebug>
TileImageProvider::TileImageProvider() :
QQuickImageProvider(QQmlImageProviderBase::Image)
{
QImage tilesetImage(":/sprites/tiles/tileset.png");
if (tilesetImage.isNull()) {
qWarning() << "Failed to load tileset image";
return;
}
int index = 0;
for (int row = 0; row < 8; ++row) {
for (int col = 0; col < 8; ++col) {
int sourceX = col * 32;
int sourceY = row * 32;
QImage subTile = tilesetImage.copy(sourceX, sourceY, 32, 32);
if (tilesetImage.isNull()) {
qWarning() << "Tile image at" << sourceX << sourceY << "is null";
return;
}
mTiles.insert(QString::number(index++), subTile);
}
}
}
QImage TileImageProvider::requestImage(const QString &id, QSize *size, const QSize &)
{
Q_ASSERT(mTiles.find(id) != mTiles.end());
*size = QSize(32, 32);
return mTiles.value(id);
}
I then create tile instances from the following Component
:
Image {
width: 32
height: 32
smooth: false
asynchronous: true
source: "image://tile/" + index
property int index
}
There's also Tiled's tile rendering code, which uses scenegraph nodes and is likely more efficient.
回答3:
It does seem like you can have it both easier and more efficient.
Instead of implementing a custom QQuickItem
you can just use a trivial ShaderEffect
. You can pass an offset and control the sampling.
Additionally, you would only need one single black and white plus, and you can have the color dynamic by passing it as a parameter to the shader.
Lastly, doing an atlas yourself is likely redundant, as the scenegraph will likely put small textures in atlases anyway. And of course, there is the advantage of not having to write a single line of C++, while still getting an efficient and flexible solution.
来源:https://stackoverflow.com/questions/43172503/rendering-a-section-of-a-non-smooth-qimage-using-qsgimagenode