使用QSGImageNode渲染非光滑QImage的一部分

时间:2017-04-02 19:17:40

标签: c++ qt qml qtquick2

我试图从图块集渲染单个图块。例如,我想在下面的tileset中显示灰色磁贴:

tileset image

在实际使用案例中,这些将是例如游戏中的水,草等瓷砖。渲染这些图块有一些要求:

  • 它们是32x32像素,将全屏渲染,因此性能非常重要。
  • 缩放时不应为smoothed

据我所知,内置的Qt Quick类型都没有满足这些要求(渲染图像的一部分未经过平滑处理)。我已尝试QQuickPaintedItem QPainter render hints(例如SmoothPixmapTransform设置为false)但没有成功;图像模糊"模糊"放大时。 AnimatedSprite支持渲染图像的各个部分,但没有API来禁用平滑。

我的想法是使用场景图API实现自定义QQuickItem

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"
        }
    }
}

此应用程序的输出看起来很好,但矩形内没有任何内容呈现:

textureSize: QSize(256, 256)
source rect: 32 0 32 32

minimal docs判断,我的实现(就我如何创建节点而言)似乎没问题。我哪里错了?

3 个答案:

答案 0 :(得分:2)

晚会结束一年,但我遇到了和你一样的问题。为了试图继承QQuickItem并且遇到过这个主题的其他人,在updatePaintNode的文档中有一个小小的金块:

  

该函数是QQuickItem::update()的结果,   如果用户在项目上设置了QQuickItem::ItemHasContents标志。

当我设置那个标志时,所有渲染的东西。

我认为自己是一个注重细节的人......

编辑:

在OP指出他们已经设置了ItemHasContents标志之后,我再次查看了代码并看到OP在节点上设置了sourceRect,OP没有&#39; t设置节点的rect,这确实是OP遇到的问题。

答案 1 :(得分:1)

最后我和朋友一起使用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);
}

然后我从以下Component创建切片实例:

Image {
    width: 32
    height: 32
    smooth: false
    asynchronous: true
    source: "image://tile/" + index

    property int index
}

还有Tiled的tile rendering code,它使用场景图节点,可能效率更高。

答案 2 :(得分:0)

看起来您可以更轻松,更高效地使用它。

您可以使用简单的QQuickItem,而不是实现自定义ShaderEffect。您可以传递偏移并控制采样。

此外,您只需要一个黑色和白色加号,并且可以通过将其作为参数传递给着色器来使颜色动态化。

最后,自己做地图册可能是多余的,因为场景图无论如何都可能在地图集上放置小纹理。当然,没有必要编写单行C ++,同时仍然可以获得高效灵活的解决方案。