Mise à l'échelle des images
Démontre comment télécharger et mettre à l'échelle des images de manière asynchrone.
Cet exemple montre comment utiliser les classes QFuture, QPromise, et QFutureWatcher pour télécharger une collection d'images depuis le réseau et les mettre à l'échelle, sans bloquer l'interface utilisateur.

L'application comprend les étapes suivantes
- Téléchargement d'images à partir de la liste d'URL spécifiée par l'utilisateur.
- Mettre les images à l'échelle.
- Afficher les images mises à l'échelle dans une grille.
Commençons par le téléchargement :
QFuture<QByteArray> Images::download(const QList<QUrl> &urls) {
La méthode download() prend une liste d'URL et renvoie un QFuture. Le QFuture stocke les données du tableau d'octets reçu pour chaque image téléchargée. Pour stocker les données dans l'objet QFuture, nous créons un objet QPromise et signalons qu'il a démarré pour indiquer le début du téléchargement :
QSharedPointer<QPromise<QByteArray>> promise(new QPromise<QByteArray>()); promise->start(); ... return promise->future(); }
Le futur associé à la promesse est renvoyé à l'appelant.
Sans entrer dans les détails, notons que l'objet promesse est enveloppé dans un objet QSharedPointer, ce qui sera expliqué plus tard.
Nous utilisons QNetworkAccessManager pour envoyer des requêtes réseau et télécharger des données pour chaque url :
for (const auto &url : urls) { QSharedPointer<QNetworkReply> reply(qnam.get(QNetworkRequest(url))); replies.push_back(reply);
Et c'est là que commence la partie intéressante :
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(); }); } ...
Au lieu de nous connecter aux signaux de QNetworkReply à l'aide de la méthode QObject::connect(), nous utilisons QtFuture::connect(). Son fonctionnement est similaire à celui de QObject::connect(), mais il renvoie un objet QFuture, qui devient disponible dès que le signal QNetworkReply::finished() est émis. Cela nous permet d'attacher des continuations et des gestionnaires d'échec, comme cela est fait dans l'exemple.
Dans la suite attachée via .then(), nous vérifions si l'utilisateur a demandé à annuler le téléchargement. Si c'est le cas, nous arrêtons de traiter la demande. En appelant la méthode QPromise::finish(), nous informons l'utilisateur que le traitement est terminé. Si la demande de réseau s'est terminée par une erreur, nous lançons une exception. L'exception sera traitée dans le gestionnaire d'échec attaché à l'aide de la méthode .onFailed(). Notez que nous avons deux gestionnaires d'échec : le premier capture les erreurs de réseau, le second toutes les autres exceptions lancées au cours de l'exécution. Les deux gestionnaires enregistrent l'exception dans l'objet promesse (qui sera traité par l'appelant de la méthode download() ) et signalent que le calcul est terminé. Notez également que, pour des raisons de simplicité, en cas d'erreur, nous interrompons tous les téléchargements en cours.
Si la demande n'a pas été annulée et qu'aucune erreur ne s'est produite, nous lisons les données de la réponse du réseau et les ajoutons à la liste des résultats de l'objet promesse :
...
promise->addResult(reply->readAll());
// Report finished on the last download
if (promise->future().resultCount() == urls.size())
promise->finish();
...Si le nombre de résultats stockés dans l'objet promesse est égal au nombre de urlà télécharger, il n'y a plus de demandes à traiter, et nous signalons donc également que la promesse est terminée.
Comme indiqué précédemment, nous avons enveloppé la promesse dans un QSharedPointer. Étant donné que l'objet promesse est partagé entre les gestionnaires connectés à chaque réponse réseau, nous devons copier et utiliser l'objet promesse à plusieurs endroits simultanément. C'est pourquoi nous utilisons un QSharedPointer.
La méthode download() est appelée à partir de la méthode Images::process. Elle est invoquée lorsque l'utilisateur appuie sur le bouton "Ajouter des URL":
...
connect(addUrlsButton, &QPushButton::clicked, this, &Images::process);
...Après avoir éliminé les éventuels restes du téléchargement précédent, nous créons une boîte de dialogue permettant à l'utilisateur de spécifier les URL des images à télécharger. En fonction du nombre d'URL spécifié, nous initialisons la mise en page dans laquelle les images seront affichées et nous lançons le téléchargement. Le futur renvoyé par la méthode download() est sauvegardé, de sorte que l'utilisateur puisse annuler le téléchargement si nécessaire :
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...")); ...
Ensuite, nous attachons une continuation pour gérer l'étape de mise à l'échelle. Nous y reviendrons plus tard :
downloadFuture
.then([this](auto) {
cancelButton->setEnabled(false);
updateStatus(tr("Scaling..."));
scalingWatcher.setFuture(QtConcurrent::run(Images::scaled,
downloadFuture.results()));
})
...Ensuite, nous attachons les gestionnaires onCanceled() et 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())); }) ...
Le gestionnaire attaché à la méthode .onCanceled() sera appelé si l'utilisateur a appuyé sur le bouton "Annuler":
...
connect(cancelButton, &QPushButton::clicked, this, &Images::cancel);
...La méthode cancel() annule simplement toutes les demandes en attente :
void Images::cancel() { statusBar->showMessage(tr("Canceling...")); downloadFuture.cancel(); abortDownload(); }
Les gestionnaires attachés à la méthode .onFailed() seront appelés si une erreur s'est produite au cours de l'une des étapes précédentes. Par exemple, si une erreur de réseau a été enregistrée dans la promesse pendant l'étape de téléchargement, elle sera transmise au gestionnaire qui prend QNetworkReply::NetworkError comme argument.
Si downloadFuture n'est pas annulé et n'a pas signalé d'erreur, la suite de la mise à l'échelle est exécutée.
Comme la mise à l'échelle peut être lourde en termes de calcul, et que nous ne voulons pas bloquer le thread principal, nous utilisons QtConcurrent::run(), pour lancer l'étape de mise à l'échelle dans un nouveau thread.
scalingWatcher.setFuture(QtConcurrent::run(Images::scaled, downloadFuture.results()));
Puisque la mise à l'échelle est lancée dans un thread séparé, l'utilisateur peut potentiellement décider de fermer l'application pendant que l'opération de mise à l'échelle est en cours. Pour gérer de telles situations avec élégance, nous passons le QFuture renvoyé par QtConcurrent::run() à l'instance QFutureWatcher.
Le signal QFutureWatcher::finished de l'observateur est connecté au slot Images::scaleFinished:
connect(&scalingWatcher, &QFutureWatcher<QList<QImage>>::finished, this, &Images::scaleFinished);
Ce slot est responsable de l'affichage des images mises à l'échelle dans l'interface utilisateur, ainsi que de la gestion des erreurs qui pourraient survenir lors de la mise à l'échelle :
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); }
Le rapport d'erreur est mis en œuvre en renvoyant une option de la méthode 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; }
Le type Images::OptionalImages ici est simplement un typedef pour std::optional:
using OptionalImages = std::optional<QList<QImage>>;
Remarque : nous ne pouvons pas gérer les erreurs de l'opération de mise à l'échelle asynchrone à l'aide du gestionnaire .onFailed(), car celui-ci doit être exécuté dans le contexte de l'objet Images dans le fil d'exécution de l'interface utilisateur. Si l'utilisateur ferme l'application pendant que le calcul asynchrone est effectué, l'objet Images sera détruit et l'accès à ses membres à partir de la suite entraînera un plantage. L'utilisation de QFutureWatcher et de ses signaux nous permet d'éviter ce problème, car les signaux sont déconnectés lorsque l'objet QFutureWatcher est détruit, de sorte que les slots correspondants ne seront jamais exécutés dans un contexte détruit.
Le reste du code est simple, vous pouvez consulter le projet d'exemple pour plus de détails.
Exécution de l'exemple
Pour exécuter l'exemple à partir de Qt Creatorouvrez le mode Welcome et sélectionnez l'exemple à partir de Examples. Pour plus d'informations, voir Qt Creator: Tutorial : Construire et exécuter.
© 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.