Dibujo de vtkButtonWidget en QVTKOpenGLNativeWidget

Este artículo explora cómo combinar Qt y VTK (Visualization Toolkit) para desarrollar interfaces gráficas de usuario (GUI), mediante un caso concreto que detalla cómo incrustar un renderizador de VTK en una aplicación Qt y crear elementos gráficos interactivos, como botones, para lograr una experiencia de usuario más enriquecedora. También se analiza en profundidad cómo gestionar los eventos de clic de los botones y cómo convertir datos de imagen de Qt al formato de VTK.

Introducción

En el desarrollo de software moderno, el diseño e implementación de interfaces gráficas de usuario (GUI) es una parte fundamental. Qt, como framework de aplicaciones multipaltaforma popular, ofrece un amplio conjunto de herramientas y funcionalidades para el desarrollo de GUI. Por su parte, VTK es un potente kit de herramientas de visualización utilizado para crear gráficos 3D complejos y visualización de datos. Este artículo presenta cómo integrar Qt y VTK, aprovechando las ventajas de cada uno, para desarrollar interfaces gráficas de usuario ricas en funcionalidades e interactividad.

Integración de Qt con VTK

Qt y VTK pueden integrarse de manera fluida, lo que permite implementar potentes interfaces gráficas y funciones de visualización de datos. En el código de ejemplo de este artículo, primero creamos una aplicación Qt y utilizamos la clase QVTKOpenGLNativeWidget para crear una ventana de renderizado de VTK. De esta forma, es posible incrustar el renderizador de VTK dentro de la aplicación Qt, logrando la combinación de interfaz gráfica y visualización de datos.

int main(int argc, char** argv)
{
    QApplication app(argc, argv);

    QVTKOpenGLNativeWidget vtkWidget;

    // Crear el renderizador
    vtkSmartPointer<vtkRenderer> renderer = vtkSmartPointer<vtkRenderer>::New();
    auto renderWindow = vtkWidget.renderWindow();
    renderWindow->AddRenderer(renderer);
    ...
    vtkWidget.show();
    ...
}

Creación de un botón interactivo

En nuestro ejemplo, utilizamos la clase vtkButtonWidget para crear un botón interactivo. Este botón puede responder a los eventos de clic del usuario y ejecutar la acción correspondiente. Para mostrar una imagen en el botón, empleamos la clase vtkTexturedButtonRepresentation2D y establecemos la textura del botón con los datos de imagen. De esta manera, podemos crear botones interactivos dentro de la interfaz gráfica, ofreciendo al usuario una experiencia de operación más amigable.

// Crear la representación 2D del botón
vtkSmartPointer<vtkTexturedButtonRepresentation2D> buttonRep2D = vtkSmartPointer<vtkTexturedButtonRepresentation2D>::New();
buttonRep2D->SetPlaceFactor(1);
buttonRep2D->SetNumberOfStates(2);

vtkSmartPointer<vtkPNGReader> pngReaderOff = vtkSmartPointer<vtkPNGReader>::New();
QString tempPathOff = QStandardPaths::writableLocation(QStandardPaths::TempLocation) + "/poweroff.png";
QFile::copy(":/resources/poweroff.png", tempPathOff);
pngReaderOff->SetFileName(tempPathOff.toStdString().c_str());
pngReaderOff->Update();
buttonRep2D->SetButtonTexture(0, pngReaderOff->GetOutput());

QImage imgPowerOn = QImage(":/resources/poweron.png");
buttonRep2D->SetButtonTexture(1, QImageToVtkImageData(imgPowerOn));

// Crear el widget de botón y asignarle la representación
vtkSmartPointer<vtkButtonWidget> buttonWidget = vtkSmartPointer<vtkButtonWidget>::New();
buttonWidget->SetInteractor(vtkWidget.interactor());
buttonWidget->SetRepresentation(buttonRep2D);
//buttonWidget->SetEnabled(1);
buttonWidget->On();

// Crear el objeto de comando para gestionar el evento de clic del botón
vtkSmartPointer<ButtonCallback> callback = vtkSmartPointer<ButtonCallback>::New();
buttonWidget->AddObserver(vtkCommand::StateChangedEvent, callback);

Gestión de eventos de clic del botón

Para gestionar los eventos de clic del botón, creamos una clase personalizada derivada de vtkCommand y sobrescribimos su método Execute. Dentro de este método, verificamos el tipo de evento y realizamos la operación correspondiente según sea necesario. De este modo, podemos lograr un manejo flexible de los eventos de clic del botón y así implementar funcionalidades interacitvas más complejas.

// Clase de comando para gestionar el evento de clic del botón
class ButtonCallback : public vtkCommand {
public:
    static ButtonCallback* New()
    {
        auto instance = new ButtonCallback();
        instance->InitializeObjectBase();
        return instance;
    }

