Gráfico de escena - Geometría personalizada
Muestra cómo implementar una geometría personalizada en el gráfico de escena Qt Quick.
El ejemplo de geometría personalizada muestra cómo crear un QQuickItem que utilice la API del gráfico de escena para construir una geometría personalizada para el gráfico de escena. Para ello, crea un elemento BezierCurve, que pasa a formar parte del módulo CustomGeometry y lo utiliza en un archivo QML.

Declaración BezierCurve
#include <QtQuick/QQuickItem> class BezierCurve : public QQuickItem { Q_OBJECT Q_PROPERTY(QPointF p1 READ p1 WRITE setP1 NOTIFY p1Changed) Q_PROPERTY(QPointF p2 READ p2 WRITE setP2 NOTIFY p2Changed) Q_PROPERTY(QPointF p3 READ p3 WRITE setP3 NOTIFY p3Changed) Q_PROPERTY(QPointF p4 READ p4 WRITE setP4 NOTIFY p4Changed) Q_PROPERTY(int segmentCount READ segmentCount WRITE setSegmentCount NOTIFY segmentCountChanged) QML_ELEMENT public: BezierCurve(QQuickItem *parent = nullptr); ~BezierCurve(); QSGNode *updatePaintNode(QSGNode *, UpdatePaintNodeData *) override; QPointF p1() const { return m_p1; } QPointF p2() const { return m_p2; } QPointF p3() const { return m_p3; } QPointF p4() const { return m_p4; } int segmentCount() const { return m_segmentCount; } void setP1(const QPointF &p); void setP2(const QPointF &p); void setP3(const QPointF &p); void setP4(const QPointF &p); void setSegmentCount(int count); signals: void p1Changed(const QPointF &p); void p2Changed(const QPointF &p); void p3Changed(const QPointF &p); void p4Changed(const QPointF &p); void segmentCountChanged(int count); private: QPointF m_p1; QPointF m_p2; QPointF m_p3; QPointF m_p4; int m_segmentCount; };
La declaración del ítem subclasea la clase QQuickItem y añade cinco propiedades. Una para cada uno de los cuatro puntos de control de la curva bezier y un parámetro para controlar el número de segmentos en que se subdivide la curva. Para cada una de las propiedades tenemos las correspondientes funciones getter y setter. Dado que estas propiedades se pueden enlazar en QML, también es preferible tener señales notificadoras para cada una de ellas, de modo que los cambios sean recogidos por el motor QML y utilizados en consecuencia.
El punto de sincronización entre la escena QML y el gráfico de escena de renderizado es la función virtual QQuickItem::updatePaintNode() que todos los elementos con lógica de gráfico de escena personalizada deben implementar.
Nota: En muchas configuraciones de hardware, el gráfico de escena se renderizará en un subproceso independiente. Por lo tanto, es crucial que la interacción con el gráfico de escena se produzca de forma controlada, en primer lugar a través de la función QQuickItem::updatePaintNode().
Implementación de BezierCurve
BezierCurve::BezierCurve(QQuickItem *parent) : QQuickItem(parent) , m_p1(0, 0) , m_p2(1, 0) , m_p3(0, 1) , m_p4(1, 1) , m_segmentCount(32) { setFlag(ItemHasContents, true); }
El constructor BezierCurve establece valores por defecto para los puntos de control y el número de segmentos. La curva de Bézier se especifica en coordenadas normalizadas relativas al rectángulo delimitador del elemento.
El constructor también establece la bandera QQuickItem::ItemHasContents. Esta bandera indica al lienzo que este elemento proporciona contenido visual y llamará a QQuickItem::updatePaintNode() cuando llegue el momento de que la escena QML se sincronice con el gráfico de la escena de renderizado.
BezierCurve::~BezierCurve() = default;
La clase BezierCurve no tiene miembros de datos que necesiten ser limpiados por lo que el destructor no hace nada. Vale la pena mencionar que el gráfico de escena de renderizado es gestionado por el propio gráfico de escena, potencialmente en un hilo diferente, por lo que nunca se deben retener referencias QSGNode en la clase QQuickItem ni tratar de limpiarlas explícitamente.
void BezierCurve::setP1(const QPointF &p) { if (p == m_p1) return; m_p1 = p; emit p1Changed(p); update(); }
La función setter para la propiedad p1 comprueba si el valor no ha cambiado y sale antes si es así. A continuación, actualiza el valor interno y emite la señal de cambio. A continuación, procede a llamar a la función QQuickItem::update() que notificará al gráfico de la escena de renderizado, que el estado de este objeto ha cambiado y necesita ser sincronizado con el gráfico de la escena de renderizado. Una llamada a update() resultará en una llamada a QQuickItem::updatePaintNode() en un momento posterior.
Los otros definidores de propiedades son equivalentes y se omiten en este ejemplo.
QSGNode *BezierCurve::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *) { QSGGeometryNode *node = nullptr; QSGGeometry *geometry = nullptr; if (!oldNode) { node = new QSGGeometryNode;
La función updatePaintNode() es el principal punto de integración para sincronizar el estado de la escena QML con el gráfico de escena de renderizado. A la función se le pasa un QSGNode que es la instancia que se devolvió en la última llamada a la función. Será nula la primera vez que se llame a la función y crearemos nuestro QSGGeometryNode que llenaremos con geometría y un material.
geometry = new QSGGeometry(QSGGeometry::defaultAttributes_Point2D(), m_segmentCount); geometry->setLineWidth(2); geometry->setDrawingMode(QSGGeometry::DrawLineStrip); node->setGeometry(geometry); node->setFlag(QSGNode::OwnsGeometry);
A continuación creamos la geometría y la añadimos al nodo. El primer argumento del constructor QSGGeometry es una definición del tipo de vértice, llamado "conjunto de atributos". Dado que los gráficos que se utilizan a menudo en QML se centran en unos pocos conjuntos de atributos estándar comunes, éstos se proporcionan por defecto. Aquí utilizamos el conjunto de atributos Point2D que tiene dos valores flotantes, uno para las coordenadas x y otro para las coordenadas y. El segundo argumento es el número de vértices.
También se pueden crear conjuntos de atributos personalizados, pero esto no se trata en este ejemplo.
Como no tenemos ninguna necesidad especial de gestionar la memoria de la geometría, especificamos que la QSGGeometryNode debe ser la propietaria de la geometría.
Para minimizar las asignaciones, reducir la fragmentación de memoria y mejorar el rendimiento, también sería posible hacer de la geometría un miembro de una subclase de QSGGeometryNode, en cuyo caso, no habríamos establecido el flag QSGGeometryNode::OwnsGeometry.
auto *material = new QSGFlatColorMaterial; material->setColor(QColor(255, 0, 0)); node->setMaterial(material); node->setFlag(QSGNode::OwnsMaterial);
La API de scene graph proporciona algunas implementaciones de materiales de uso común. En este ejemplo utilizamos QSGFlatColorMaterial, que rellenará la forma definida por la geometría con un color sólido. De nuevo pasamos la propiedad del material al nodo, para que pueda ser limpiado por el grafo de escena.
} else { node = static_cast<QSGGeometryNode *>(oldNode); geometry = node->geometry(); geometry->allocate(m_segmentCount); }
En el caso de que el elemento QML haya cambiado y sólo queramos modificar la geometría del nodo existente, convertimos oldNode en una instancia de QSGGeometryNode y extraemos su geometría. En caso de que el número de segmentos haya cambiado, llamamos a QSGGeometry::allocate() para asegurarnos de que tiene el número correcto de vértices.
QSizeF itemSize = size(); QSGGeometry::Point2D *vertices = geometry->vertexDataAsPoint2D(); for (int i = 0; i < m_segmentCount; ++i) { qreal t = i / qreal(m_segmentCount - 1); qreal invt = 1 - t; QPointF pos = invt * invt * invt * m_p1 + 3 * invt * invt * t * m_p2 + 3 * invt * t * t * m_p3 + t * t * t * m_p4; float x = pos.x() * itemSize.width(); float y = pos.y() * itemSize.height(); vertices[i].set(x, y); } node->markDirty(QSGNode::DirtyGeometry);
Para rellenar la geometría primero extraemos la matriz de vértices. Dado que estamos utilizando uno de los conjuntos de atributos por defecto, podemos utilizar la función de conveniencia QSGGeometry::vertexDataAsPoint2D(). Luego pasamos por cada segmento y calculamos su posición y escribimos ese valor en el vértice.
return node;
}Al final de la función devolvemos el nodo para que el gráfico de la escena pueda renderizarlo.
Punto de entrada de la aplicación
int main(int argc, char **argv) { QGuiApplication app(argc, argv); QQuickView view; QSurfaceFormat format = view.format(); format.setSamples(16); view.setFormat(format); view.setSource(QUrl("qrc:///scenegraph/customgeometry/main.qml")); view.show(); return app.exec(); }
La aplicación es una aplicación QML directa, con un QGuiApplication y un QQuickView al que pasamos un fichero .qml.
QML_ELEMENT
Para hacer uso del elemento BezierCurve, necesitamos registrarlo en el motor QML, usando la macro QML_ELEMENT. Esto le da el nombre BezierCurve y lo convierte en parte del módulo CustomGeometry 1.0 definido en los archivos de compilación del proyecto:
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
cmake_minimum_required(VERSION 3.16)
project(customgeometry_declarative LANGUAGES CXX)
find_package(Qt6 REQUIRED COMPONENTS Core Gui Quick)
qt_standard_project_setup()
qt_add_executable(customgeometry_declarative WIN32 MACOSX_BUNDLE
beziercurve.cpp beziercurve.h
main.cpp
)
target_link_libraries(customgeometry_declarative PRIVATE
Qt6::Core
Qt6::Gui
Qt6::Quick
)
qt_add_qml_module(customgeometry_declarative
URI CustomGeometry
QML_FILES main.qml
RESOURCE_PREFIX /scenegraph/customgeometry
NO_RESOURCE_TARGET_PATH
)
install(TARGETS customgeometry_declarative
BUNDLE DESTINATION .
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
)
qt_generate_deploy_qml_app_script(
TARGET customgeometry_declarative
OUTPUT_SCRIPT deploy_script
MACOS_BUNDLE_POST_BUILD
NO_UNSUPPORTED_PLATFORM_ERROR
DEPLOY_USER_QML_MODULES_ON_UNSUPPORTED_PLATFORM
)
install(SCRIPT ${deploy_script})TARGET = customgeometry
QT += quick
CONFIG += qmltypes
QML_IMPORT_NAME = CustomGeometry
QML_IMPORT_MAJOR_VERSION = 1
SOURCES += \
main.cpp \
beziercurve.cpp
HEADERS += \
beziercurve.h
RESOURCES += customgeometry.qrc
target.path = $$[QT_INSTALL_EXAMPLES]/quick/scenegraph/customgeometry
INSTALLS += targetComo la curva bezier se dibuja como tiras de línea, especificamos que la vista debe ser multimuestreada para obtener antialiasing. Esto no es necesario, pero hará que el elemento se vea un poco mejor en hardware que lo soporte. El multimuestreo no está activado por defecto porque a menudo resulta en un mayor uso de memoria.
Utilización del elemento
import QtQuick import CustomGeometry
Nuestro archivo .qml importa el módulo QtQuick 2.0 para obtener los tipos estándar y también nuestro propio módulo CustomGeometry 1.0 que contiene nuestros objetos BezierCurve recién creados.
Item { width: 300 height: 200 BezierCurve { id: line anchors.fill: parent anchors.margins: 20
A continuación, creamos nuestro elemento raíz y una instancia de BezierCurve que anclamos para rellenar la raíz.
property real t SequentialAnimation on t { NumberAnimation { to: 1; duration: 2000; easing.type: Easing.InOutQuad } NumberAnimation { to: 0; duration: 2000; easing.type: Easing.InOutQuad } loops: Animation.Infinite } p2: Qt.point(t, 1 - t) p3: Qt.point(1 - t, t) }
Para hacer el ejemplo un poco más interesante añadimos una animación para cambiar los dos puntos de control de la curva. Los puntos finales no cambian.
Text { anchors.bottom: line.bottom x: 20 width: parent.width - 40 wrapMode: Text.WordWrap text: qsTr("This curve is a custom scene graph item, implemented using line strips") } }
Por último superponemos un breve texto explicando lo que muestra el ejemplo.
© 2026 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.