# Como usar QMovie en Qt

[Qt](/proyecto-qt-framework-de-desarrollo-de-aplicaciones/) trae una clase denominada [QMovie](https://doc.qt.io/qt-5/qmovie.html) que facilita mostrar pequeñas animaciones sin mucho esfuerzo.

[QMovie](https://doc.qt.io/qt-5/qmovie.html) está diseñada para ser independiente del formato de archivo, pero como internamente depende de [QImageReader](https://doc.qt.io/qt-5/qimagereader.html), solo puede utilizarse con los que esta última soporta —véase [QMovie](https://doc.qt.io/qt-5/qmovie.html)::[supportedFormats](https://doc.qt.io/qt-5/qmovie.html#supportedFormats)()— . Esto incluye GIF animados, archivos MNG y MJPEG. Para mostrar vídeo y otros contenidos multimedia, es mejor utilizar el *framework* [Qt Multimedia](https://doc.qt.io/qt-5/qtmultimedia-index.html).

# Primeros pasos

La forma más sencilla de usar [QMovie](https://doc.qt.io/qt-5/qmovie.html) es asignar un objeto [QMovie](https://doc.qt.io/qt-5/qmovie.html) a un control [QLabel](https://doc.qt.io/qt-5/qlabel.html) usando el método [QLabel](https://doc.qt.io/qt-5/qlabel.html)::[setMovie](https://doc.qt.io/qt-5/qlabel.html#setMovie)():

```cpp
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](https://doc.qt.io/qt-5/qmovie.html), 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](https://doc.qt.io/qt-5/qmovie.html) a [QLabel](https://doc.qt.io/qt-5/qlabel.html):

```cpp
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](https://doc.qt.io/qt-5/qmovie.html) el nombre escogido por el usuario mediante el método [QMovie](https://doc.qt.io/qt-5/qmovie.html)::[setFileName](https://doc.qt.io/qt-5/qmovie.html#setFileName)():

```cpp
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](https://doc.qt.io/qt-5/qmovie.html)::[isValid](https://doc.qt.io/qt-5/qmovie.html#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](https://doc.qt.io/qt-5/qmovie.html)::[device](https://doc.qt.io/qt-5/qmovie.html#device)(). Este devuelve el objeto [QFile](https://doc.qt.io/qt-5/qfile.html) —realmente devuelva una instancia de [QIODevice](https://doc.qt.io/qt-5/qiodevice.html), que es la clase base de [QFile](https://doc.qt.io/qt-5/qfile.html) y de todas clases que representan dispositivos de E/S— vinculado con la instancia de [QMovie](https://doc.qt.io/qt-5/qmovie.html). Así podemos comprobar mediante el método [QIODevice](https://doc.qt.io/qt-5/qiodevice.html)::[isOpen](https://doc.qt.io/qt-5/qfile.html#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](https://doc.qt.io/qt-5/qmovie.html)::[start](https://doc.qt.io/qt-5/qmovie.html#start)() y [QMovie](https://doc.qt.io/qt-5/qmovie.html)::[stop](https://doc.qt.io/qt-5/qmovie.html#stop)().

En el ejemplo anterior se puede observar como el *slot* [QMovie](https://doc.qt.io/qt-5/qmovie.html)::[start](https://doc.qt.io/qt-5/qmovie.html#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](/proyecto-qt-framework-de-desarrollo-de-aplicaciones/) 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](https://doc.qt.io/qt-5/qabstractbutton.html#clicked)() al *slot* [QMovie](https://doc.qt.io/qt-5/qmovie.html)::[start](https://doc.qt.io/qt-5/qmovie.html#start)() de la siguiente manera:

```cpp
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](https://doc.qt.io/qt-5/qmovie.html)::[start](https://doc.qt.io/qt-5/qmovie.html#start)() y [QMovie](https://doc.qt.io/qt-5/qmovie.html)::[stop](https://doc.qt.io/qt-5/qmovie.html#stop)() indican a la instancia de [QMovie](https://doc.qt.io/qt-5/qmovie.html) 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](https://doc.qt.io/qt-5/qmovie.html)::[start](https://doc.qt.io/qt-5/qmovie.html#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](https://doc.qt.io/qt-5/qmovie.html#start)():

```cpp
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](/proyecto-qt-framework-de-desarrollo-de-aplicaciones) 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](https://doc.qt.io/qt-5/qmovie.html) 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](https://doc.qt.io/qt-5/qmovie.html) emite una señal [updated](https://doc.qt.io/qt-5/qmovie.html#updated)() cada vez que el *frame* actual cambia.

Para aprovecharlo, declaramos un *slot* para que reciba la señal [QMovie](https://doc.qt.io/qt-5/qmovie.html)::[updated](https://doc.qt.io/qt-5/qmovie.html#updated)():

```cpp
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](https://doc.qt.io/qt-5/qlabel.html). En ese sentido, el método [QLabel](https://doc.qt.io/qt-5/qlabel.html)::[setPixmap](https://doc.qt.io/qt-5/qlabel.html#setPixmap)() permite indicar al objeto [QLabel](https://doc.qt.io/qt-5/qlabel.html) qué imagen queremos mostrar. Mientras que [QMovie](https://doc.qt.io/qt-5/qmovie.html)::[currentPixmap](https://doc.qt.io/qt-5/qmovie.html#currentPixmap)() nos permite obtener el último *frame* del objeto [QMovie](https://doc.qt.io/qt-5/qmovie.html) en formato [QPixmap](https://doc.qt.io/qt-5/qpixmap.html):

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

Suprimimos el uso del método [QLabel](https://doc.qt.io/qt-5/qlabel.html)::[setMovie](https://doc.qt.io/qt-5/qlabel.html#setMovie)(), para que el objeto [QLabel](https://doc.qt.io/qt-5/qlabel.html) no sepa nada de nuestra animación, y conectamos la señal [QMovie](https://doc.qt.io/qt-5/qmovie.html)::[updated](https://doc.qt.io/qt-5/qmovie.html#updated)() con nuestro nuevo *slot*:

```cpp
    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](https://doc.qt.io/qt-5/qmovie.html) — Qt Documentation.
    
2. [Movie Example](http://doc.qt.io/qt-5/qtwidgets-widgets-movie-example.html) — Qt Documentation. 
    
3. [Image Viewer Example](http://doc.qt.io/qt-5/qtwidgets-widgets-imageviewer-example.html) — Qt Documentation.
