Search code examples
c++qtqfileistringstream

Using QFile made istringstream as binary input (for libpng)


I am attempting to use libpng in order to read a png from a Qt resource. The catch: The class doing the reading should not have any dependencies of Qt.

In a first step, reading http://www.piko3d.net/tutorials/libpng-tutorial-loading-png-files-from-streams/#CustomRead I already succeeded in writing a function

read_png(istream& in)

I also succeeded in passing a plain old ifstream

ifstream in("abs_path_to_png/icon.png");

to read_png(..) and having it successfully reading the png. But how to get a (preferably platform independent) istream from a Qt resource? Performance is no great issue so I initially came up with

bool Io_Qt::get_istringstream_from_QFile(QFile& qfile, istringstream& iss)
{
    // [.. Some checking for existence and the file being open ..]
    QString qs(qfile.readAll());
    iss.str(qs.toStdString());

    // I also tried: QByteArray arr(qfile.readAll()); iss.str(arr.data());

    return qfile.isOpen();
}

// Someplace else iss and qfile are created like this:

istringstream iss(std::stringstream::in | std::stringstream::binary);
QFile qfile(":/res/icon.png");
qfile.open(QIODevice::ReadOnly);

This in fact yields an iss that is, at first glance, looking good, when saying

cout << "'" << iss.str().c_str() << "'" << endl;

I get

'�PNG

'

There appears to be some whitespace issue though. For

ifstream in("abs_path_to_png/icon.png");
char c;
cout << "'";
for (int j=0;j<8;j++)
{
    in >> c;
    cout << c;
}
cout << "'" << endl;

yields

'�PNG'

and while the latter works the former variation ultimately leads the libpng checking function png_sig_cmp(..) into rejecting my png as invalid. My first reflex is about "binary". However:

  1. istringstream iss(std::stringstream::in | std::stringstream::binary); feels right.
  2. QIODevice::ReadOnly does not appear to have a binary partner.

Do you see what I missed?


Solution

  • As Ike says, it seems indeed to be about the differences between text-centered operators '>>', '<<' and stuff like '.str(..)' as opposed to binary-centered commands like '.read', and '.write'. Plus it is about initializing the streams correctly. When I finally got the program to do what I wanted the gospel went something like this:

    First I used a plain stringstream alongside the QFile:

    // Explicitly setting flags should at least contain ::in and ::out
    // stringstream ss(std::stringstream::in | std::stringstream::out | std::stringstream::binary)
    // However, the default constructor is perfectly fine.
    stringstream ss;
    QFile qfile(":/res/icon.png");
    qfile.open(QIODevice::ReadOnly);
    

    This I passed to my function which now looks like this:

    bool Io_Qt::get_stringstream_from_QFile(QFile& qfile, stringstream& ss)
    {
        // [.. some sanity checks..]
        QDataStream in(&qfile);
        uint len = qfile.size();
        char* c = (char*)malloc(len*sizeof(char));
        in.readRawData(c,len);
        ss.write(c,len);
        free (c);
        return true;
    }
    

    This stream was filled, and had the right size. Especially since .write(..) writes the required number of characters regardless of how many zeros are within the data. My biggest problem was my being loath to have both std::stringstream::in AND std::stringstream::out activated at the same time because the combination seemed somewhat wacky to me. Yet both are needed. However, I found I may skip std::stringstream::binary. But since it does not seem to do any harm I like to keep it for good luck. Feel free to comment on this superstition though! :-)