diff --git a/src/imageformats/chunks.cpp b/src/imageformats/chunks.cpp index 046f078..eb37959 100644 --- a/src/imageformats/chunks.cpp +++ b/src/imageformats/chunks.cpp @@ -331,6 +331,8 @@ IFFChunk::ChunkList IFFChunk::innerFromDevice(QIODevice *d, bool *ok, IFFChunk * chunk = QSharedPointer(new SHAMChunk()); } else if (cid == TBHD_CHUNK) { chunk = QSharedPointer(new TBHDChunk()); + } else if (cid == VDAT_CHUNK) { + chunk = QSharedPointer(new VDATChunk()); } else if (cid == VERS_CHUNK) { chunk = QSharedPointer(new VERSChunk()); } else if (cid == XBMI_CHUNK) { @@ -853,6 +855,23 @@ inline qint64 rgbNDecompress(QIODevice *input, char *output, qint64 olen) return j; } + +inline qint64 vdatDecompress(const IFFChunk *chunk, const BMHDChunk *header, qint32 y, char *output, qint64 olen) +{ + auto vdats = IFFChunk::searchT(chunk); + auto rowLen = header->rowLen(); + if (olen < rowLen * vdats.size()) { + return -1; + } + for (qint32 i = 0, n = vdats.size(); i < n; ++i) { + auto&& uc = vdats.at(i)->uncompressedData(header); + if (y * rowLen > uc.size() - rowLen) + return -1; + std::memcpy(output + (i * rowLen), uc.data() + (y * rowLen), rowLen); + } + return vdats.size() * rowLen; +} + QByteArray BODYChunk::strideRead(QIODevice *d, qint32 y, const BMHDChunk *header, const CAMGChunk *camg, const CMAPChunk *cmap, const IPALChunk *ipal, const QByteArray& formType) const { if (!isValid() || header == nullptr || d == nullptr) { @@ -878,6 +897,8 @@ QByteArray BODYChunk::strideRead(QIODevice *d, qint32 y, const BMHDChunk *header // WARNING: The online spec says it's the same as TIFF but that's // not accurate: the RLE -128 code is not a noop. rr = packbitsDecompress(d, buf.data(), buf.size(), true); + } else if (header->compression() == BMHDChunk::Compression::Vdat) { + rr = vdatDecompress(this, header, y, buf.data(), buf.size()); } else if (header->compression() == BMHDChunk::Compression::RgbN8) { if (isRgb8) rr = rgb8Decompress(d, buf.data(), buf.size()); @@ -984,6 +1005,15 @@ QByteArray BODYChunk::rgbN(const QByteArray &planes, qint32, const BMHDChunk *he return planes; } +bool BODYChunk::innerReadStructure(QIODevice *d) +{ + auto ok = true; + if (d->peek(4) == VDAT_CHUNK) { + setChunks(IFFChunk::innerFromDevice(d, &ok, this)); + } + return ok; +} + QByteArray BODYChunk::deinterleave(const QByteArray &planes, qint32 y, const BMHDChunk *header, const CAMGChunk *camg, const CMAPChunk *cmap, const IPALChunk *ipal) const { if (planes.size() != strideSize(header, ILBM_FORM_TYPE)) { @@ -2371,6 +2401,123 @@ bool NAMEChunk::innerReadStructure(QIODevice *d) } +/* ****************** + * *** VDAT Chunk *** + * ****************** */ + +VDATChunk::~VDATChunk() +{ + +} + +VDATChunk::VDATChunk() +{ + +} + +bool VDATChunk::isValid() const +{ + return chunkId() == VDATChunk::defaultChunkId(); +} + +static QByteArray decompressVdat(const QByteArray &comp) +{ + QByteArray out; + auto ok = true; + auto cpos = 0; + + auto readU16BE = [&](const QByteArray &src, int &pos, bool *ok) -> quint16 { + if (pos + 2 > src.size()) { + *ok = false; + return 0; + } + auto v = quint16((quint8(src[pos]) << 8) | quint8(src[pos + 1])); + pos += 2; + return v; + }; + + auto readI8 = [&](const QByteArray &src, int &pos, bool *ok) -> qint8 { + if (pos >= src.size()) { + *ok = false; + return 0; + } + return qint8(src[pos++]); + }; + + auto emitWord = [&](quint16 w) { + out.append(char(w & 0xFF)); + out.append(char(w >> 8)); + }; + + auto cmdCnt = readU16BE(comp, cpos, &ok); + if (!ok) { + return{}; + } + + // decode command stream + auto dpos = cmdCnt + (cmdCnt & 1); + for (auto n = cmdCnt; cpos < n && dpos < comp.size() && ok;) { + auto cmd = readI8(comp, cpos, &ok); + if (cmd == 0) { + auto count = readU16BE(comp, dpos, &ok); + for (auto i = 0; i < count; ++i) + emitWord(readU16BE(comp, dpos, &ok)); + } else if (cmd == 1) { + auto count = readU16BE(comp, dpos, &ok); + auto value = readU16BE(comp, dpos, &ok); + for (auto i = 0; i < count; ++i) + emitWord(value); + } else if (cmd < 0) { + auto count = -qint32(cmd); + for (auto i = 0; i < count; ++i) + emitWord(readU16BE(comp, dpos, &ok)); + } else { + auto count = quint16(cmd); + auto value = readU16BE(comp, dpos, &ok); + for (auto i = 0; i < count; ++i) + emitWord(value); + } + if (!ok) { + return{}; + } + } + return out; +} + +static QByteArray vdatToIlbmPlane(const QByteArray &vdatData, const BMHDChunk *header) +{ + QByteArray ba(vdatData.size(), char()); + auto rowLen = header->rowLen(); + for (auto x = 0, n = 0; x < rowLen; x += 2) { + for (auto y = 0, off = x, h = header->height(); y < h; y++, off += rowLen) { + if ((off + 1 >= ba.size()) || n + 1 >= vdatData.size()) { + return{}; + } + ba[off + 1] = vdatData.at(n++); + ba[off] = vdatData.at(n++); + } + } + return ba; +} + +const QByteArray &VDATChunk::uncompressedData(const BMHDChunk *header) const +{ + if (uncompressed.isEmpty()) { + auto tmp = decompressVdat(data()); + if (tmp.size() == header->rowLen() * header->height()) { + uncompressed = vdatToIlbmPlane(tmp, header); + } + } + return uncompressed; +} + + +bool VDATChunk::innerReadStructure(QIODevice *d) +{ + return cacheData(d); +} + + /* ****************** * *** VERS Chunk *** * ****************** */ @@ -2785,7 +2932,7 @@ QByteArray IDATChunk::strideRead(QIODevice *d, qint32 y, const IHDRChunk *header } if (header->model() == IHDRChunk::Rgb555) { - for(qint32 x = 0, w = rr.size() - 1; x < w; x += 2) { + for (qint32 x = 0, w = rr.size() - 1; x < w; x += 2) { std::swap(rr[x], rr[x + 1]); } } diff --git a/src/imageformats/chunks_p.h b/src/imageformats/chunks_p.h index 94854ff..fc8c04d 100644 --- a/src/imageformats/chunks_p.h +++ b/src/imageformats/chunks_p.h @@ -84,6 +84,7 @@ Q_DECLARE_LOGGING_CATEGORY(LOG_IFFPLUGIN) #define FVER_CHUNK QByteArray("FVER") #define HIST_CHUNK QByteArray("HIST") #define NAME_CHUNK QByteArray("NAME") +#define VDAT_CHUNK QByteArray("VDAT") #define VERS_CHUNK QByteArray("VERS") #define XMP0_CHUNK QByteArray("XMP0") // https://aminet.net/package/docs/misc/IFF-metadata @@ -431,7 +432,8 @@ public: enum Compression { Uncompressed = 0, /**< Image data are uncompressed. */ Rle = 1, /**< Image data are RLE compressed. */ - RgbN8 = 4 /**< RGB8/RGBN compresson. */ + Vdat = 2, /**< Image data are VDAT compressed. */ + RgbN8 = 4 /**< Image data are RGB8/RGBN compressed. */ }; enum Masking { None = 0, /**< Designates an opaque rectangular image. */ @@ -815,6 +817,8 @@ protected: QByteArray rgbN(const QByteArray &planes, qint32 y, const BMHDChunk *header, const CAMGChunk *camg = nullptr, const CMAPChunk *cmap = nullptr, const IPALChunk *ipal = nullptr) const; + virtual bool innerReadStructure(QIODevice *d) override; + private: mutable QByteArray _readBuffer; }; @@ -1369,6 +1373,32 @@ protected: virtual bool innerReadStructure(QIODevice *d) override; }; + +/*! + * \brief The VDATChunk class + */ +class VDATChunk : public IFFChunk +{ +public: + virtual ~VDATChunk() override; + VDATChunk(); + VDATChunk(const VDATChunk& other) = default; + VDATChunk& operator =(const VDATChunk& other) = default; + + virtual bool isValid() const override; + + CHUNKID_DEFINE(VDAT_CHUNK) + + const QByteArray &uncompressedData(const BMHDChunk *header) const; + +protected: + virtual bool innerReadStructure(QIODevice *d) override; + +private: + mutable QByteArray uncompressed; +}; + + /*! * \brief The VERSChunk class */