    virtual void Execute(vtkObject* caller, unsigned long eventId, void* callData) override 
    {
        // Verificar el tipo de evento
        if (eventId == vtkCommand::StateChangedEvent)
        {
            // Procesar el evento de cambio de estado del botón
            vtkButtonWidget* buttonWidget = reinterpret_cast<vtkButtonWidget*>(caller);
            qDebug() << vtkCommand::GetStringFromEventId(eventId);
            vtkButtonRepresentation* representation = reinterpret_cast<vtkButtonRepresentation*>(buttonWidget->GetRepresentation());
            qDebug() << "vtkButtonWidget State: " << representation->GetState();
        }
    }
};

Por supuesto, también es posible añadir elementos de interfaz de Qt; este artículo se centra en presentar vtkWidget.

Conversión de datos de imagen

Al transferir datos de imagen entre Qt y VTK, es necesario realizar una conversión de formato. En nuestro ejemplo, demostramos cómo convertir un objeto QImage de Qt a un objeto vtkImageData de VTK. De esta manera, podemos transferir eficientemente datos de imagen entre la aplicación Qt y el renderizador de VTK, logrando funcionalidades de visualización de datos más enriquecedoras.

/// @brief Convierte QImage a vtkImageData
/// @param src 
/// @return 
vtkSmartPointer<vtkImageData> QImageToVtkImageData(const QImage& src)
{
    vtkSmartPointer<vtkImageData> dst = vtkSmartPointer<vtkImageData>::New();
    int w = src.width();
    int h = src.height();
    // Establecer las dimensiones
    dst->SetDimensions(w, h, 1);
    // Establecer el tipo de dato y el número de componentes
    vtkNew<vtkInformation> info;
    dst->SetScalarType(VTK_UNSIGNED_CHAR, info);
    int channels = src.pixelFormat().channelCount();
    dst->SetNumberOfScalarComponents(channels, info);
    // Asignar memoria
    dst->AllocateScalars(info);
    // Obtener el puntero inicial a los datos de la imagen
    unsigned char* dstPtr = static_cast<unsigned char*>(dst->GetScalarPointer());
    
    // Recorrer los píxeles de la imagen y copiarlos a vtkImageData
    for (int y = 0; y < h; y++)
    {
        for (int x = 0; x < w; x++)
        {
            // Obtener el valor del píxel
            QRgb pixel = src.pixel(x, y);
            // El orden de las filas en QImage es de arriba hacia abajo,
            // mientras que en vtkImageData es de abajo hacia arriba
            switch (channels)
            {
            case 1:
                dstPtr[(h - y - 1) * w + x] = qGray(pixel);
                break;
            case 3:
                dstPtr[((h - y - 1) * w + x) * 3 + 0] = qRed(pixel);
                dstPtr[((h - y - 1) * w + x) * 3 + 1] = qGreen(pixel);
                dstPtr[((h - y - 1) * w + x) * 3 + 2] = qBlue(pixel);
                break;
            case 4:
                dstPtr[((h - y - 1) * w + x) * 4 + 0] = qRed(pixel);
                dstPtr[((h - y - 1) * w + x) * 4 + 1] = qGreen(pixel);
                dstPtr[((h - y - 1) * w + x) * 4 + 2] = qBlue(pixel);
                dstPtr[((h - y - 1) * w + x) * 4 + 3] = qAlpha(pixel);
                break;
            }
        }
    }

    return dst;
}

Código cmopleto

#include <QApplication>
#include <QVTKOpenGLNativeWidget.h>
#include <QFile>
#include <QStandardPaths>
#include <QImage>
#include <QString>
#include <QColorSpace>
#include <QDebug>

#include <vtkActor.h>
#include <vtkProperty.h>
#include <vtkSmartPointer.h>
#include <vtkRenderer.h>
#include <vtkRenderWindow.h>
#include <vtkButtonWidget.h>
#include <vtkTexturedButtonRepresentation.h>
#include <vtkTexturedButtonRepresentation2D.h>
#include <vtkImageData.h>
#include <vtkPNGReader.h>
#include <vtkRenderWindowInteractor.h>
#include <vtkCommand.h>
#include <vtkNew.h>
#include <vtkInformation.h>

// Clase de comando para gestionar el evento de clic del botón
class ButtonCallback : public vtkCommand {
public:
    static ButtonCallback* New()
    {
        auto instance = new ButtonCallback();
        instance->InitializeObjectBase();
        return instance;
    }

    virtual void Execute(vtkObject* caller, unsigned long eventId, void* callData) override 
    {
        // Verificar el tipo de evento
        if (eventId == vtkCommand::StateChangedEvent)
        {
            // Procesar el evento de cambio de estado del botón
            vtkButtonWidget* buttonWidget = reinterpret_cast<vtkButtonWidget*>(caller);
            qDebug() << vtkCommand::GetStringFromEventId(eventId);
            vtkButtonRepresentation* representation = reinterpret_cast<vtkButtonRepresentation*>(buttonWidget->GetRepresentation());
            qDebug() << "vtkButtonWidget State: " << representation->GetState();
        }
    }
};

