Использование QFile сделало istringstream бинарным вводом (для libpng)

Я пытаюсь использовать libpng для чтения png из ресурса Qt. Подвох: класс, выполняющий чтение, не должен не иметь каких-либо зависимостей от Qt.

На первом этапе прочитайте http://www.piko3d.net/tutorials/libpng-tutorial-loading-png-files-from-streams/#CustomRead Мне уже удалось написать функцию

read_png(istream& in)

Мне также удалось пройти старый добрый ifstream

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

в read_png(..) и он успешно читает файл png. Но как получить (желательно независимый от платформы) istream из ресурса Qt? Производительность не является большой проблемой, поэтому я сначала придумал

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);

На самом деле это приводит к тому, что на первый взгляд хорошо выглядит, если говорить

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

я получил

'�PNG

'

Однако, похоже, есть проблема с пробелами. За

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

урожаи

'�PNG'

и хотя последний работает, первый вариант в конечном итоге приводит к тому, что функция проверки libpng png_sig_cmp(..) отклоняет мой png как недействительный. Мой первый рефлекс связан с «бинарным». Однако:

  1. istringstream iss(std::stringstream::in | std::stringstream::binary); чувствует себя хорошо.
  2. QIODevice::ReadOnly не имеет бинарного партнера.

Видишь, что я пропустил?


person Markus-Hermann    schedule 06.05.2015    source источник


Ответы (3)


Вы работаете с потоками, как с текстовыми данными, с операторами лексического извлечения. Ознакомьтесь с ios::binary, а также методами read и write, которые подходят при работе с двоичным потоком.

В вашем случае я бы отказался от operator<< и operator>> в пользу read и write. Используйте ostream::write для записи данных массива байтов, возвращенных из QIODevice::readAll(), чтобы передать его содержимое во временный поток строк, например, и используйте ostream::read в своих тестах для проверки его содержимого.

Хорошим тестовым случаем, чтобы убедиться, что вы перенесли правильно, является написание теста, в котором вы читаете содержимое из QFile, используете ostream::write для передачи его в поток двоичного выходного файла (ofstream), а затем пытаетесь загрузить его в программное обеспечение для работы с изображениями. чтобы увидеть, все ли в порядке. Затем замените свой файловый поток строковым потоком и передайте его libpng, когда он у вас заработает.

person Community    schedule 06.05.2015
comment
Спасибо за ваши подсказки. Я предполагаю, что тест на достоверность заключается в том, принимает ли png_sig_cmp(..) содержимое потока. Это подходит для моего подхода ifstream. Это не для istringstream. В обоих рабочих процессах в него не входят операторы ‹‹ или ››, а для iss.str(..): он заполняется либо с помощью QString(qfile.readAll()) в моем первом, либо QByteArray(qfile.readAll()) в моем вторая попытка Но безрезультатно. Естественно, я либо коснулся потоков для вывода (используя ‹‹ и ››), либо для отправки в libpng. Не оба сразу. - person Markus-Hermann; 06.05.2015
comment
Попробуйте что-то вроде этого -- QByteArray bytes = qfile.readAll(); vector<char> 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()); - person ; 06.05.2015
comment
Затем посмотрите, правильно ли "my_test.png" открывается в программе для работы с изображениями. Если это так, вы знаете, что ваша логика для перехода от объекта QIODevice к объекту ostream работает правильно (на этом этапе можно использовать строковые потоки), а остальное должно быть простым, если у вас все остальное работает нормально. - person ; 06.05.2015
comment
Одна из вещей, которая меня интересует: почему бы не пропустить посредника Qt и просто открыть png прямо в fstream? Тогда вам не нужно сначала переносить байты из QIODevice в stringstream. Это потому, что у вас есть внешний код, который неизбежно должен вместо этого загружать PNG в QFile? Если это так, то приведенное выше решение должно работать для передачи содержимого из QFile в ostream при условии, что я не ошибся. - person ; 06.05.2015
comment
Теперь я попробовал stringstream ss(std::stringstream::binary); заполнение его, начиная с QByteArray arr = qfile.readAll(); и экспериментировать с ss.write(arr.data(),arr.size()); Однако это привело к пустому потоку (хотя arr не пуст)... продолжаем попытки. ss.str(string(arr.data())) воспроизвела неправильные данные, как показано в моем исходном сообщении; --- Что касается посредника qt: кажется, он мне нужен, так как я хочу воспользоваться ресурсами qrc в стиле:/res/icon.png - person Markus-Hermann; 06.05.2015
comment
Плохо, я забыл о методе data() и без необходимости пытался передать его временному vector<char>. Хотя с такой логикой все должно быть в порядке. Что означает пустой поток? Вы пробовали read из него и ничего не получили? - person ; 06.05.2015
comment
Здесь в потоках следует избегать функций str, operator<< и operator>>. Они не работают с двоичными данными — они преобразуют их и анализируют (например, останавливаются на двоичных 0). Иными словами, если вы работаете с двоичными данными и используете функции, которые не принимают размер в байтах в своих параметрах, то они, как правило, не подходят для двоичных данных. - person ; 06.05.2015
comment
Я подозреваю, что ваши проблемы сводятся к смешиванию этих текстовых функций с двоичными данными. iostreams являются древними остатками C++ и довольно запутанными, но вы должны быть осторожны и использовать только эти дружественные к двоичному коду функции, а не operator>> или str(x). Как правило, для бинарных потоков ввода-вывода вашими основными функциями являются read и write, tellg и seekg, и всегда будьте осторожны, чтобы создать поток как двоичный. - person ; 06.05.2015
comment
Немного проб и ошибок позже я все-таки заработал - на основе вашего первоначального намека на чтение и запись. Я опубликую резюме. - person Markus-Hermann; 06.05.2015

