Como usar QMovie en Qt

Como usar QMovie en Qt

Qt trae una clase denominada QMovie que facilita mostrar pequeñas animaciones sin mucho esfuerzo.

QMovie está diseñada para ser independiente del formato de archivo, pero como internamente depende de QImageReader, solo puede utilizarse con los que esta última soporta —véase QMovie::supportedFormats()— . Esto incluye GIF animados, archivos MNG y MJPEG. Para mostrar vídeo y otros contenidos multimedia, es mejor utilizar el framework Qt Multimedia.

Primeros pasos

La forma más sencilla de usar QMovie es asignar un objeto QMovie a un control QLabel usando el método QLabel::setMovie():

QMovie *movie = new QMovie("video.mjpeg");
ui->label->setMovie(movie);

donde ui es el miembro de la clase que tiene asignada la instancia de la ventana creada previamente con Qt Creator.

Nombre de archivo especificado por el usuario

No siempre ocurre que el nombre del archivo a reproducir se conozca de antemano al desarrollar el programar. Si por ejemplo se pretende que el usuario lo escoja de entre los disponibles en su disco duro, podemos crear un objeto QMovie, guardarlo en un miembro de la clase —manteniendo así un puntero al mismo que nos permita referenciarlo más adelante— y asignar dicho objeto QMovie a QLabel:

MovieViewerWindow::MovieViewerWindow(QWidget *parent)
    : QMainWindow(parent),
      ui(new Ui::MovieViewerWindow)
{
    movie_ = new QMovie();
    ui->label->setMovie(movie_);
}

En el slot de la acción que abre el cuadro de diálogo abrir archivo, asignamos al objeto QMovie el nombre escogido por el usuario mediante el método QMovie::setFileName():

void MovieViewerWindow::on_actionOpen_triggered()
{
    // Aquí el código que abre el cuadro de diálogo y comprueba si
    // el usuario seleccionó algún archivo...
    //
    // QString fileName = QFileDialog::getFileName(...);
    //
    // ...
    //

    movie_->setFileName(fileName);
    if (!movie_->isValid()) {
        QMessageBox::critical(this, tr(“Error”),
            tr("No se pudo abrir el archivo o el formato "
               "es inválido"));
        return;
    }
    movie_->start();    // Iniciar la reproducción de la animación
}

Como se puede observar, es conveniente utilizar el método QMovie::isValid() para comprobar si el archivo pudo ser abierto y tiene uno de los formatos soportados.

Para distinguir entre ambos tipos de error, con el objeto de mostrar al usuario un mensaje diferente según el caso, podemos emplear el método QMovie::device(). Este devuelve el objeto QFile —realmente devuelva una instancia de QIODevice, que es la clase base de QFile y de todas clases que representan dispositivos de E/S— vinculado con la instancia de QMovie. Así podemos comprobar mediante el método QIODevice::isOpen() si el archivo se pudo abrir con éxito o no.

Control de la reproducción

El control de la reproducción se puede hacer mediante los slots QMovie::start() y QMovie::stop().

En el ejemplo anterior se puede observar como el slot QMovie::start() es invocado exactamente de la misma manera que un método convencional para iniciar la reproducción de la animación. Sin embargo, el hecho de que los desarrolladores de Qt lo hayan declarado como un slot y no como un método nos permitiría conectarlo a una señal emitida desde otro control.

Por ejemplo, si tuviéramos un botón de play podríamos conectar su señal clicked() al slot QMovie::start() de la siguiente manera:

connect(ui->playButton, SIGNAL(clicked()), movie_, SLOT(start()));

de forma que al pulsar dicho botón se inicie automáticamente la reproducción.

Otro detalle a tener en cuenta es que los slots QMovie::start() y QMovie::stop() indican a la instancia de QMovie que inicie o detengan la reproducción, pero, una vez hecho, vuelven inmediatamente. Es decir, que no se quedan a la espera de que la animación se reproduzca o esperan a que termine.

Este es un detalle importante porque al slot on_actionOpen_triggered() de nuestro ejemplo se llega a través del bucle de mensajes, cuando el sistema de ventanas notifica a la aplicación un click sobre la acción correspondiente. Si en el slot introdujéramos tareas de larga duración, la ejecución tardaría en volver al bucle de mensajes, retrasando el momento en el que la aplicación puede procesar nuevos eventos de los usuarios. Es decir, que si QMovie::start() se quedara a la espera y añadiéramos un botón para detener la reproducción, este nunca funcionaría porque la aplicación no volvería al bucle de mensajes hasta que la reproducción no hubiera terminado.

Podemos comprobar esto añadiendo una espera justo después de invocar el slot start():

QWaitCondition sleep;
QMutex mutex;
sleep.wait(&mutex, 2000);    // Espera de 2 segundos

Debido a los efectos desastrosos que este tipo de esperas tienen en las aplicaciones dirigidas por eventos, Qt no incluye funciones del tipo de sleep(), delay(), usleep() y nanosleep(), que muchos sistemas operativos sí soportan.

Procesando la imagen frame a frame

Aunque QMovie se hace cargo de mostrar la animación sin que tengamos que intervenir de ninguna otra manera, en ocasiones puede ser interesante tener acceso a los frames de manera individualizada para poder procesarlos antes de que sean mostrados. Por ese motivo, QMovie emite una señal updated() cada vez que el frame actual cambia.

Para aprovecharlo, declaramos un slot para que reciba la señal QMovie::updated():

private slots:
    // Otros slots...
    //
    // void on_actionOpen_triggered();
    //
    // ...
    //
    void showFrame(const QRect& rect);

Definimos el código del slot para que al ser invocado actualice la imagen mostrada por el control QLabel. En ese sentido, el método QLabel::setPixmap() permite indicar al objeto QLabel qué imagen queremos mostrar. Mientras que QMovie::currentPixmap() nos permite obtener el último frame del objeto QMovie en formato QPixmap:

void MovieViewerWindow::showFrame(const QRect& rect)
{
    QPixmap pixmap = movie_->currentPixmap();
    ui->label->setPixmap(pixmap);
}

Suprimimos el uso del método QLabel::setMovie(), para que el objeto QLabel no sepa nada de nuestra animación, y conectamos la señal QMovie::updated() con nuestro nuevo slot:

    movie_ = new QMovie();
    // ui->label->setMovie(movie_);
    connect(movie_, SIGNAL(updated(const QRect&)),
    this, SLOT(showFrame(const QRect&)));
}

Ahora podríamos introducir en el slot todo aquello que nos interese hacer sobre los frames antes de mostrarlos.

Referencias

  1. QMovie — Qt Documentation.

  2. Movie Example — Qt Documentation.

  3. Image Viewer Example — Qt Documentation.