QQuickImageProvider - get image from the network



  • Hi,
    I need to show images downloaded from the network, but I can't just set the image URL to Image element, because the image needs to be requested with certain cookies from the server. So it seems that I have to subclass QQuickImageProvider to fetch image the way the server requires it. The problem is that I can't return the image data immediately from my reimplemented QQuickImageProvider::requestImage() because data is returned in a slot connected to QNetworkRequest::finished(). In requestImage() I can only send the HTTP request and connect QNetworkRequest::finished() to a slot. I don't have the image data at the time when requestImage() returns.

    In other words, QQuickImageProvider's API is synchronous, but Qt network API is asynchronous. Any ideas?


  • Lifetime Qt Champion

    Hi,

    You can use something like:

    QEventLoop loop;
    connect(reply, SIGNAL(finished()), &loop, SLOT(quit()));
    loop.exec();
    // process request and return image
    

    WARNING This currently doesn't take into account that there might be timeout or other network problems so you have to adapt that code for these situations.

    Hope it helps



  • Hello SGaist,

    thanks for your reply. It works! Unfortunately, it crashes my application. Here is my code:

    QImage EbookImageProvider::requestImage(const QString &id, QSize *size, const QSize &requestedSize)
    {
    	QNetworkRequest request{QUrl(id)};
    	request.setRawHeader("Cookie", QString("Csob=" + CServerCommunicator::sessionID()).toUtf8());
    	QNetworkReply *reply = CServerCommunicator::netManager()->get(request);
    	QEventLoop loop;
    	QObject::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
    	loop.exec();
    
    	if(reply->error() != QNetworkReply::NoError) {
    		reply->deleteLater();
    		*size = m_defaultImage.size();
    		return m_defaultImage;
    	}
    
    	QByteArray data = reply->readAll();
    	reply->deleteLater();
    	reply = nullptr;
    
    	QImage returnImage;
    	if(returnImage.loadFromData(data)) {
    		*size = returnImage.size();
    		return returnImage;
    	}
    	*size = m_defaultImage.size();
    	return m_defaultImage;
    }
    

    I'm saving a default image to m_defaultImage in EbookImageProvider's constructor. I get the following output at the beginning (can be unrelated to image provider):
    QEventLoop: Cannot be used without QApplication
    QDBusConnection: system D-Bus connection created before QCoreApplication. Application may misbehave.
    And when images are shown:
    QObject: Cannot create children for a parent that is in a different thread.
    (Parent is QNetworkAccessManager(0xc76c50), parent's thread is QThread(0xc761a0), current thread is QQuickPixmapReader(0x25a5520)
    When I scroll the list view with images and reach the end of the list, application crashes. This happens on Desktop 64-bit and Android, in Qt 5.4.2. It's interesting that Images in listview are shown all at once, not one by one.
    Any ideas?

    UPDATE: I modified the code to create QNetworkAccessManager directly in requestImage(). This eliminated "different thread" warnings, but the application still crashes. But maybe less frequently.



  • @m_andrej

    Hi,

    We had a similar problem. We solved it as follows:

    • Make worker thread to do the fetching.
    • In requestImage() use a QSemaphore to block the main thread (yes I know)
    • When the fetching thread finishes, unlock the QSemaphore and processing continues as normal

    Hope this helps,



  • @t3685
    Thanks for the suggestion. Does this mean that the application UI will freeze while main thread is blocked?



  • @m_andrej

    Yes, it will block. But I believe the solution using the event loop will also block. If you don't want to UI to block, you can give an option to QImageProvider to use a separate thread

    http://doc.qt.io/qt-5/qqmlimageproviderbase.html#Flag-enum


  • Lifetime Qt Champion

    @m_andrej Do you have a QApplication or a QGuiApplication ?

    @t3685 Interesting flag, I've missed that one



  • @SGaist I'm creating a QApplication in main() - I left this code as generated by Qt Creator.

    But I think I found a better solution altogether - I'm going to ditch QQuickImageProvider and subclass QQuickPaintedItem instead. I will call QQuickPaintedItem::update() when network reply finishes.



  • @m_andrej

    There is an altogether different solution too to your original objective ie,

    "because the image needs to be requested with certain cookies from the server. 
    So it seems that I have to subclass QQuickImageProvider  ....."
    

    in involves using QQmlNetworkAccessManagerFactory . I was able to solve a similar
    problem that involved using QNetworkDiskCache . Initially i had tried the provider
    approach but the subclassing was much simpler and stable ( read no more application
    crashes)

    i am posting a relevant part of the code.

    
    #include "customnamfactory.h"
    
    #include <QNetworkDiskCache>
    #include <QStandardPaths>
    
    CustomNAMFactory::CustomNAMFactory()
    {
    
    }
    
    
    QNetworkAccessManager *CustomNAMFactory::create(QObject *parent)
    {
        QNetworkAccessManager *nam = new QNetworkAccessManager(parent);
        QNetworkDiskCache *diskCache = new QNetworkDiskCache ;
        QString cacheDir = QStandardPaths::writableLocation(QStandardPaths::CacheLocation);
        diskCache->setCacheDirectory(cacheDir);
        nam->setCache(diskCache);
    
        return nam;
    }
    
    
    
    engine_ptr->setNetworkAccessManagerFactory(new CustomNAMFactory);
    

    checkout http://doc.qt.io/qt-5/qtqml-networkaccessmanagerfactory-example.html
    for a complete example.

    regds
    mallah.


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.