2

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?

Markus-Hermann
  • 789
  • 11
  • 24

3 Answers3

1

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.

  • Thanks for your hints. I guess the test for validity is whether or not png_sig_cmp(..) accepts the stream content. It does for my ifstream approach. It does not for the istringstream one. Within both workflows no "<<" or ">>" operators enter into it and for iss.str(..): It is filled either using QString(qfile.readAll()) in my first or QByteArray(qfile.readAll()) in my second attepmt. To no avail. Naturally I did either touch the streams for output (using "<<" and ">>") or for sending to the libpng. Not both at once. – Markus-Hermann May 06 '15 at 14:16
  • Try something like this -- `QByteArray bytes = qfile.readAll(); vector buf(bytes.size()); bytes.read(&buf[0], buf.size()); ofstream out_file("my_test.png", ios::binary); out_file.write((const char*)&buf[0], buf.size());` –  May 06 '15 at 14:21
  • Then see if `"my_test.png"` opens up properly in an image software. If it does, you know your logic to transfer from a `QIODevice` to an `ostream` object works properly (can use stringstreams at this point), and the rest should be straightforward if you have the rest working fine. –  May 06 '15 at 14:26
  • One of the things I'm wondering: why not skip the Qt middleman and just open the png up directly in an `fstream`? Then you don't have to bother transferring bytes from a `QIODevice` to a `stringstream` first. Is it because you have outer code that inevitably has to load PNGs into a `QFile` instead? If so, that above solution should work to transfer the contents from a QFile to an `ostream` provided I didn't goof. –  May 06 '15 at 14:31
  • Now I have tried stringstream ss(std::stringstream::binary); filling it starting at QByteArray arr = qfile.readAll(); and experimenting with ss.write(arr.data(),arr.size()); However, this led to an empty stream (even though arr is not empty)... keeping trying. ss.str(string(arr.data())) reproduced the illformed data as shown in my original post; --- As for the "qt Middleman": I seem to need it since I want to take advantage of qrc resources of the style ":/res/icon.png" – Markus-Hermann May 06 '15 at 15:06
  • My bad, I forgot about the `data()` method and was unnecessarily trying to transfer it to a temp `vector`. Though that logic should really be okay. What does empty stream mean? Did you try a `read` from it and get nothing back? –  May 06 '15 at 15:14
  • The functions you want to avoid here in streams are `str`, `operator<<`, and `operator>>`. Those don't work with binary data -- they'll convert it and parse it (ex: stopping at binary 0s). Put another way, if you're working in binary and using functions which don't accept a byte size in their parameters, then they're generally not suitable for binary data. –  May 06 '15 at 15:16
  • I suspect your problems boil down to mixing in those text-mode functions with binary data. `iostreams` are ancient remnants of C++ and rather confusing, but you want to be careful to use those binary-friendly functions only, not like `operator>>` or `str(x)`. Typically for binary iostreams, your bread and butter functions are `read` and `write`, `tellg` and `seekg`, and always careful to create the stream as binary. –  May 06 '15 at 15:30
  • 1
    A little trial and error later I got it working after all -- based on your original hint about read and write. I will post a summary. – Markus-Hermann May 06 '15 at 18:33
0

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

Markus-Hermann
  • 789
  • 11
  • 24
  • I could be wrong but I would have thought opposite: that the binary flag is required but `stringstream::in` and `stringstream::out` aren't necessarily, since that should be the default state of a `stringstream` (both input and output) as opposed to `istringstream` or `ostringstream`. –  May 06 '15 at 19:24
  • That is what I would think, too. Now I also tested binary alone. However: I've seen in the results that it is like stated in my post: in and out are both required. Binary for some reason is not. The png in question is 32x32, 3 bytes per pixel, color type 2, handdrawn by me using GIMP. All this happens on a 64bit Debian machine. – Markus-Hermann May 06 '15 at 19:28
  • That's an odd one -- quite perplexing to me and maybe compiler-specific. Oh well, it can't hurt to specify them! Normally I find iostreams so counter-intuitive that I use them very indirectly or sometimes just favor the C functions. It's one of the reasons I couldn't give you a perfect answer, but I'm glad you have it working. –  May 06 '15 at 19:30
  • 1
    Nah I got it! I actually force the mode by calling the constructor the way I do as stated in the post. If I simply initialize default-like as in *stringstream ss;* It works just fine as well. :-) That still does not explain the unnecessity of std::stringstream::binary though. :-/ – Markus-Hermann May 06 '15 at 19:33
0

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.

DomTomCat
  • 8,189
  • 1
  • 49
  • 64