ネットワークからの同期および非同期のイメージダウンロードとその後の処理

すべてに幸運を!



今日は、ネットワークからの同期および非同期のイメージダウンロードの方法の1つについてお話します。 記事が退屈しないように、Qtを使用して何らかの方法でアップロードされた画像を処理しようとします。





どうやってアップロードしますか?



イメージをダウンロードするには、 QNetworkAccessManager



QEventLoop



を使用し、いくつかのメタオブジェクトも使用します。 サポートされているQtから任意の形式の画像をHTTP経由でアップロードします。 まあ、私たちはまだリダイレクトを処理しています。



どのように処理しますか?



サブクラスを持つ素晴らしいQGraphicsEffectクラスがあります。 ただし、この記事のフレームワークではそれらと連携しません。 そしてその理由も説明します。 たとえば、Qt 4.8.0では、これらの影響により、Mac OS X 10.7でアプリケーションがクラッシュします。+、および同じシステム上のQt 4.7.4では、まったく機能しません。 どのように発生したのかわかりませんが、Qtバグトラッカーにバグを設定しました。



そのため、画像処理用の独自のクラスを作成します。 彼は次のことができるようになります。



テストプロジェクトの完全なコードは、記事の最後にあるgithubのリンクからダウンロードできます。



画像をロードする



そもそも、何を望むかを決めます。 しかし、これが必要です。特定のクラスの特定のメソッドを呼び出し、画像のURLを渡すだけでなく、結果の画像をどのオブジェクトに、どのメソッドで渡すかを指定します。 そして、イメージがロードされると、クラスは目的のオブジェクトの目的のメソッドを呼び出して、ダウンロードしたイメージをそれに渡す必要があります。 そして、これはすべて非同期です。 いいですね?



原因に! Networking



クラスを作成し(静的にしましたが、これは大きな役割を果たしません)、 NetworkingPrivate



クラスを作成します-実際の作業のために。



 // networking.h class Networking { public: static QImage httpGetImage(const QUrl& src); static void httpGetImageAsync(const QUrl& src, QObject * receiver, const char * slot); private: static NetworkingPrivate * networkingPrivate; static void init(); } // networking_p.h class NetworkingPrivate : public QObject { Q_OBJECT public: NetworkingPrivate(); ~NetworkingPrivate(); QImage httpGetImage(const QUrl& src) const; void httpGetImageAsync(const QUrl& src, QObject * receiver, const char * slot); public slots: void onFinished(QNetworkReply* reply); private: QNetworkAccessManager * nam; QEventLoop * loop; QMap<QNetworkReply*, QPair<QObject*, QPair<QUrl, const char *> > > requests; };
      
      





実際、このクラスは、写真を同期的および非同期的にロードできます。 だから選択肢があります。



使用例:



 // myclass.h class MyClass: public QObject { // ... public slots: void loadImage(const QString & urlString); void onImageReady(const QUrl& url, const QImage & image); } // myclass.cpp void MyClass::loadImage(const QString & urlString) { Networking::httpGetImageAsync(QUrl(urlString), this, "onImageRead"); }
      
      





乗り越えられないプライベートクラスについて少し説明します。 httpリクエストを送信するためにQNetworkAccessManager



、同期リクエストの場合に応答を待つためにQEventLoop



、およびすべてのリクエストを保存するためにこのホラー QMap<QNetworkReply*, QPair<QObject*, QPair<QUrl, const char *> > > requests



リクエスト、ロード後にどのオブジェクトにどの画像を配信するかを知っている。



