Escalado de imágenes
Demuestra cómo descargar y escalar imágenes de forma asíncrona.
Este ejemplo muestra cómo utilizar las clases QFuture, QPromise, y QFutureWatcher para descargar una colección de imágenes de la red y escalarlas, sin bloquear la UI.

La aplicación consta de los siguientes pasos
- Descargar imágenes de la lista de URLs especificada por el usuario.
- Escalar las imágenes.
- Mostrar las imágenes escaladas en una cuadrícula.
Empecemos por la descarga:
QFuture<QByteArray> Images::download(const QList<QUrl> &urls) {
El método download() toma una lista de URLs y devuelve un QFuture. El QFuture almacena los datos de la matriz de bytes recibidos para cada imagen descargada. Para almacenar los datos dentro de QFuture, creamos un objeto QPromise e informamos de que ha comenzado para indicar el inicio de la descarga:
QSharedPointer<QPromise<QByteArray>> promise(new QPromise<QByteArray>()); promise->start(); ... return promise->future(); }
El futuro asociado a la promesa se devuelve al llamante.
Sin entrar todavía en detalles, observemos que el objeto promesa está envuelto dentro de un QSharedPointer. Esto se explicará más adelante.
Utilizamos QNetworkAccessManager para enviar peticiones de red y descargar datos para cada url:
for (const auto &url : urls) { QSharedPointer<QNetworkReply> reply(qnam.get(QNetworkRequest(url))); replies.push_back(reply);
Y aquí empieza la parte interesante:
QtFuture::connect(reply.get(), &QNetworkReply::finished).then([=] { if (promise->isCanceled()) { if (!promise->future().isFinished()) promise->finish(); return; } if (reply->error() != QNetworkReply::NoError) { if (!promise->future().isFinished()) throw reply->error(); } promise->addResult(reply->readAll()); // Report finished on the last download if (promise->future().resultCount() == urls.size()) promise->finish(); }).onFailed([promise] (QNetworkReply::NetworkError error) { promise->setException(std::make_exception_ptr(error)); promise->finish(); }).onFailed([promise] { const auto ex = std::make_exception_ptr( std::runtime_error("Unknown error occurred while downloading.")); promise->setException(ex); promise->finish(); }); } ...
En lugar de conectarnos a las señales de QNetworkReply usando el método QObject::connect(), usamos QtFuture::connect(). Funciona de forma similar a QObject::connect(), pero devuelve un objeto QFuture, que pasa a estar disponible en cuanto se emite la señal QNetworkReply::finished(). Esto nos permite adjuntar continuaciones y manejadores de fallos, como se hace en el ejemplo.
En la continuación adjuntada a través de .then(), comprobamos si el usuario ha solicitado cancelar la descarga. Si es así, dejamos de procesar la petición. Llamando al método QPromise::finish(), notificamos al usuario que el procesamiento ha finalizado. En caso de que la petición de red haya terminado con un error, lanzamos una excepción. La excepción será manejada en el manejador de fallas adjunto usando el método .onFailed(). Nótese que tenemos dos manejadores de fallos: el primero captura los errores de red, el segundo todas las demás excepciones lanzadas durante la ejecución. Ambos manejadores guardan la excepción dentro del objeto promesa (para ser manejado por el llamador del método download() ) e informan que el cómputo ha terminado. Nótese también que, por simplicidad, en caso de error interrumpimos todas las descargas pendientes.
Si la petición no se ha cancelado y no se ha producido ningún error, leemos los datos de la respuesta de red y los añadimos a la lista de resultados del objeto promesa:
...
promise->addResult(reply->readAll());
// Report finished on the last download
if (promise->future().resultCount() == urls.size())
promise->finish();
...Si el número de resultados almacenados dentro del objeto promise es igual al número de los urls pendientes de descargar, no hay más peticiones que procesar, por lo que también informamos de que la promise ha finalizado.
Como se mencionó anteriormente, hemos envuelto la promesa dentro de un QSharedPointer. Dado que el objeto promesa se comparte entre los manejadores conectados a cada respuesta de red, necesitamos copiar y utilizar el objeto promesa en múltiples lugares simultáneamente. Por lo tanto, se utiliza un QSharedPointer.
El método download() se llama desde el método Images::process. Se invoca cuando el usuario pulsa el botón "Añadir URL":
...
connect(addUrlsButton, &QPushButton::clicked, this, &Images::process);
...Después de limpiar los posibles restos de la descarga anterior, creamos un cuadro de diálogo para que el usuario pueda especificar las URL de las imágenes que desea descargar. Basándonos en el número de URL especificadas, inicializamos el diseño donde se mostrarán las imágenes e iniciamos la descarga. El futuro devuelto por el método download() se guarda, para que el usuario pueda cancelar la descarga si lo necesita:
void Images::process() { // Clean previous state replies.clear(); addUrlsButton->setEnabled(false); if (downloadDialog->exec() == QDialog::Accepted) { const auto urls = downloadDialog->getUrls(); if (urls.empty()) return; cancelButton->setEnabled(true); initLayout(urls.size()); downloadFuture = download(urls); statusBar->showMessage(tr("Downloading...")); ...
A continuación, adjuntamos una continuación para manejar el paso de escalado. Más adelante hablaremos de ello:
downloadFuture
.then([this](auto) {
cancelButton->setEnabled(false);
updateStatus(tr("Scaling..."));
scalingWatcher.setFuture(QtConcurrent::run(Images::scaled,
downloadFuture.results()));
})
...Después adjuntamos los manejadores onCanceled() y onFailed():
.onCanceled([this] { updateStatus(tr("Download has been canceled.")); }) .onFailed([this](QNetworkReply::NetworkError error) { updateStatus(tr("Download finished with error: %1").arg(error)); // Abort all pending requests abortDownload(); }) .onFailed([this](const std::exception &ex) { updateStatus(tr(ex.what())); }) ...
El manejador adjunto a través del método .onCanceled() será llamado si el usuario ha pulsado el botón "Cancelar":
...
connect(cancelButton, &QPushButton::clicked, this, &Images::cancel);
...El método cancel() simplemente aborta todas las peticiones pendientes:
void Images::cancel() { statusBar->showMessage(tr("Canceling...")); downloadFuture.cancel(); abortDownload(); }
Los manejadores adjuntos a través del método .onFailed() serán llamados en caso de que se haya producido un error durante uno de los pasos anteriores. Por ejemplo, si se ha guardado un error de red dentro de la promesa durante el paso de descarga, se propagará al manejador que toma QNetworkReply::NetworkError como argumento.
Si downloadFuture no se cancela y no informa de ningún error, se ejecuta la continuación del escalado.
Dado que el escalado puede ser computacionalmente pesado, y no queremos bloquear el hilo principal, usamos QtConcurrent::run(), para lanzar el paso de escalado en un nuevo hilo.
scalingWatcher.setFuture(QtConcurrent::run(Images::scaled, downloadFuture.results()));
Dado que el escalado se lanza en un hilo separado, el usuario puede decidir cerrar la aplicación mientras la operación de escalado está en curso. Para manejar estas situaciones con elegancia, pasamos el QFuture devuelto por QtConcurrent::run() a la instancia QFutureWatcher.
La señal QFutureWatcher::finished del observador está conectada a la ranura Images::scaleFinished:
connect(&scalingWatcher, &QFutureWatcher<QList<QImage>>::finished, this, &Images::scaleFinished);
Esta ranura es responsable de mostrar las imágenes escaladas en la interfaz de usuario, y también de manejar los errores que potencialmente podrían ocurrir durante el escalado:
void Images::scaleFinished() { const OptionalImages result = scalingWatcher.result(); if (result.has_value()) { const auto scaled = result.value(); showImages(scaled); updateStatus(tr("Finished")); } else { updateStatus(tr("Failed to extract image data.")); } addUrlsButton->setEnabled(true); }
El informe de errores se implementa devolviendo un opcional desde el método Images::scaled():
Images::OptionalImages Images::scaled(const QList<QByteArray> &data) { QList<QImage> scaled; for (const auto &imgData : data) { QImage image; image.loadFromData(imgData); if (image.isNull()) return std::nullopt; scaled.push_back(image.scaled(100, 100, Qt::KeepAspectRatio)); } return scaled; }
El tipo Images::OptionalImages aquí es simplemente un typedef para std::optional:
using OptionalImages = std::optional<QList<QImage>>;
Nota: No podemos manejar los errores de la operación de escalado asíncrono usando el manejador .onFailed(), porque el manejador necesita ser ejecutado en el contexto del objeto Images en el hilo UI. Si el usuario cierra la aplicación mientras se realiza el cálculo asíncrono, el objeto Images se destruirá, y acceder a sus miembros desde la continuación provocará un fallo. El uso de QFutureWatcher y sus señales nos permite evitar el problema, ya que las señales se desconectan cuando se destruye QFutureWatcher, por lo que las ranuras relacionadas nunca se ejecutarán en un contexto destruido.
El resto del código es sencillo, puedes consultar el proyecto de ejemplo para más detalles.
Ejecutar el ejemplo
Para ejecutar el ejemplo desde Qt Creatorabra el modo Welcome y seleccione el ejemplo de Examples. Para más información, consulta Qt Creator: Tutorial: Construir y ejecutar.
© 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.