Markus-Hermann
Markus-Hermann

Reputation: 987

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?

Upvotes: 2

Views: 779

Answers (3)

DomTomCat
DomTomCat

Reputation: 8569

A more clean, less C-ish, more Qt/C++ -ish version can be:

QFile file(filePath);
file.open(QIODevice::ReadOnly);
QByteArray data = file.readAll();
std::istringstream iss(data.toStdString());

now use iss, in my case this was for libTIFF:

TIFF* tif = TIFFStreamOpen("MemTIFF", &iss);
// ...

Also, for PNGs you can now follow your already posted article, since std::istringstream is of type std::istream.

Note, this solution involves full loading of the file data into memory.

Upvotes: 0

Markus-Hermann
Markus-Hermann

Reputation: 987

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! :-)

Upvotes: 0

user4842163
user4842163

Reputation:

You're working with the streams like they're text data with lexical extraction operators. Check out ios::binary as well as the read and write methods which are appropriate when working with a binary stream.

I would forgo operator<< and operator>> outright in your case in favor of read and write. Use ostream::write to write the byte array data returned from QIODevice::readAll() to transfer its contents to your temporary stringstream, e.g., and use ostream::read in your tests to validate its contents.

A good test case to make sure you transferred properly is to write a test where you read the contents from a QFile, use ostream::write to transfer it to an binary output file stream (ofstream), and then try to load it up in an image software to see if it's okay. Then swap your file stream with a stringstream and pass it to libpng when you have that working.

Upvotes: 1

Related Questions