/// @brief Convierte QImage a vtkImageData
/// @param src 
/// @return 
vtkSmartPointer<vtkImageData> QImageToVtkImageData(const QImage& src)
{
    vtkSmartPointer<vtkImageData> dst = vtkSmartPointer<vtkImageData>::New();
    int w = src.width();
    int h = src.height();
    // Establecer las dimensiones
    dst->SetDimensions(w, h, 1);
    // Establecer el tipo de dato y el número de componentes
    vtkNew<vtkInformation> info;
    dst->SetScalarType(VTK_UNSIGNED_CHAR, info);
    int channels = src.pixelFormat().channelCount();
    dst->SetNumberOfScalarComponents(channels, info);
    // Asignar memoria
    dst->AllocateScalars(info);
    // Obtener el puntero inicial a los datos de la imagen
    unsigned char* dstPtr = static_cast<unsigned char*>(dst->GetScalarPointer());
    
    // Recorrer los píxeles de la imagen y copiarlos a vtkImageData
    for (int y = 0; y < h; y++)
    {
        for (int x = 0; x < w; x++)
        {
            // Obtener el valor del píxel
            QRgb pixel = src.pixel(x, y);
            // El orden de las filas en QImage es de arriba hacia abajo,
            // mientras que en vtkImageData es de abajo hacia arriba
            switch (channels)
            {
            case 1:
                dstPtr[(h - y - 1) * w + x] = qGray(pixel);
                break;
            case 3:
                dstPtr[((h - y - 1) * w + x) * 3 + 0] = qRed(pixel);
                dstPtr[((h - y - 1) * w + x) * 3 + 1] = qGreen(pixel);
                dstPtr[((h - y - 1) * w + x) * 3 + 2] = qBlue(pixel);
                break;
            case 4:
                dstPtr[((h - y - 1) * w + x) * 4 + 0] = qRed(pixel);
                dstPtr[((h - y - 1) * w + x) * 4 + 1] = qGreen(pixel);
                dstPtr[((h - y - 1) * w + x) * 4 + 2] = qBlue(pixel);
                dstPtr[((h - y - 1) * w + x) * 4 + 3] = qAlpha(pixel);
                break;
            }
        }
    }

    return dst;
}

int main(int argc, char** argv)
{
    QApplication app(argc, argv);

    QVTKOpenGLNativeWidget vtkWidget;

    // Crear el renderizador
    vtkSmartPointer<vtkRenderer> renderer = vtkSmartPointer<vtkRenderer>::New();
    auto renderWindow = vtkWidget.renderWindow();
    renderWindow->AddRenderer(renderer);

    // Crear la representación 2D del botón
    vtkSmartPointer<vtkTexturedButtonRepresentation2D> buttonRep2D = vtkSmartPointer<vtkTexturedButtonRepresentation2D>::New();
    buttonRep2D->SetPlaceFactor(1);
    buttonRep2D->SetNumberOfStates(2);

    vtkSmartPointer<vtkPNGReader> pngReaderOff = vtkSmartPointer<vtkPNGReader>::New();
    QString tempPathOff = QStandardPaths::writableLocation(QStandardPaths::TempLocation) + "/poweroff.png";
    QFile::copy(":/resources/poweroff.png", tempPathOff);
    pngReaderOff->SetFileName(tempPathOff.toStdString().c_str());
    pngReaderOff->Update();
    buttonRep2D->SetButtonTexture(0, pngReaderOff->GetOutput());

    QImage imgPowerOn = QImage(":/resources/poweron.png");
    buttonRep2D->SetButtonTexture(1, QImageToVtkImageData(imgPowerOn));

    // Crear el widget de botón y asignarle la representación
    vtkSmartPointer<vtkButtonWidget> buttonWidget = vtkSmartPointer<vtkButtonWidget>::New();
    buttonWidget->SetInteractor(vtkWidget.interactor());
    buttonWidget->SetRepresentation(buttonRep2D);
    //buttonWidget->SetEnabled(1);
    buttonWidget->On();

    // Crear el objeto de comando para gestionar el evento de clic del botón
    vtkSmartPointer<ButtonCallback> callback = vtkSmartPointer<ButtonCallback>::New();
    buttonWidget->AddObserver(vtkCommand::StateChangedEvent, callback);

    vtkWidget.show();

    int code = app.exec();
    QFile::remove(tempPathOff);
    //QFile::remove(tempPathOn);
    return code;
}

A través de este artículo, el lector puede aprender a combinar Qt y VTK para desarrollar interfaces gráficas de usuario ricas en funcionalidades e interactividad. Hemos detallado cómo incrustar un renderizador de VTK en una aplicación Qt y crear botones interactivos. Asimismo, hemos analizado en profundidad cómo gestionar los eventos de clic de los botones y cómo realizar la conversión de formato de datos de imagen. Esperamos que este artículo sea de utilidad y permita a los lectores aprovechar de manera más flexible Qt y VTK en el desarrollo de interfaces gráficas y visualización de datos.

Etiquetas: QVTKOpenGLNativeWidget vtkButtonWidget Qt VTK vtkImageData

Publicado el 8-31 10:00