From d02dcb064b8b2d7820c060c81623a25bb97b8fbb Mon Sep 17 00:00:00 2001 From: Mirco Miranda Date: Sat, 17 Aug 2024 06:40:29 +0000 Subject: [PATCH] PCX: added options support - Added support for ```Size``` and ```Format``` options and slightly improved format detection from canRead(). - Added PCXHEADER::isValid() method to consolidate header consistency checks in one place. --- src/imageformats/pcx.cpp | 169 ++++++++++++++++++++++++++++++--------- src/imageformats/pcx_p.h | 8 ++ 2 files changed, 141 insertions(+), 36 deletions(-) diff --git a/src/imageformats/pcx.cpp b/src/imageformats/pcx.cpp index 6d47cf9..4efd19a 100644 --- a/src/imageformats/pcx.cpp +++ b/src/imageformats/pcx.cpp @@ -67,6 +67,40 @@ public: { return (Encoding == 1); } + /*! + * \brief isValid + * Checks if the header data are valid for the PCX. + * \note Put here the header sanity checks. + * \return True if the header is a valid PCX header, otherwise false. + */ + inline bool isValid() const + { + return Manufacturer == 10 && BytesPerLine != 0; + } + /*! + * \brief isSupported + * \return True if the header is valid and the PCX format is supported by the plugin. Otherwise false. + */ + inline bool isSupported() const + { + return isValid() && format() != QImage::Format_Invalid; + } + inline QImage::Format format() const + { + auto fmt = QImage::Format_Invalid; + if (Bpp == 1 && NPlanes == 1) { + fmt = QImage::Format_Mono; + } else if (Bpp == 1 && NPlanes == 4) { + fmt = QImage::Format_Indexed8; + } else if (Bpp == 4 && NPlanes == 1) { + fmt = QImage::Format_Indexed8; + } else if (Bpp == 8 && NPlanes == 1) { + fmt = QImage::Format_Indexed8; + } else if (Bpp == 8 && NPlanes == 3) { + fmt = QImage::Format_RGB32; + } + return fmt; + } quint8 Manufacturer; // Constant Flag, 10 = ZSoft .pcx quint8 Version; // Version information· @@ -100,6 +134,8 @@ public: // found only in PB IV/IV Plus quint16 VScreenSize; // Vertical screen size in pixels. New field // found only in PB IV/IV Plus + + quint8 unused[54]; }; #pragma pack(pop) @@ -173,9 +209,8 @@ static QDataStream &operator>>(QDataStream &s, PCXHEADER &ph) ph.VScreenSize = vscreensize; // Skip the rest of the header - quint8 byte; - for (auto i = 0; i < 54; ++i) { - s >> byte; + for (size_t i = 0, n = sizeof(ph.unused); i < n; ++i) { + s >> ph.unused[i]; } return s; @@ -213,9 +248,8 @@ static QDataStream &operator<<(QDataStream &s, const PCXHEADER &ph) s << ph.HScreenSize; s << ph.VScreenSize; - quint8 byte = 0; - for (int i = 0; i < 54; ++i) { - s << byte; + for (size_t i = 0, n = sizeof(ph.unused); i < n; ++i) { + s << ph.unused[i]; } return s; @@ -230,6 +264,34 @@ PCXHEADER::PCXHEADER() s >> *this; } +bool peekHeader(QIODevice *d, PCXHEADER& h) +{ + qint64 pos = 0; + if (!d->isSequential()) { + pos = d->pos(); + } + + auto ok = false; + { // datastream is destroyed before working on device + QDataStream ds(d); + ds.setByteOrder(QDataStream::LittleEndian); + ds >> h; + ok = ds.status() == QDataStream::Ok && h.isValid(); + } + + if (!d->isSequential()) { + return d->seek(pos) && ok; + } + + // sequential device undo + auto head = reinterpret_cast(&h); + auto readBytes = sizeof(h); + while (readBytes > 0) { + d->ungetChar(head[readBytes-- - 1]); + } + return ok; +} + static bool readLine(QDataStream &s, QByteArray &buf, const PCXHEADER &header) { quint32 i = 0; @@ -265,7 +327,7 @@ static bool readImage1(QImage &img, QDataStream &s, const PCXHEADER &header) { QByteArray buf(header.BytesPerLine, 0); - img = imageAlloc(header.width(), header.height(), QImage::Format_Mono); + img = imageAlloc(header.width(), header.height(), header.format()); img.setColorCount(2); if (img.isNull()) { @@ -301,7 +363,7 @@ static bool readImage4(QImage &img, QDataStream &s, const PCXHEADER &header) QByteArray buf(header.BytesPerLine * 4, 0); QByteArray pixbuf(header.width(), 0); - img = imageAlloc(header.width(), header.height(), QImage::Format_Indexed8); + img = imageAlloc(header.width(), header.height(), header.format()); img.setColorCount(16); if (img.isNull()) { qWarning() << "Failed to allocate image, invalid dimensions?" << QSize(header.width(), header.height()); @@ -353,7 +415,7 @@ static bool readImage4v2(QImage &img, QDataStream &s, const PCXHEADER &header) { QByteArray buf(header.BytesPerLine, 0); - img = imageAlloc(header.width(), header.height(), QImage::Format_Indexed8); + img = imageAlloc(header.width(), header.height(), header.format()); img.setColorCount(16); if (img.isNull()) { @@ -394,7 +456,7 @@ static bool readImage8(QImage &img, QDataStream &s, const PCXHEADER &header) { QByteArray buf(header.BytesPerLine, 0); - img = imageAlloc(header.width(), header.height(), QImage::Format_Indexed8); + img = imageAlloc(header.width(), header.height(), header.format()); img.setColorCount(256); if (img.isNull()) { @@ -457,7 +519,7 @@ static bool readImage24(QImage &img, QDataStream &s, const PCXHEADER &header) QByteArray g_buf(header.BytesPerLine, 0); QByteArray b_buf(header.BytesPerLine, 0); - img = imageAlloc(header.width(), header.height(), QImage::Format_RGB32); + img = imageAlloc(header.width(), header.height(), header.format()); if (img.isNull()) { qWarning() << "Failed to allocate image, invalid dimensions?" << QSize(header.width(), header.height()); @@ -685,7 +747,18 @@ static bool writeImage24(QImage &img, QDataStream &s, PCXHEADER &header) return true; } +class PCXHandlerPrivate +{ +public: + PCXHandlerPrivate() {} + ~PCXHandlerPrivate() {} + + PCXHEADER m_header; +}; + PCXHandler::PCXHandler() + : QImageIOHandler() + , d(new PCXHandlerPrivate) { } @@ -707,11 +780,14 @@ bool PCXHandler::read(QImage *outImage) return false; } - PCXHEADER header; - + auto&& header = d->m_header; s >> header; - if (header.Manufacturer != 10 || header.BytesPerLine == 0 || s.atEnd()) { + if (s.status() != QDataStream::Ok || s.atEnd()) { + return false; + } + + if (!header.isSupported()) { return false; } @@ -781,6 +857,46 @@ bool PCXHandler::write(const QImage &image) return ok; } +bool PCXHandler::supportsOption(ImageOption option) const +{ + if (option == QImageIOHandler::Size) { + return true; + } + if (option == QImageIOHandler::ImageFormat) { + return true; + } + return false; +} + +QVariant PCXHandler::option(ImageOption option) const +{ + QVariant v; + + if (option == QImageIOHandler::Size) { + auto&& header = d->m_header; + if (header.isSupported()) { + v = QVariant::fromValue(QSize(header.width(), header.height())); + } else if (auto dev = device()) { + if (peekHeader(dev, header) && header.isSupported()) { + v = QVariant::fromValue(QSize(header.width(), header.height())); + } + } + } + + if (option == QImageIOHandler::ImageFormat) { + auto&& header = d->m_header; + if (header.isSupported()) { + v = QVariant::fromValue(header.format()); + } else if (auto dev = device()) { + if (peekHeader(dev, header) && header.isSupported()) { + v = QVariant::fromValue(header.format()); + } + } + } + + return v; +} + bool PCXHandler::canRead(QIODevice *device) { if (!device) { @@ -788,30 +904,11 @@ bool PCXHandler::canRead(QIODevice *device) return false; } - qint64 oldPos = device->pos(); - - char head[1]; - qint64 readBytes = device->read(head, sizeof(head)); - if (readBytes != sizeof(head)) { - if (device->isSequential()) { - while (readBytes > 0) { - device->ungetChar(head[readBytes-- - 1]); - } - } else { - device->seek(oldPos); - } + PCXHEADER header; + if (!peekHeader(device, header)) { return false; } - - if (device->isSequential()) { - while (readBytes > 0) { - device->ungetChar(head[readBytes-- - 1]); - } - } else { - device->seek(oldPos); - } - - return qstrncmp(head, "\012", 1) == 0; + return header.isSupported(); } QImageIOPlugin::Capabilities PCXPlugin::capabilities(QIODevice *device, const QByteArray &format) const diff --git a/src/imageformats/pcx_p.h b/src/imageformats/pcx_p.h index 3a9b817..d443c03 100644 --- a/src/imageformats/pcx_p.h +++ b/src/imageformats/pcx_p.h @@ -9,7 +9,9 @@ #define KIMG_PCX_P_H #include +#include +class PCXHandlerPrivate; class PCXHandler : public QImageIOHandler { public: @@ -19,7 +21,13 @@ public: bool read(QImage *image) override; bool write(const QImage &image) override; + bool supportsOption(QImageIOHandler::ImageOption option) const override; + QVariant option(QImageIOHandler::ImageOption option) const override; + static bool canRead(QIODevice *device); + +private: + const QScopedPointer d; }; class PCXPlugin : public QImageIOPlugin