c++grpcqt6qimageqbytearray

How to correctly convert image to QByteArray and vice versa?


I am building two apps, one client, and one server. I need to send and image via RPC to the server app and convert it back into QImage.

When I do the conversion, I noticed that the images don't match and I need them to match 100%.

Here's what I tried:

[Client]

void ServerConnection::MakeRequest(QImage& imageData, const uint8_t imageId) {

        // Reset each time received images
        receivedImages_.clear();

        // Remove the limits on sizes
        grpc::ChannelArguments ch_args;
        ch_args.SetMaxReceiveMessageSize(-1); 
        ch_args.SetMaxSendMessageSize(-1);
        channel_ = grpc::CreateCustomChannel(ip_ + ":" + std::to_string(port_), grpc::InsecureChannelCredentials(), ch_args);
        // Create a stub to manage the connection
        stub_ = ImageService::NewStub(channel_);

        // Create a request message
        ImageRequest request;
        request.set_image_id(std::to_string(imageId));

        // Convert QImage to QByteArray with JPG format
        QByteArray byteArray;
        QBuffer buffer(&byteArray);
        buffer.open(QIODevice::WriteOnly);

        if (!imageData.isNull() && imageData.save(&buffer, "JPG")) {
            // Set the image data in the request
            request.set_image_data(byteArray.toBase64().toStdString());

            // Create a response message
            ImageResponse response;

            // Create a new context for this RPC
            grpc::ClientContext context;

            // Call the RPC method
            grpc::Status status = stub_->ProcessImage(&context, request, &response);

            // Handle the response
            if (status.ok()) {
                // Handle response
                std::cout << "Received set ID: " << response.set_id() << std::endl;
                for (const auto& image_data : response.image_data()) {
                    // Convert QByteArray to QImage
                    QByteArray byteArray = QByteArray::fromBase64(QString::fromStdString(image_data).toUtf8());
                    QImage image = QImage::fromData(byteArray, "JPG");
                    receivedImages_.push_back(image);
                }
            } else {
                // Handle error
                std::cerr << "Error: " << status.error_message() << std::endl;
            }
        } else {
            // Handle error saving QImage
            std::cerr << "Error: Failed to save QImage to QByteArray" << std::endl;
        }
    }

[Server]

grpc::Status ProcessImage(grpc::ServerContext* context, const ImageRequest* request, ImageResponse* response) override {
        QByteArray imageData = QByteArray::fromBase64(request->image_data().c_str());
        // Check if image data is valid
        if (imageData.isNull()) {
            return grpc::Status::CANCELLED;
        } else {
            std::cout << "Image data is valid" << std::endl;
        }

        // Convert QImage to cv::Mat
        QImage image;
        image.loadFromData(reinterpret_cast<const uchar*>(imageData.constData()), imageData.size());
    QImage image;
    // Save QImage
    QString savePath = "output_image.jpg";
    image.save(savePath, "JPG");

        // ..........
    
        return grpc::Status::OK;
    }

I tried to compare data after conversion on the client, and on the server when it's received and they match. So, I'm stuck with how to properly convert images.


Solution

  • How to correctly convert image to QByteArray and vice versa?

    Maybe you already are doing it right? How would you know? :)

    Get rid of grpc as it's not necessary to just demonstrate the problem. Start with the basics and go from there:

    1. Load QImage from a file, save the same QImage to a differenet file, compare the files with whatever tool you're using. Once that works,

    2. Load QImage from a file, dump into a QByteArray, make a new QImage from that, save to a file, compare. Then

    3. Load QImage from a file, into a QByteArray, into a Base-64 encoded string, back into a QByteArray, into a QImage, save to a file, compare. Then

    4. Load QImage into a file,...,into Base-64-encoded string, then transfer it in-process via grpc, then reverse to a QImage, save to file, compare. Then

    5. Same as #4, but separate the grpc endpoints across two processes running on the same machine.

    If you do it that way, you'll have a clear indication of where the problem occurs.

    Of course, the image comparison can be done using Qt itself. To get you started, here's how to do steps 1..4, but painting the image instead of loading it from an external file:

    #include <QtGui>
    #include <QDebug>
    
    int main(int, char*[])
    {
        // Source
        QImage src({64, 64}, QImage::Format_RGB32);
        QPainter(&src).fillRect(src.rect(), Qt::red);
    
        QBuffer srcBuf;
        srcBuf.open(QBuffer::ReadWrite);
        src.save(&srcBuf, "PNG");
        srcBuf.close();
    
        QByteArray srcBase64 = srcBuf.buffer().toBase64();
    
        // Destination
        QByteArray dstBase64 = srcBase64;
        QBuffer dstBuf;
        dstBuf.buffer() = QByteArray::fromBase64(dstBase64);
    
        QImage dst;
        dst.load(&dstBuf, "PNG");
    
        qDebug() << (dst==src);
        Q_ASSERT(dst==src);
    }