diff --git a/autotests/read/iff/testcard_acbm.iff b/autotests/read/iff/testcard_acbm.iff new file mode 100644 index 0000000..d3098f2 Binary files /dev/null and b/autotests/read/iff/testcard_acbm.iff differ diff --git a/autotests/read/iff/testcard_acbm.png b/autotests/read/iff/testcard_acbm.png new file mode 100644 index 0000000..626622c Binary files /dev/null and b/autotests/read/iff/testcard_acbm.png differ diff --git a/src/imageformats/chunks.cpp b/src/imageformats/chunks.cpp index 278c8c5..357b851 100644 --- a/src/imageformats/chunks.cpp +++ b/src/imageformats/chunks.cpp @@ -8,6 +8,7 @@ #include "chunks_p.h" #include "packbits_p.h" +#include #include #include @@ -243,7 +244,9 @@ IFFChunk::ChunkList IFFChunk::innerFromDevice(QIODevice *d, bool *ok, IFFChunk * for (; !d->atEnd() && (nextChunkPos == 0 || d->pos() < nextChunkPos);) { auto cid = d->peek(4); QSharedPointer chunk; - if (cid == ANNO_CHUNK) { + if (cid == ABIT_CHUNK) { + chunk = QSharedPointer(new ABITChunk()); + } else if (cid == ANNO_CHUNK) { chunk = QSharedPointer(new ANNOChunk()); } else if (cid == AUTH_CHUNK) { chunk = QSharedPointer(new AUTHChunk()); @@ -285,7 +288,7 @@ IFFChunk::ChunkList IFFChunk::innerFromDevice(QIODevice *d, bool *ok, IFFChunk * chunk = QSharedPointer(new XMP0Chunk()); } else { // unknown chunk chunk = QSharedPointer(new IFFChunk()); - qCInfo(LOG_IFFPLUGIN) << "IFFChunk::innerFromDevice: unknown chunk" << cid; + qCDebug(LOG_IFFPLUGIN) << "IFFChunk::innerFromDevice: unknown chunk" << cid; } // change the alignment to the one of main chunk (required for unknown Maya IFF chunks) @@ -596,7 +599,7 @@ bool BODYChunk::isValid() const QByteArray BODYChunk::strideRead(QIODevice *d, const BMHDChunk *header, const CAMGChunk *camg, const CMAPChunk *cmap, bool isPbm) const { - if (!isValid() || header == nullptr) { + if (!isValid() || header == nullptr || d == nullptr) { return {}; } @@ -849,6 +852,68 @@ QByteArray BODYChunk::deinterleave(const QByteArray &planes, const BMHDChunk *he return ba; } +/* ****************** + * *** ABIT Chunk *** + * ****************** */ + +ABITChunk::~ABITChunk() +{ + +} + +ABITChunk::ABITChunk() : BODYChunk() +{ + +} + +bool ABITChunk::isValid() const +{ + return chunkId() == ABITChunk::defaultChunkId(); +} + +QByteArray ABITChunk::strideRead(QIODevice *d, const BMHDChunk *header, const CAMGChunk *camg, const CMAPChunk *cmap, bool isPbm) const +{ + if (!isValid() || header == nullptr || d == nullptr) { + return {}; + } + if (header->compression() != BMHDChunk::Compression::Uncompressed || isPbm) { + return {}; + } + + // convert ABIT data to an ILBM line on the fly + auto ilbmLine = QByteArray(strideSize(header, isPbm), char()); + auto rowSize = header->rowLen(); + auto height = header->height(); + if (_y >= height) { + return {}; + } + for (qint32 plane = 0, planes = qint32(header->bitplanes()); plane < planes; ++plane) { + if (!seek(d, qint64(plane) * rowSize * height + _y * rowSize)) + return {}; + auto offset = qint64(plane) * rowSize; + if (offset + rowSize > ilbmLine.size()) + return {}; + if (d->read(ilbmLine.data() + offset, rowSize) != rowSize) + return {}; + } + // next line on the next run + ++_y; + + // decode the ILBM line + QBuffer buf; + buf.setData(ilbmLine); + if (!buf.open(QBuffer::ReadOnly)) { + return {}; + } + return BODYChunk::strideRead(&buf, header, camg, cmap, isPbm); +} + +bool ABITChunk::resetStrideRead(QIODevice *d) const +{ + _y = 0; + return BODYChunk::resetStrideRead(d); +} + /* ****************** * *** FORM Chunk *** * ****************** */ @@ -883,6 +948,8 @@ bool FORMChunk::innerReadStructure(QIODevice *d) setChunks(IFFChunk::innerFromDevice(d, &ok, this)); } else if (_type == PBM__FORM_TYPE) { setChunks(IFFChunk::innerFromDevice(d, &ok, this)); + } else if (_type == ACBM_FORM_TYPE) { + setChunks(IFFChunk::innerFromDevice(d, &ok, this)); } return ok; } diff --git a/src/imageformats/chunks_p.h b/src/imageformats/chunks_p.h index 73b1aa1..b58640f 100644 --- a/src/imageformats/chunks_p.h +++ b/src/imageformats/chunks_p.h @@ -44,6 +44,7 @@ #define PRO8_CHUNK QByteArray("PRO8") // FORM ILBM IFF +#define ABIT_CHUNK QByteArray("ABIT") #define BMHD_CHUNK QByteArray("BMHD") #define BODY_CHUNK QByteArray("BODY") #define CAMG_CHUNK QByteArray("CAMG") @@ -70,6 +71,7 @@ #define VERS_CHUNK QByteArray("VERS") #define XMP0_CHUNK QByteArray("XMP0") // https://aminet.net/package/docs/misc/IFF-metadata +#define ACBM_FORM_TYPE QByteArray("ACBM") #define ILBM_FORM_TYPE QByteArray("ILBM") #define PBM__FORM_TYPE QByteArray("PBM ") #define CIMG_FOR4_TYPE QByteArray("CIMG") @@ -573,20 +575,21 @@ public: * \return The scanline as requested for QImage. * \warning Call resetStrideRead() once before this one. */ - QByteArray strideRead(QIODevice *d, const BMHDChunk *header, const CAMGChunk *camg = nullptr, const CMAPChunk *cmap = nullptr, bool isPbm = false) const; + virtual QByteArray strideRead(QIODevice *d, const BMHDChunk *header, const CAMGChunk *camg = nullptr, const CMAPChunk *cmap = nullptr, bool isPbm = false) const; /*! * \brief resetStrideRead * Reset the stride read set the position at the beginning of the data and reset all buffers. - * \param d The device + * \param d The device. * \param header The BMHDChunk chunk (mandatory) * \param camg The CAMG chunk (optional) * \return True on success, otherwise false. * \sa strideRead + * \note Must be called once before strideRead(). */ - bool resetStrideRead(QIODevice *d) const; + virtual bool resetStrideRead(QIODevice *d) const; -private: +protected: /*! * \brief strideSize * \param isPbm Set true if the image is PBM. @@ -596,9 +599,35 @@ private: QByteArray deinterleave(const QByteArray &planes, const BMHDChunk *header, const CAMGChunk *camg = nullptr, const CMAPChunk *cmap = nullptr, bool isPbm = false) const; +private: mutable QByteArray _readBuffer; }; + +/*! + * \brief The ABITChunk class + */ +class ABITChunk : public BODYChunk +{ +public: + virtual ~ABITChunk() override; + ABITChunk(); + ABITChunk(const ABITChunk& other) = default; + ABITChunk& operator =(const ABITChunk& other) = default; + + virtual bool isValid() const override; + + CHUNKID_DEFINE(ABIT_CHUNK) + + virtual QByteArray strideRead(QIODevice *d, const BMHDChunk *header, const CAMGChunk *camg = nullptr, const CMAPChunk *cmap = nullptr, bool isPbm = false) const override; + + virtual bool resetStrideRead(QIODevice *d) const override; + +private: + mutable qint32 _y; +}; + + /*! * \brief The FORMChunk class */ diff --git a/src/imageformats/iff.cpp b/src/imageformats/iff.cpp index 48ac874..d62dfe7 100644 --- a/src/imageformats/iff.cpp +++ b/src/imageformats/iff.cpp @@ -91,6 +91,17 @@ bool IFFHandler::canRead(QIODevice *device) return false; } + // I avoid parsing obviously incorrect files + auto cid = device->peek(4); + if (cid != CAT__CHUNK && + cid != FORM_CHUNK && + cid != LIST_CHUNK && + cid != CAT4_CHUNK && + cid != FOR4_CHUNK && + cid != LIS4_CHUNK) { + return false; + } + auto ok = false; auto pos = device->pos(); auto chunks = IFFChunk::fromDevice(device, &ok); @@ -220,6 +231,11 @@ bool IFFHandler::readStandardImage(QImage *image) } auto bodies = IFFChunk::searchT(form); + if (bodies.isEmpty()) { + auto abits = IFFChunk::searchT(form); + for (auto &&abit : abits) + bodies.append(abit); + } if (bodies.isEmpty()) { img.fill(0); } else {