Decode Atari ST VDAT chunks

This commit is contained in:
Mirco Miranda
2025-09-10 14:20:47 +02:00
parent 99e4223393
commit 2410e45614
2 changed files with 179 additions and 2 deletions

View File

@ -331,6 +331,8 @@ IFFChunk::ChunkList IFFChunk::innerFromDevice(QIODevice *d, bool *ok, IFFChunk *
chunk = QSharedPointer<IFFChunk>(new SHAMChunk());
} else if (cid == TBHD_CHUNK) {
chunk = QSharedPointer<IFFChunk>(new TBHDChunk());
} else if (cid == VDAT_CHUNK) {
chunk = QSharedPointer<IFFChunk>(new VDATChunk());
} else if (cid == VERS_CHUNK) {
chunk = QSharedPointer<IFFChunk>(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<VDATChunk>(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]);
}
}

View File

@ -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
*/