画像のサイズ変更
画像を非同期でダウンロードし、リサイズする方法を示します。
この例では、QFuture 、QPromise 、およびQFutureWatcher クラスを使用して、UIをブロックすることなく、ネットワークから一連の画像をダウンロードし、サイズ変更する方法を示します。

このアプリケーションは、以下の手順で構成されています。
- ユーザーが指定したURLのリストから画像をダウンロードします。
- 画像のサイズ変更を行います。
- リサイズされた画像をグリッドレイアウトで表示する。
まずはダウンロードから始めましょう:
QFuture<QByteArray> Images::download(const QList<QUrl> &urls)
{download() メソッドはURLのリストを受け取り、QFuture を返します。QFuture は、ダウンロードされた各画像について受信したバイト配列データを格納します。データをQFuture 内に格納するために、QPromise オブジェクトを作成し、ダウンロードの開始を通知するためにその開始を報告します:
QSharedPointer<QPromise<QByteArray>> promise(new QPromise<QByteArray>());
promise->start();
...
return promise->future();
}このプロミスに関連付けられたフューチャーが呼び出し元に返されます。
詳細についてはまだ触れませんでしたが、promiseオブジェクトがQSharedPointer でラップされていることに注意してください。これについては後で説明します。
QNetworkAccessManager を使用して、各 URL に対してネットワークリクエストを送信し、データをダウンロードします:
for (const auto &url : urls) {
QSharedPointer<QNetworkReply> reply(qnam.get(QNetworkRequest(url)));
replies.push_back(reply);そしてここからが興味深い部分です:
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();
});
}
...QObject::connect() メソッドを使ってQNetworkReply のシグナルに接続する代わりに、QtFuture::connect() を使用します。これはQObject::connect() と同様の動作をしますが、QFuture オブジェクトを返します。このオブジェクトは、QNetworkReply::finished() シグナルが発信されるやいなや利用可能になります。これにより、例で示されているように、継続処理や失敗ハンドラを紐付けることが可能になります。
.then() を通じて接続された継続処理内では、ユーザーがダウンロードの中止を要求したかどうかを確認します。その場合は、リクエストの処理を中止します。QPromise::finish() メソッドを呼び出すことで、処理が完了したことをユーザーに通知します。ネットワークリクエストがエラーで終了した場合は、例外をスローします。 この例外は、.onFailed() メソッドを使用して登録された失敗ハンドラで処理されます。ここで注意すべき点は、2つの失敗ハンドラが存在することです。1つ目はネットワークエラーを捕捉し、2つ目は実行中にスローされたその他のすべての例外を捕捉します。どちらのハンドラも、例外を Promise オブジェクト内に保存し(download() メソッドの呼び出し元によって処理されるため)、計算が完了したことを報告します。 また、簡略化のため、エラーが発生した場合は、保留中のダウンロードをすべて中断することに注意してください。
リクエストがキャンセルされておらず、エラーも発生しなかった場合、ネットワークからの応答からデータを読み取り、それをプロミスオブジェクトの結果リストに追加します:
...
promise->addResult(reply->readAll());
// Report finished on the last download
if (promise->future().resultCount() == urls.size())
promise->finish();
...プロミスオブジェクト内に格納されている結果の数が、ダウンロードすべきurlの数と等しい場合、処理すべきリクエストはこれ以上ないため、プロミスが完了したことも報告します。
前述の通り、プロミスをQSharedPointer でラップしています。プロミスオブジェクトは各ネットワーク応答に接続されたハンドラー間で共有されるため、プロミスオブジェクトを複製し、複数の場所で同時に使用する必要があります。そのため、QSharedPointer が使用されます。
download() メソッドは、Images::process メソッドから呼び出されます。これは、ユーザーが「URLを追加」ボタンを押したときに呼び出されます:
...
connect(addUrlsButton, &QPushButton::clicked, this, &Images::process);
...前回のダウンロードで残っている可能性のあるデータをクリアした後、ユーザーがダウンロードする画像のURLを指定できるダイアログを作成します。指定されたURLの数に基づいて、画像が表示されるレイアウトを初期化し、ダウンロードを開始します。download() メソッドによって返されたFutureは保存されるため、ユーザーは必要に応じてダウンロードをキャンセルできます:
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..."));
...次に、スケーリング処理を処理するための継続をアタッチします。これについては後で詳しく説明します:
downloadFuture
.then([this](auto) {
cancelButton->setEnabled(false);
updateStatus(tr("Scaling..."));
scalingWatcher.setFuture(QtConcurrent::run(Images::scaled,
downloadFuture.results()));
})
...その後、onCanceled() および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()));
})
....onCanceled() メソッドを介して登録されたハンドラは、ユーザーが「キャンセル」ボタンを押した際に呼び出されます:
...
connect(cancelButton, &QPushButton::clicked, this, &Images::cancel);
...cancel() メソッドは、単に保留中のすべてのリクエストを中止します:
void Images::cancel()
{
statusBar->showMessage(tr("Canceling..."));
downloadFuture.cancel();
abortDownload();
}.onFailed() メソッドを介して登録されたハンドラは、前のステップのいずれかでエラーが発生した場合に呼び出されます。たとえば、ダウンロードステップ中にネットワークエラーが Promise 内に保存された場合、そのエラーはQNetworkReply::NetworkError を引数とするハンドラに伝播されます。
downloadFuture がキャンセルされておらず、かつエラーも報告されていない場合、スケーリングの継続処理が実行されます。
スケーリングは計算負荷が高くなる可能性があり、メインスレッドをブロックしたくないため、QtConcurrent::run() を使用して、新しいスレッドでスケーリングステップを起動します。
scalingWatcher.setFuture(QtConcurrent::run(Images::scaled,
downloadFuture.results()));スケーリングは別のスレッドで起動されるため、スケーリング処理の進行中にユーザーがアプリケーションを終了させる可能性があります。このような状況を適切に処理するために、QtConcurrent::run()によって返された `QFuture ` を `QFutureWatcher ` インスタンスに渡します。
ウォッチャーのQFutureWatcher::finished シグナルは、Images::scaleFinished スロットに接続されています:
connect(&scalingWatcher, &QFutureWatcher<QList<QImage>>::finished,
this, &Images::scaleFinished);このスロットは、UIに拡大縮小された画像を表示する役割を担うほか、拡大縮小中に発生しうるエラーの処理も行います:
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);
}エラーの報告は、Images::scaled() メソッドからoptionalを返すことで実装されています:
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;
}ここでのImages::OptionalImages 型は、単にstd::optional のtypedefです:
using OptionalImages = std::optional<QList<QImage>>;注: .onFailed() ハンドラを使用して非同期スケーリング操作からのエラーを処理することはできません 。これは、ハンドラが UI スレッド内のImages オブジェクトのコンテキストで実行される必要があるためです。非同期計算が行われている間にユーザーがアプリケーションを終了した場合、Images オブジェクトは破棄され、継続からそのメンバにアクセスするとクラッシュが発生します。QFutureWatcher とそのシグナルを使用することで、この問題を回避できます。QFutureWatcher が破棄されるとシグナルが切断されるため、関連するスロットが破棄されたコンテキストで実行されることは決してないからです。
残りのコードは単純明快です。詳細については、サンプルプロジェクトをご確認ください。
例の動作確認
サンプルを実行するには、以下の手順に従ってください:
- Qt Creator
「Welcome 」モードを開き、「Examples 」からこの例を選択してください。詳細については、Qt Creator の「チュートリアル:ビルドと実行」を参照してください。
- Qt Extension for Visual Studio Code
Command Palette から「Qt: Open Qt examples 」コマンドを実行し、リストからサンプルを選択します。詳細については、Qt Extension for Visual Studio Code :チュートリアル「ビルドと実行」を参照してください。
© 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.