Как говорит Айк, похоже, что речь действительно идет о различиях между текстовыми операторами '>>', '‹‹' и такими вещами, как '.str(..)', в отличие от двоично-центрированных команд, таких как '.read', и написать'. Плюс речь идет о правильной инициализации потоков. Когда я, наконец, получил программу, которая делала то, что я хотел, Евангелие выглядело примерно так:

Сначала я использовал простой поток строк вместе с 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);

Это я передал своей функции, которая теперь выглядит так:

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;
}

Этот поток был наполнен и имел нужный размер. Тем более, что .write(..) записывает необходимое количество символов вне зависимости от того, сколько нулей в данных. Моя самая большая проблема заключалась в том, что я не хотел одновременно активировать и std::stringstream::in, и std::stringstream::out, потому что эта комбинация казалась мне несколько странной. Но оба нужны. Однако я обнаружил, что могу пропустить std::stringstream::binary. Но так как он, кажется, не причиняет никакого вреда, я предпочитаю хранить его на удачу. Не стесняйтесь комментировать это суеверие! :-)

person Markus-Hermann    schedule 06.05.2015
comment
Я мог ошибаться, но я бы подумал наоборот: двоичный флаг требуется, но stringstream::in и stringstream::out не обязательно, так как это должно быть состоянием по умолчанию для stringstream (как ввода, так и вывода), а не istringstream или ostringstream. - person ; 06.05.2015
comment
Я бы тоже так подумал. Теперь я также тестировал двоичный файл в одиночку. Однако: я видел в результатах, что это похоже на то, что указано в моем посте: оба входа и выхода необходимы. Двоичных почему-то нет. Речь идет о png 32x32, 3 байта на пиксель, тип цвета 2, нарисованный мной вручную с помощью GIMP. Все это происходит на 64-битной машине Debian. - person Markus-Hermann; 06.05.2015
comment
Это странно - меня это сбивает с толку и, возможно, зависит от компилятора. Ну, не помешает их указать! Обычно я нахожу потоки ввода-вывода настолько нелогичными, что использую их очень косвенно или иногда просто отдаю предпочтение функциям C. Это одна из причин, по которой я не могу дать вам идеальный ответ, но я рад, что у вас все работает. - person ; 06.05.2015
comment
Нах я понял! Я фактически форсирую режим, вызывая конструктор так, как я делаю, как указано в сообщении. Если я просто инициализирую по умолчанию, как в stringstream ss;, он тоже работает нормально. :-) Это все еще не объясняет ненужность std::stringstream::binary. :-/ - person Markus-Hermann; 06.05.2015

Более чистая, менее C-ish, более Qt/C++-ish версия может быть:

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

теперь используйте iss, в моем случае это было для libTIFF:

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

Кроме того, для файлов PNG теперь можно следить за уже опубликованными article, так как std::istringstream относится к типу std::istream.

Обратите внимание, что это решение предполагает полную загрузку данных файла в память.

person DomTomCat    schedule 13.04.2018