ここで最も興味深い部分は、プライベートクラスの関数の実装です(ごNetworking



Networking



クラスは呼び出しをプライベートクラスにリダイレクトするだけです)。



 NetworkingPrivate::NetworkingPrivate() { nam = new QNetworkAccessManager(); loop = new QEventLoop(); connect(nam, SIGNAL(finished(QNetworkReply*)), loop, SLOT(quit())); connect(nam, SIGNAL(finished(QNetworkReply*)), SLOT(onFinished(QNetworkReply*))); } NetworkingPrivate::~NetworkingPrivate() { nam->deleteLater(); loop->deleteLater(); } QImage NetworkingPrivate::httpGetImage(const QUrl& src) const { QNetworkRequest request; request.setUrl(src); QNetworkReply * reply = nam->get(request); loop->exec(); QVariant redirectedUrl = reply->attribute(QNetworkRequest::RedirectionTargetAttribute); QUrl redirectedTo = redirectedUrl.toUrl(); if (redirectedTo.isValid()) { // guard from infinite redirect loop if (redirectedTo != reply->request().url()) { return httpGetImage(redirectedTo); } else { qWarning() << "[NetworkingPrivate] Infinite redirect loop at " + redirectedTo.toString(); return QImage(); } } else { QImage img; QImageReader reader(reply); if (reply->error() == QNetworkReply::NoError) reader.read(&img); else qWarning() << QString("[NetworkingPrivate] Reply error: %1").arg(reply->error()); reply->deleteLater(); return img; } } void NetworkingPrivate::httpGetImageAsync(const QUrl& src, QObject * receiver, const char * slot) { QNetworkRequest request; request.setUrl(src); QPair<QObject*, QPair<QUrl, const char *> > obj; obj.first = receiver; obj.second.first = src; obj.second.second = slot; QNetworkReply * reply = nam->get(request); requests.insert(reply, obj); } void NetworkingPrivate::onFinished(QNetworkReply* reply) { if (requests.contains(reply)) { QPair<QObject*, QPair<QUrl, const char *> > obj = requests.value(reply); QVariant redirectedUrl = reply->attribute(QNetworkRequest::RedirectionTargetAttribute); QUrl redirectedTo = redirectedUrl.toUrl(); if (redirectedTo.isValid()) { // guard from infinite redirect loop if (redirectedTo != reply->request().url()) { httpGetImageAsync(redirectedTo, obj.first, obj.second.second); } else { qWarning() << "[NetworkingPrivate] Infinite redirect loop at " + redirectedTo.toString(); } } else { QImage img; QImageReader reader(reply); if (reply->error() == QNetworkReply::NoError) reader.read(&img); else qWarning() << QString("[NetworkingPrivate] Reply error: %1").arg(reply->error()); if (obj.first && obj.second.second) QMetaObject::invokeMethod(obj.first, obj.second.second, Qt::DirectConnection, Q_ARG(QUrl, obj.second.first), Q_ARG(QImage, img)); } requests.remove(reply); reply->deleteLater(); } }
      
      





これらの機能を分析します。 コンストラクターで、 QEventLoop



QNetworkAccessManager



を作成し、 QEventLoop::quit()



onFinished



メソッドでリクエストを完了するためにシグナルを接続します。



同期ダウンロードの場合、リクエストを実行し、ダウンロードの最後に完了する予定のイベントループを実行します。 同時に、ユーザーがリンク短縮サービスを通過したものを含む写真へのリンクを入力できるように、リダイレクトとその固定をまだ確認しています。



さて、画像を取得しQImageReader



QImageReader



を実行し、データを読み込んで最終的なQImage



に戻します。



非同期ロードでは、すべてがより複雑になります。 リクエスト(より正確には、レスポンスリンク)、ターゲットオブジェクト、およびそのメソッドを怖いQMap



から、リクエストを実行します。 そして、リクエストの最後に、同期リクエストと同じことをすべて行います(リダイレクト、その周期性をチェックし、画像を読み取ります)が、 QMetaObject::invokeMethod



を使用して、受信したQImage



をターゲットにQMetaObject::invokeMethod



ます。 パラメーターとして-要求URLと画像。



簡単なフォームを作成してURLを入力し、ボタンをクリックしてネットワークから画像を取得できます。 同期または非同期。 そして喜ぶ。



しかし、さらに先に進むと、結果のイメージが変わります。



画像処理のクラス



別のクラスを作成します(理由はまったくありませんが、再び静的にしています)。これをImageManager



と呼びましょう。 そして、次のメソッドが含まれます。



 class ImageManager { public: static QImage normallyResized(const QImage & image, int maximumSideSize); static QImage grayscaled(const QImage & image); static QImage squared(const QImage & image, int size); static QImage roundSquared(const QImage & image, int size, int radius); static QImage addShadow(const QImage & image, QColor color, QPoint offset, bool canResize = false); static QImage colorized(const QImage & image, QColor color); static QImage opacitized(const QImage & image, double opacity = 0.5); static QImage addSpace(const QImage & image, int left, int top, int right, int bottom); static QImage rotatedImage(const QImage & image, qreal angle); static QColor resolveColor(const QString & name); };
      
      





これにより、次のようなものを取得できます(記事の最後にあるテストプロジェクトを参照)。







順番に行きましょう。 最初の方法は最も面白くなく、画像サイズを最大側に正規化します。 3番目の方法もあまりおもしろくありません-画像を正方形にします(サイズを調整し、余分な部分をトリミングします)。 記事にはソースコードも含めません。



さらに興味深いことになります。



グレースケール。



私はこれを行うための2つの方法さえ見つけましたが、私は速度のために両方をテストすることに煩わされていません。 だから、私は2つから選択します。



最初の方法は、画像をQImage :: Format_Indexed8に変換することです。これは、画像をインデックス可能な8ビットカラーに変換することを意味します。 これを行うには、白から黒までの256要素の「カラーマップ」を作成します。



 QImage gray(image.size(), QImage::Format_ARGB32); gray.fill(Qt::transparent); static QVector<QRgb> monoTable; if (monoTable.isEmpty()) { for (int i = 0; i <= 255; i++) monoTable.append(qRgb(i, i, i)); } QPainter p(&gray); p.drawImage(0, 0, image.convertToFormat(QImage::Format_Indexed8, monoTable)); p.end(); return gray;
      
      





2番目の方法は、画像ビットを使用した直接作業に基づいています。 すべてのピクセルを調べて、グレーに設定します。



 QImage img = image; if (!image.isNull()) { int pixels = img.width() * img.height(); if (pixels*(int)sizeof(QRgb) <= img.byteCount()) { QRgb *data = (QRgb *)img.bits(); for (int i = 0; i < pixels; i++) { int val = qGray(data[i]); data[i] = qRgba(val, val, val, qAlpha(data[i])); } } } return img;
      
      





私の意見では、追加のイメージは作成されないため、2番目の方法はより高速に動作するはずです。 さらに、透明度のある画像にも適しています。これも非常に優れています。 それが彼がフィナーレで使用される理由です。



角を丸くする



このアルゴリズムは非常に興味深いものです。 私の最初の考えは、マスクを作成し、その上で画像をトリミングすることでした。 しかし、QPainter :: draw [Ellipse | Arc | RoundedRect | Path]を使用してこのマスクを正しく描画しようとして長い間失敗した後、私はこの考えを捨てました。 何らかの理由で、このアプローチはいくつかのフィレット半径に対してのみ良い結果をもたらします。 さらに、結果はオペレーティングシステムによって異なる場合があり、この方法も尊重されません。 これは、明らかに、ビットマスクのアンチエイリアシングができないためです。黒と白の2色しか使用できません。 新しい方法はこれらの問題を笑い、アンチエイリアスを使用して滑らかなフィレットの形で追加のバンを提供します。



 QImage shapeImg(QSize(size, size), QImage::Format_ARGB32_Premultiplied); shapeImg.fill(Qt::transparent); QPainter sp(&shapeImg); sp.setRenderHint(QPainter::Antialiasing); sp.setPen(QPen(Qt::color1)); sp.setBrush(QBrush(Qt::color1)); sp.drawRoundedRect(QRect(0, 0, size, size), radius + 1, radius + 1); sp.end(); QImage roundSquaredImage(size, size, QImage::Format_ARGB32_Premultiplied); roundSquaredImage.fill(Qt::transparent); QPainter p(&roundSquaredImage); p.drawImage(0, 0, shapeImg); p.setCompositionMode(QPainter::CompositionMode_SourceIn); p.drawImage(0, 0, squared(image, size)); p.end(); return roundSquaredImage;
      
      





本質は、画像をマスクするのとほとんど同じです。 丸みを帯びた黒い正方形を作成し(アンチエイリアスを使用)、その上に構成モードQPainter::CompositionMode_SourceIn



して元の画像を描画します。 彼らが言うように、シンプルで上品です。



影を追加



次に、画像に影を追加してみます。 さらに、透明性を考慮します。 もちろん、結果の画像は元の画像とは異なる寸法になる場合があります。



 QSize shadowedSize = image.size(); if (canResize) { shadowedSize += QSize(qAbs(offset.x()), qAbs(offset.y())); } QImage shadowed(shadowedSize, QImage::Format_ARGB32_Premultiplied); shadowed.fill(Qt::transparent); QPainter p(&shadowed); QImage shadowImage(image.size(), QImage::Format_ARGB32_Premultiplied); shadowImage.fill(Qt::transparent); QPainter tmpPainter(&shadowImage); tmpPainter.setCompositionMode(QPainter::CompositionMode_Source); tmpPainter.drawPixmap(QPoint(0, 0), QPixmap::fromImage(image)); tmpPainter.setCompositionMode(QPainter::CompositionMode_SourceIn); tmpPainter.fillRect(shadowImage.rect(), color); tmpPainter.end(); QPoint shadowOffset = offset; if (canResize) { if (offset.x() < 0) shadowOffset.setX(0); if (offset.y() < 0) shadowOffset.setY(0); } p.drawImage(shadowOffset, shadowImage); QPoint originalOffset(0, 0); if (canResize) { if (offset.x() < 0) originalOffset.setX(qAbs(offset.x())); if (offset.y() < 0) originalOffset.setY(qAbs(offset.y())); } p.drawPixmap(originalOffset, QPixmap::fromImage(image)); p.end(); return shadowed;
      
      





ここでは、最初にさまざまな構成モードで巧妙な描画を使用して影画像を作成し、次にそれと元の画像を上に描画します。 もちろん、必要なシフトがあります。



色付け



色付けの効果を実現するには、さまざまな方法があります。 私の意見では、最も成功したものを選びました。



 QImage resultImage(image.size(), QImage::Format_ARGB32_Premultiplied); resultImage.fill(Qt::transparent); QPainter painter(&resultImage); painter.drawImage(0, 0, grayscaled(image)); painter.setCompositionMode(QPainter::CompositionMode_Screen); painter.fillRect(resultImage.rect(), color); painter.end(); resultImage.setAlphaChannel(image.alphaChannel()); return resultImage;
      
      





ここでは、元の画像をグレーの濃淡で描画し(既にわかっています)、画面合成モードで目的の色の四角形をオーバーレイします。 そして、アルファチャンネルについて忘れないでください。



透明度の変更



次に、画像を透明にします。 QPainter::setOpacity



を使用すると非常に簡単QPainter::setOpacity







 QImage resultImage(image.size(), QImage::Format_ARGB32); resultImage.fill(Qt::transparent); QPainter painter(&resultImage); painter.setOpacity(opacity); painter.drawImage(0, 0, image); painter.end(); resultImage.setAlphaChannel(image.alphaChannel()); return resultImage;
      
      







画像を回転させる



中心を中心に回転します。 任意のポイントを中心とした回転の実装は、宿題として読者に任せます。 ここのすべても非常に簡単です-主なことは、スムーズな変換を忘れないことです。



 QImage rotated(image.size(), QImage::Format_ARGB32_Premultiplied); rotated.fill(Qt::transparent); QPainter p(&rotated); p.setRenderHint(QPainter::Antialiasing); p.setRenderHint(QPainter::SmoothPixmapTransform); qreal dx = image.size().width() / 2.0, dy = image.size().height() / 2.0; p.translate(dx, dy); p.rotate(angle); p.translate(-dx, -dy); p.drawImage(0, 0, image); p.end(); return rotated;
      
      





グランドファイナル



これで、テストプログラムを作成(またはGitHub 'aからダウンロード)して、結果を楽しむことができます!



ボーナスとして、文字列値から色をより便利に読み取るための小さな関数を提供します。 何らかの理由で、Qtは#RRGGBBAA



形式の色を理解しません。

 QColor ImageManager::resolveColor(const QString & name) { QColor color; if (QColor::isValidColor(name)) color.setNamedColor(name); else { // trying to parse "#RRGGBBAA" color if (name.length() == 9) { QString solidColor = name.left(7); if (QColor::isValidColor(solidColor)) { color.setNamedColor(solidColor); int alpha = name.right(2).toInt(0, 16); color.setAlpha(alpha); } } } if (!color.isValid()) qWarning() << QString("[ImageManager::resolveColor] Can\'t parse color: %1").arg(name); return color; }
      
      





同時に、すべての標準色( white



transparent



#ffa0ee



)も完全に理解されます。



PS: githubのサンプルコードを調べる価値があるかどうか疑問に思う人のために、ここに数行を残します。 まず、この記事のコードは少し単純化されています-いくつかの便利なチェックなどが削除されました。 次に、完全な例では、要求時にCookieの受信と保存/使用を使用します。 第三に、9つの部分(9つの部分からなる画像)で構成される画像を描画するための追加機能があります。これにより、ボタン、入力フィールド、およびその他の類似物の手動レンダリングを簡素化できます。 パンが提供されます!



PPS:検討されたすべてのタスクを実行するためのより成功したアルゴリズムを誰かが知っている場合、コメントでそれらを表現することを歓迎します! 同じことが他の画像処理方法にも当てはまります。喜んで読みました。



All Articles