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.
This commit is contained in:
Mirco Miranda 2024-08-17 06:40:29 +00:00
parent 0590c6b49d
commit d02dcb064b
2 changed files with 141 additions and 36 deletions

View File

@ -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<char*>(&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

View File

@ -9,7 +9,9 @@
#define KIMG_PCX_P_H
#include <QImageIOPlugin>
#include <QScopedPointer>
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<PCXHandlerPrivate> d;
};
class PCXPlugin : public QImageIOPlugin