diff --git a/autotests/read/pcx/indexed8.pcx b/autotests/read/pcx/indexed8.pcx new file mode 100644 index 0000000..121a45e Binary files /dev/null and b/autotests/read/pcx/indexed8.pcx differ diff --git a/autotests/read/pcx/indexed8.png b/autotests/read/pcx/indexed8.png new file mode 100644 index 0000000..a978500 Binary files /dev/null and b/autotests/read/pcx/indexed8.png differ diff --git a/autotests/readtest.cpp b/autotests/readtest.cpp index 6390a36..b41bdf6 100644 --- a/autotests/readtest.cpp +++ b/autotests/readtest.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -18,6 +19,44 @@ #include "fuzzyeq.cpp" +/** + * @brief The SequentialFile class + * Class to make a file a sequential device. This class is used to check if the plugins could works + * on a sequential device such as a socket. + */ +class SequentialFile : public QFile +{ +public: + SequentialFile() + : QFile() + { + } + explicit SequentialFile(const QString &name) + : QFile(name) + { + } +#ifndef QT_NO_QOBJECT + explicit SequentialFile(QObject *parent) + : QFile(parent) + { + } + SequentialFile(const QString &name, QObject *parent) + : QFile(name, parent) + { + } +#endif + + bool isSequential() const override + { + return true; + } + + qint64 size() const override + { + return bytesAvailable(); + } +}; + static void writeImageData(const char *name, const QString &filename, const QImage &image) { QFile file(filename); @@ -56,7 +95,7 @@ int main(int argc, char **argv) QCoreApplication::removeLibraryPath(QStringLiteral(PLUGIN_DIR)); QCoreApplication::addLibraryPath(QStringLiteral(PLUGIN_DIR)); QCoreApplication::setApplicationName(QStringLiteral("readtest")); - QCoreApplication::setApplicationVersion(QStringLiteral("1.0.0")); + QCoreApplication::setApplicationVersion(QStringLiteral("1.1.0")); QCommandLineParser parser; parser.setApplicationDescription(QStringLiteral("Performs basic image conversion checking.")); @@ -98,6 +137,7 @@ int main(int argc, char **argv) int passed = 0; int failed = 0; + int skipped = 0; QTextStream(stdout) << "********* " << "Starting basic read tests for " << suffix << " images *********\n"; @@ -111,72 +151,88 @@ int main(int argc, char **argv) QTextStream(stdout) << "QImageReader::supportedImageFormats: " << formatStrings.join(", ") << "\n"; const QFileInfoList lstImgDir = imgdir.entryInfoList(); - for (const QFileInfo &fi : lstImgDir) { - if (!fi.suffix().compare("png", Qt::CaseInsensitive)) { - continue; - } - int suffixPos = fi.filePath().count() - suffix.count(); - QString inputfile = fi.filePath(); - QString expfile = fi.filePath().replace(suffixPos, suffix.count(), QStringLiteral("png")); - QString expfilename = QFileInfo(expfile).fileName(); - - QImageReader inputReader(inputfile, format); - QImageReader expReader(expfile, "png"); - - QImage inputImage; - QImage expImage; - - if (!expReader.read(&expImage)) { - QTextStream(stdout) << "ERROR: " << fi.fileName() << ": could not load " << expfilename << ": " << expReader.errorString() << "\n"; - ++failed; - continue; - } - if (!inputReader.canRead()) { - QTextStream(stdout) << "FAIL : " << fi.fileName() << ": failed can read: " << inputReader.errorString() << "\n"; - ++failed; - continue; - } - if (!inputReader.read(&inputImage)) { - QTextStream(stdout) << "FAIL : " << fi.fileName() << ": failed to load: " << inputReader.errorString() << "\n"; - ++failed; - continue; - } - if (expImage.width() != inputImage.width()) { - QTextStream(stdout) << "FAIL : " << fi.fileName() << ": width was " << inputImage.width() << " but " << expfilename << " width was " - << expImage.width() << "\n"; - ++failed; - } else if (expImage.height() != inputImage.height()) { - QTextStream(stdout) << "FAIL : " << fi.fileName() << ": height was " << inputImage.height() << " but " << expfilename << " height was " - << expImage.height() << "\n"; - ++failed; + // Launch 2 runs for each test: first run on a random device, second run on a sequential device + for (int seq = 0; seq < 2; ++seq) { + if (seq) { + QTextStream(stdout) << "* Run on SEQUENTIAL device (SKIP = no sequential support and it's OK)\n"; } else { - QImage::Format inputFormat = preferredFormat(inputImage.format()); - QImage::Format expFormat = preferredFormat(expImage.format()); - QImage::Format cmpFormat = inputFormat == expFormat ? inputFormat : QImage::Format_ARGB32; + QTextStream(stdout) << "* Run on RANDOM device\n"; + } + for (const QFileInfo &fi : lstImgDir) { + if (!fi.suffix().compare("png", Qt::CaseInsensitive)) { + continue; + } + int suffixPos = fi.filePath().count() - suffix.count(); + QString inputfile = fi.filePath(); + QString expfile = fi.filePath().replace(suffixPos, suffix.count(), QStringLiteral("png")); + QString expfilename = QFileInfo(expfile).fileName(); - if (inputImage.format() != cmpFormat) { - QTextStream(stdout) << "INFO : " << fi.fileName() << ": converting " << fi.fileName() << " from " << formatToString(inputImage.format()) - << " to " << formatToString(cmpFormat) << '\n'; - inputImage = inputImage.convertToFormat(cmpFormat); - } - if (expImage.format() != cmpFormat) { - QTextStream(stdout) << "INFO : " << fi.fileName() << ": converting " << expfilename << " from " << formatToString(expImage.format()) << " to " - << formatToString(cmpFormat) << '\n'; - expImage = expImage.convertToFormat(cmpFormat); - } - if (fuzzyeq(inputImage, expImage, fuzziness)) { - QTextStream(stdout) << "PASS : " << fi.fileName() << "\n"; - ++passed; - } else { - QTextStream(stdout) << "FAIL : " << fi.fileName() << ": differs from " << expfilename << "\n"; - writeImageData("expected data", fi.fileName() + QLatin1String("-expected.data"), expImage); - writeImageData("actual data", fi.fileName() + QLatin1String("-actual.data"), inputImage); + std::unique_ptr inputDevice(seq ? new SequentialFile(inputfile) : new QFile(inputfile)); + QImageReader inputReader(inputDevice.get(), format); + QImageReader expReader(expfile, "png"); + + QImage inputImage; + QImage expImage; + + if (!expReader.read(&expImage)) { + QTextStream(stdout) << "ERROR: " << fi.fileName() << ": could not load " << expfilename << ": " << expReader.errorString() << "\n"; ++failed; + continue; + } + if (!inputReader.canRead()) { + // All plugins must pass the test on a random device. + // canRead() must also return false if the plugin is unable to run on a sequential device. + if (inputDevice->isSequential()) { + QTextStream(stdout) << "SKIP : " << fi.fileName() << ": cannot read on a sequential device\n"; + ++skipped; + } else { + QTextStream(stdout) << "FAIL : " << fi.fileName() << ": failed can read: " << inputReader.errorString() << "\n"; + ++failed; + } + continue; + } + if (!inputReader.read(&inputImage)) { + QTextStream(stdout) << "FAIL : " << fi.fileName() << ": failed to load: " << inputReader.errorString() << "\n"; + ++failed; + continue; + } + if (expImage.width() != inputImage.width()) { + QTextStream(stdout) << "FAIL : " << fi.fileName() << ": width was " << inputImage.width() << " but " << expfilename << " width was " + << expImage.width() << "\n"; + ++failed; + } else if (expImage.height() != inputImage.height()) { + QTextStream(stdout) << "FAIL : " << fi.fileName() << ": height was " << inputImage.height() << " but " << expfilename << " height was " + << expImage.height() << "\n"; + ++failed; + } else { + QImage::Format inputFormat = preferredFormat(inputImage.format()); + QImage::Format expFormat = preferredFormat(expImage.format()); + QImage::Format cmpFormat = inputFormat == expFormat ? inputFormat : QImage::Format_ARGB32; + + if (inputImage.format() != cmpFormat) { + QTextStream(stdout) << "INFO : " << fi.fileName() << ": converting " << fi.fileName() << " from " << formatToString(inputImage.format()) + << " to " << formatToString(cmpFormat) << '\n'; + inputImage = inputImage.convertToFormat(cmpFormat); + } + if (expImage.format() != cmpFormat) { + QTextStream(stdout) << "INFO : " << fi.fileName() << ": converting " << expfilename << " from " << formatToString(expImage.format()) + << " to " << formatToString(cmpFormat) << '\n'; + expImage = expImage.convertToFormat(cmpFormat); + } + if (fuzzyeq(inputImage, expImage, fuzziness)) { + QTextStream(stdout) << "PASS : " << fi.fileName() << "\n"; + ++passed; + } else { + QTextStream(stdout) << "FAIL : " << fi.fileName() << ": differs from " << expfilename << "\n"; + writeImageData("expected data", fi.fileName() + QLatin1String("-expected.data"), expImage); + writeImageData("actual data", fi.fileName() + QLatin1String("-actual.data"), inputImage); + ++failed; + } } } } - QTextStream(stdout) << "Totals: " << passed << " passed, " << failed << " failed\n"; + QTextStream(stdout) << "Totals: " << passed << " passed, " << skipped << " skipped, " << failed << " failed\n"; QTextStream(stdout) << "********* " << "Finished basic read tests for " << suffix << " images *********\n"; diff --git a/src/imageformats/kra.cpp b/src/imageformats/kra.cpp index a5e75ca..fff976c 100644 --- a/src/imageformats/kra.cpp +++ b/src/imageformats/kra.cpp @@ -57,6 +57,9 @@ bool KraHandler::canRead(QIODevice *device) qWarning("KraHandler::canRead() called with no device"); return false; } + if (device->isSequential()) { + return false; + } char buff[57]; if (device->peek(buff, sizeof(buff)) == sizeof(buff)) { diff --git a/src/imageformats/ora.cpp b/src/imageformats/ora.cpp index 96290e8..79c5353 100644 --- a/src/imageformats/ora.cpp +++ b/src/imageformats/ora.cpp @@ -56,6 +56,9 @@ bool OraHandler::canRead(QIODevice *device) qWarning("OraHandler::canRead() called with no device"); return false; } + if (device->isSequential()) { + return false; + } char buff[54]; if (device->peek(buff, sizeof(buff)) == sizeof(buff)) { diff --git a/src/imageformats/pcx.cpp b/src/imageformats/pcx.cpp index e9b0a23..477c3cc 100644 --- a/src/imageformats/pcx.cpp +++ b/src/imageformats/pcx.cpp @@ -174,7 +174,7 @@ static QDataStream &operator>>(QDataStream &s, PCXHEADER &ph) // Skip the rest of the header quint8 byte; - while (s.device()->pos() < 128) { + for (auto i = 0; i < 54; ++i) { s >> byte; } @@ -684,12 +684,6 @@ bool PCXHandler::canRead(QIODevice *device) return false; } - // We do not support sequential images - // We need to know the current position to properly read the header - if (device->isSequential()) { - return false; - } - qint64 oldPos = device->pos(); char head[1]; diff --git a/src/imageformats/psd.cpp b/src/imageformats/psd.cpp index 80a61b2..1c7d263 100644 --- a/src/imageformats/psd.cpp +++ b/src/imageformats/psd.cpp @@ -1249,6 +1249,9 @@ bool PSDHandler::canRead(QIODevice *device) qWarning("PSDHandler::canRead() called with no device"); return false; } + if (device->isSequential()) { + return false; + } qint64 oldPos = device->pos(); diff --git a/src/imageformats/raw.cpp b/src/imageformats/raw.cpp index 5151489..8ec8bc2 100644 --- a/src/imageformats/raw.cpp +++ b/src/imageformats/raw.cpp @@ -855,6 +855,14 @@ int RAWHandler::currentImageNumber() const bool RAWHandler::canRead(QIODevice *device) { + if (!device) { + qWarning("RAWHandler::canRead() called with no device"); + return false; + } + if (device->isSequential()) { + return false; + } + device->startTransaction(); std::unique_ptr rawProcessor(new LibRaw); diff --git a/src/imageformats/xcf.cpp b/src/imageformats/xcf.cpp index 9885452..fa04267 100644 --- a/src/imageformats/xcf.cpp +++ b/src/imageformats/xcf.cpp @@ -3341,6 +3341,9 @@ bool XCFHandler::canRead(QIODevice *device) qCDebug(XCFPLUGIN) << "XCFHandler::canRead() called with no device"; return false; } + if (device->isSequential()) { + return false; + } qint64 oldPos = device->pos();