Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

Send image inside Json through QTcpSocket



  • Hello everyone.

    I have a local client-server application where data in sent through QTcpSockets. At a certain point, the client asks for a photo, and the server must send it to the client.

    Server

    Setup:

    QByteArray info="";  // Data to be send
    QJsonObject qjo;
    QDataStream ds(socket);
    

    The server loads the image from the filesystem and fills the related QByteArray ba:

    QImage imageObject;
    imageObject.load(Path/to/image);
    QPixmap image = QPixmap::fromImage(imageObject);
    
    QByteArray ba;              // Construct a QByteArray object
    QBuffer buffer(&ba);        // Construct a QBuffer object using the QbyteArray
    image.save(&buffer, "JPG"); // Save the QImage data into the QBuffer
    

    For my application, the server should send not only the image, but also a code meaning that this is the "case" of a photo request:

    qjo.insert("request_type", PHOTO);  /* PHOTO (=13) is defined in a .h */
    

    Now, I would like to do the same thing with ba:

    qjo.insert("photo_data", [some way to put ba]);  // I can't put ba directly...
    

    because the final step is this one:

    QJsonDocument doc(qjo);
    QString strJson(doc.toJson(QJsonDocument::Compact));
    info.append(strJson);
    ds << info;
    

    I've tried this:

    qjo.insert("photo_data", ba.data());
    /* ba.data() = "\xEF\xBF\xBD\xEF\xBF\xBD\xEF\xBF\xBD\xEF\xBF\xBD" */
    

    Client
    It reads all the data correctly. Then, when it extracts photo_data, this doesn't seem to work:

    QJsonValue photo = obj.take("photo");
    /* photo = QJsonValue(string, "????") */
    

    So, how can I send the QByteArray from server to client properly?


  • Lifetime Qt Champion

    e.g. by converting the QByteArray to base64 encoding.



  • @Christian-Ehrlicher
    Hi, i tried:

    qjo.insert("photo", ba.toBase64());
    

    but the IDE says:

    no viable conversion from 'QByteArray' to 'const QJsonValue'
    

  • Lifetime Qt Champion

    @Tamfub said in Send image inside Json through QTcpSocket:

    but the IDE says:

    The compiler maybe, but not the IDE...

    You have to convert the QByteArray to a QString.



  • @Christian-Ehrlicher
    Yes sorry, I meant the compiler. By the way, I tried both

    qjo.insert("photo", QString::fromStdString(ba.toBase64().toStdString()));
    

    and

    qjo.insert("photo", QString::fromUtf8(data));
    

    but the result in the client is:

    QJsonValue photo = obj.take("photo");
    qDebug() << photo.toString();  // Output: "????"
    

    And data sent by the server for photo is "\xEF\xBF\xBD\xEF\xBF\xBD\xEF\xBF\xBD\xEF\xBF\xBD" again.



  • @Tamfub said in Send image inside Json through QTcpSocket:

    qjo.insert("photo", QString::fromStdString(ba.toBase64().toStdString()));

    Have you tried this qjo.insert("photo", QString(ba.toBase64())); ?

    And then:

    QJsonValue photo = obj.take("photo");
    qDebug() << photo.toString();  
    
    

  • Lifetime Qt Champion

    Then you do something wrong:

    QJsonObject obj;
    obj["photo"] = QString::fromLatin1(QByteArray("\x01\x02").toBase64());
    qDebug() << obj["photo"].toString();  // outputs the base64 string 'AQI='
    

    QByteArray::toHex() can also be used but is larger.



  • @KroMignon
    Yes, I tried and this is the result:

    Server

    qjo.insert("photo", QString(ba.toBase64()));
    qDebug() << QString(ba.toBase64));
    // No output...
    

    Client

    QJsonValue photo = obj.take("photo");
    qDebug() << photo.toString();
    // No output...
    


  • @Tamfub said in Send image inside Json through QTcpSocket:

    qjo.insert("photo", QString(ba.toBase64()));

    Sorry, my fault ==> qjo.insert("photo", QString::fromLatin1(ba.toBase64())); (as @Christian-Ehrlicher already wrote)



  • @Christian-Ehrlicher
    I tried these:

    Server

    QJsonObject obj;
    obj["photo"] = QString::fromLatin1(ba.toBase64());
    qjo.insert("photo", obj["photo"].toString());
    qDebug() << obj["photo"].toString();
    // "AQI="
    

    Client

    QJsonValue photo = obj.take("photo");
    qDebug() << photo.toString();
    // "AQI="
    

    /********************/

    Server

    qjo.insert("photo", QString::fromLatin1(ba.toHex()));
    qDebug() << QString::fromLatin1(ba.toHex());
    // No output...
    

    Client

    QJsonValue photo = obj.take("photo");
    qDebug() << photo.toString();
    // No output...
    

    /********************/

    Server

    qjo.insert("photo", QString::fromLatin1(ba.toBase64());
    qDebug() << QString::fromLatin1(ba.toBase64());
    // No output...
    

    Client

    QJsonValue photo = obj.take("photo");
    qDebug() << photo.toString();
    // No output...
    

  • Lifetime Qt Champion

    Because you unpack your json structure wrong I would guess. In the first example you pack it twice as you can see.



  • @Christian-Ehrlicher
    Ok, I removed the qjo.insert("photo", obj["photo"].toString()); line but it still won't work.

    By the way, I noticed something in my client. This is the part where it receives data from the server:

    QByteArray dataReceived;
    QDataStream ds(sock);
    while(sock->bytesAvailable()){
        ds.startTransaction();
        ds >> dataReceived;
    }
    ds.commitTransaction();
    
    while(sock->bytesAvailable()){
        // Start the transaction
        ds.startTransaction();
        // Read data
        ds >> dataReceived;
        // Error checking
        if ((!ds.commitTransaction()) && (ds.status()!=QDataStream::Ok)){
             sock->flush();
             qDebug() << "Error!";
             return;
        }
    }
    
    QString string = (QString)dataReceived;
    QJsonDocument doc = QJsonDocument::fromJson(string.toUtf8());
    QJsonObject obj = doc.object();
    
    ...
    
    QJsonValue photo = obj.take("photo");
    qDebug() << photo.toString();
    
    

    Then the output, in all the examples, is:

    Error!
    Error!
    Error!
    Error!
    Error!
    Error!
    

    and it reaches the part where it reads the photo data. There is something wrong when the client receives data, maybe?


  • Lifetime Qt Champion

    Sorry but you should start with one problem first. Properly pack/unpack the data in your json string. Then we can go over to the next problem.



  • @Tamfub
    Dunno, let's start with

    while(sock->bytesAvailable()){
        ds.startTransaction();
        ds >> dataReceived;
    }
    ds.commitTransaction();
    

    Why would you (at least potentially) start multiple transactions and only commit once at the end?

    Next, I don't understand your whole sequential bytesAvailable() loops. Why two loops? Are you sure your transactions/tests tally against what is being sent in the same fashion?

    Practice code just sending string "hello", perhaps with transactions commented out, while you verify the protocol is correct? Then move to the base64 stuff. And debug out the first & last few characters sent & received to verify they correspond before you look at decoding. You want to discover where your issue is little by little.



  • @Christian-Ehrlicher @JonB
    Ok, I guess I'll have to revise my protocol, then I'll return to the other problem later :)


  • Lifetime Qt Champion

    Hi,

    @Tamfub said in Send image inside Json through QTcpSocket:

    QString string = (QString)dataReceived;

    dataReceived is a QByteArray, that cast is completely wrong.



  • @Christian-Ehrlicher @JonB
    I've given a look to the fortune client and server examples and tried to toy with data.

    In server.cpp, I replaced

    out << fortunes[QRandomGenerator::global()->bounded(fortunes.size())];
    

    with

    QString str;
    for(int i=0; i<1000000; i++)
        str.append("a");
    out << str;
    

    void Client::readFortune() became:

    void Client::readFortune()
    {
        in.startTransaction();
    
        QString data;
        in >> data;
        qDebug() << "data: " << data;
    
        if (!in.commitTransaction())
            return;
    
        qDebug() << "data (end): ";
        qDebug() << data;
    
    //    if (nextFortune == currentFortune) {
    //        QTimer::singleShot(0, this, &Client::requestNewFortune);
    //        return;
    //    }
    
    //    currentFortune = nextFortune;
    //    //statusLabel->setText(currentFortune);
    //    qDebug() << "current: " << currentFortune;
    //    getFortuneButton->setEnabled(true);
        qDebug() << "finished";
    }
    

    The output in the client is:

    next:  ""
    next (end): 
    finished
    

    I guess qDebug() does not print large data?



  • @Tamfub said in Send image inside Json through QTcpSocket:

    I guess qDebug() does not print large data?

    Do not rely on qDebug() output visibility. I think it says somewhere qDebug(qPrintable(qString)); or print QString::length().



  • @SGaist
    Hi.
    Ok, I guess I should use something like QString::fromLatin1(array), right?



  • @JonB
    Ok, I put some printing of the sizes, and found out that receivedData.size() = 2 x sentData.size().

    Server

    ds << QString::fromLatin1(ba.toBase64());
    qDebug() << "size: " << QString::fromLatin1(ba.toBase64()).size(); // 75684
    

    Client

    ds.startTransaction();
    QByteArray data;
    ds >> data;
    
    if (!ds.commitTransaction())
            return;
    
    qDebug() << "data(size): " << risp.size();  // 151368
    

    How is this possible? If I try with

    QByteArray str;
    for(int i=0; i<1000000; i++)
           str.append("a");
    ds << str;
    qDebug() << "size: " << str.size();  // 1000000
    

    instead, the two sizes match!



  • @Tamfub said in Send image inside Json through QTcpSocket:

    qDebug() << "size: " << QString::fromLatin1(ba.toBase64()).size(); // 75684

    qDebug() << "data(size): " << risp.size(); // 151368

    What arithmetic formula do you notice connects the two numbers in your comments? :)



  • @JonB I already noticed the receivedData in double the sentData, I wrote it before. But I can't understand why.



  • @Tamfub
    Because one is a QByteArray and one is a QString? You can't send one type, decode it as a different type, and expect them to be the same.


  • Lifetime Qt Champion

    @Tamfub
    Hi
    QString is 16 bit. QByteArray is 8bit



  • @JonB @mrjj
    you're damn right, I didn't notice that. My bad :)
    Thank you both! Now I will try to pack/unpack correctly.



  • I managed to fix the (un)packing during the sending/receiving part. Now I miss the final step: create a Pixmap from the received data and show it. To sum up:

    Server

    QImage imageObject;
    imageObject.load(Path/to/image);
    QPixmap image = QPixmap::fromImage(imageObject);
    
    QByteArray ba;              // Construct a QByteArray object
    QBuffer buffer(&ba);        // Construct a QBuffer object using the QbyteArray
    image.save(&buffer, "JPG"); // Save the QImage data into the QBuffer
    
    
    qjo.insert("request_type", PHOTO_REQUEST);
    qjo.insert("photo", QString::fromLatin1(ba.toBase64()));
    
    qDebug() << "Size of QString::fromLatin1(ba.toBase64())): " << QString::fromLatin1(ba.toBase64()).size();	// 470184
    
    QJsonDocument doc(qjo);
    QString strJson(doc.toJson(QJsonDocument::Compact));
    info.append(strJson);
    ds << info;
    

    Client

    ds.startTransaction();
    
    QByteArray risp;
    ds >> risp;
    qDebug() << "risp: " << risp;
    
    if (!ds.commitTransaction())
        return;
    
    ...
    
    QJsonValue photo = obj.take("photo");
    QByteArray ba = photo.toString().toLatin1();
    qDebug() << "photo data size (latin): " << photo.toString().toLatin1().size();	// 470184
    //qDebug() << "photo size (utf8): " << photo.toString().toUtf8().size();
    QPixmap pm;
    
    ???  // Missing part
    
    ui->myLabel->setPixmap(pm);
    

    ui->myLabel has another Pixmap set at the moment. In the ??? part, I tried:

    if(!pm.loadFromData(ba, "JPG")){
        qDebug() << "Error: data could not be loaded.";
        return;
    }
    

    and it always ended up printing the error message.

    Then, I read this and tried

    const uchar * const data = reinterpret_cast<const uchar *>(ba.constData());
    qDebug() << "uchar size: " << data;
    
    QPixmap pixmap = QPixmap::fromImage(
        QImage(
            data,
            ui->myLabel->width(),
            ui->myLabel->height(),
            QImage::Format_RGB888
        )
    );
    

    The previous Pixmap of ui->myLabel is removed, but now there is a white one. Any ideas?



  • @Tamfub said in Send image inside Json through QTcpSocket:

    ow I miss the final step: create a Pixmap from the received data and show it. To sum up:

    Have you take a look at QPixmap documentation? I think you will quickly find QPixmap::loadFromData()...



  • @KroMignon
    Hi.
    I eventually managed to show the image. I was missing:

    ba = ba.fromBase64();
    

    before pm.loadFromData(...), and now it works. Thanks!


Log in to reply