【问题标题】:Rendering a section of a non-smooth QImage using QSGImageNode使用 QSGImageNode 渲染非平滑 QImage 的一部分
【发布时间】:2017-04-02 19:17:40
【问题描述】:

我正在尝试从图块集中渲染单个图块。比如我想在下面的tileset中显示灰色的tile:

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

  • 它们是 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 来看,我的实现(就我如何创建节点而言)似乎还可以。我哪里错了?

【问题讨论】:

    标签: c++ qt qml qtquick2


    【解决方案1】:

    聚会晚了一年,但我遇到了和你一样的问题。为了其他任何试图继承 QQuickItem 并遇到此线程的人,文档中有一些关于 updatePaintNode 的小块:

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

    当我设置那个标志时,一切都被渲染了。

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

    编辑:

    在 OP 指出他们已经设置了 ItemHasContents 标志后,我再次查看代码,发现虽然 OP 在节点上设置了 sourceRect,但 OP 没有设置 rect节点的问题,这确实是 OP 遇到的问题。

    【讨论】:

    • 我的代码已经调用了该函数,所以很遗憾,这不是问题的答案。
    • 你是对的——看……我不是那么注重细节。啊。但是我确实再次查看了您的代码,我发现的一个区别是,当您设置节点的 sourceRect 时,您没有设置节点的矩形。我给QSGImageNode::setRect() 打了电话,我删除了,但没有任何消息。也许这就是你所缺少的?
    • 很好,调用imageNode-&gt;setRect(0, 0, 32, 32); 修复它。 :) 你应该在你的答案中包含这个。
    • 我一定会添加它——我想确保它/实际上/首先解决了您的问题。
    【解决方案2】:

    我最终接受了朋友的想法,使用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,它使用场景图节点并且可能更高效。

    【讨论】:

      【解决方案3】:

      看起来您确实可以更轻松,更高效。

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

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

      最后,自己制作图集可能是多余的,因为场景图可能会在图集中放置小纹理。当然,这样做的好处是不必编写一行 C++,同时仍然可以获得高效灵活的解决方案。

      【讨论】:

      • 该死,我忘了提另外一点,就是这些图块是 32x32 像素,会填满整个全屏窗口。我曾考虑使用着色器,但担心它会如何执行。这在实践中会成为问题吗?颜色和加号图标是示例;实际的瓷砖是例如草,水等。有趣的是,您应该提到地图集 - 我之前使用的是单个图块,它工作正常,但我切换到了一个图块集,以便更容易在我正在使用的位图编辑器中编辑图块。
      • 无论如何,您最终都会为每个像素执行一个片段着色器。这就是opengl的工作原理。因此,仅着色器的解决方案不可能更糟。自己在地图集中排列图像可能会有所帮助,因为您最终可能会比场景图做得更好。除非遇到 VRAM 使用问题,否则我不会打扰。您仍然可以将其保留为单个图像以便于编辑,并使用平铺偏移编写一个简单的脚本,将其分割成单个图像。
      • 顺便说一句,对于本质上是位图(只有 2 个颜色值)的东西,您可以期望它在没有采样的情况下放大时看起来很清晰,但对于“草、水等”,我怀疑它会是漂亮的。您可能想研究有符号距离场。
      • 嗯,关于脚本的要点。那可以很好地工作。平滑属性可以满足我的需要;请参阅:stackoverflow.com/q/23579857/904422 顺便说一句,您能否提供一个可以执行此操作的着色器示例?
      • 基本上是老好“近邻”,通过floor()-ing 默认采样器值实现:csantosbh.wordpress.com/tag/nearest-neighbor-sampling
      猜你喜欢
      • 2013-12-13
      • 1970-01-01
      • 1970-01-01
      • 2014-03-21
      • 1970-01-01
      • 1970-01-01
      • 2011-12-07
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多