From 0a9f9fe106368f8c21645de82c7843947d7bab77 Mon Sep 17 00:00:00 2001 From: Mirco Miranda Date: Tue, 15 Jul 2025 07:38:19 +0200 Subject: [PATCH] IFF: support for Ham8, HalfBride, Pbm and ILBM 32-bits modes --- autotests/read/iff/meta_rgba.iff | Bin 0 -> 274520 bytes autotests/read/iff/meta_rgba.iff.json | 31 ++ autotests/read/iff/meta_rgba.png | Bin 0 -> 28752 bytes autotests/read/iff/testcard_pbm.iff | Bin 0 -> 13380 bytes autotests/read/iff/testcard_pbm.png | Bin 0 -> 3296 bytes autotests/read/iff/testcard_rlepbm.iff | Bin 0 -> 6972 bytes autotests/read/iff/testcard_rlepbm.png | Bin 0 -> 3296 bytes src/imageformats/CMakeLists.txt | 2 +- src/imageformats/chunks.cpp | 520 ++++++++++++++++++++----- src/imageformats/chunks_p.h | 282 ++++++++++++-- src/imageformats/iff.cpp | 82 +++- 11 files changed, 774 insertions(+), 143 deletions(-) create mode 100644 autotests/read/iff/meta_rgba.iff create mode 100644 autotests/read/iff/meta_rgba.iff.json create mode 100644 autotests/read/iff/meta_rgba.png create mode 100644 autotests/read/iff/testcard_pbm.iff create mode 100644 autotests/read/iff/testcard_pbm.png create mode 100644 autotests/read/iff/testcard_rlepbm.iff create mode 100644 autotests/read/iff/testcard_rlepbm.png diff --git a/autotests/read/iff/meta_rgba.iff b/autotests/read/iff/meta_rgba.iff new file mode 100644 index 0000000000000000000000000000000000000000..c458503b4732e9d7d9e6741c2d45ccbee0148731 GIT binary patch literal 274520 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
X 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 literal 0 HcmV?d00001 diff --git a/autotests/read/iff/testcard_pbm.png b/autotests/read/iff/testcard_pbm.png new file mode 100644 index 0000000000000000000000000000000000000000..e97488fd4ecbd97adfdd5e34fc18ac35be0bff02 GIT binary patch literal 3296 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 literal 0 HcmV?d00001 diff --git a/autotests/read/iff/testcard_rlepbm.png b/autotests/read/iff/testcard_rlepbm.png new file mode 100644 index 0000000000000000000000000000000000000000..626622c1427a219bc96c80cc6b741989f36687fc GIT binary patch literal 3296 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 '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c == ' '))) + if (c < ' ' || c > '~') return false; } return true; @@ -58,10 +69,7 @@ bool IFFChunk::readStructure(QIODevice *d) ok = ok && innerReadStructure(d); } if (ok) { - auto pos = _dataPos + _size; - if (auto align = pos % alignBytes()) - pos += alignBytes() - align; - ok = pos < d->pos() ? false : d->seek(pos); + ok = d->seek(nextChunkPos()); } return ok; } @@ -127,18 +135,21 @@ bool IFFChunk::readInfo(QIODevice *d) QByteArray IFFChunk::readRawData(QIODevice *d, qint64 relPos, qint64 size) const { - if (!seek(d, relPos)) + if (!seek(d, relPos)) { return{}; - if (size == -1) + } + if (size == -1) { size = _size; + } auto read = std::min(size, _size - relPos); return d->read(read); } bool IFFChunk::seek(QIODevice *d, qint64 relPos) const { - if (d == nullptr) + if (d == nullptr) { return false; + } return d->seek(_dataPos + relPos); } @@ -147,6 +158,19 @@ bool IFFChunk::innerReadStructure(QIODevice *) return true; } +void IFFChunk::setAlignBytes(qint32 bytes) +{ + _align = bytes; +} + +qint64 IFFChunk::nextChunkPos() const +{ + auto pos = _dataPos + _size; + if (auto align = pos % alignBytes()) + pos += alignBytes() - align; + return pos; +} + IFFChunk::ChunkList IFFChunk::search(const QByteArray &cid, const QSharedPointer &chunk) { return search(cid, ChunkList() << chunk); @@ -165,8 +189,9 @@ IFFChunk::ChunkList IFFChunk::search(const QByteArray &cid, const ChunkList &chu bool IFFChunk::cacheData(QIODevice *d) { - if (bytes() > 8 * 1024 * 1024) + if (bytes() > 8 * 1024 * 1024) { return false; + } _data = readRawData(d); return _data.size() == _size; } @@ -186,7 +211,7 @@ void IFFChunk::setRecursionCounter(qint32 cnt) _recursionCnt = cnt; } -IFFChunk::ChunkList IFFChunk::innerFromDevice(QIODevice *d, bool *ok, qint32 alignBytes, qint32 recursionCnt) +IFFChunk::ChunkList IFFChunk::innerFromDevice(QIODevice *d, bool *ok, IFFChunk *parent) { auto tmp = false; if (ok == nullptr) { @@ -198,42 +223,63 @@ IFFChunk::ChunkList IFFChunk::innerFromDevice(QIODevice *d, bool *ok, qint32 ali return {}; } + auto alignBytes = qint32(2); + auto recursionCnt = qint32(); + auto nextChunkPos = qint64(); + if (parent) { + alignBytes = parent->alignBytes(); + recursionCnt = parent->recursionCounter(); + nextChunkPos = parent->nextChunkPos(); + } + if (recursionCnt > RECURSION_PROTECTION) { return {}; } IFFChunk::ChunkList list; - for (; !d->atEnd();) { + for (; !d->atEnd() && (nextChunkPos == 0 || d->pos() < nextChunkPos);) { auto cid = d->peek(4); QSharedPointer chunk; - if (cid == FORM_CHUNK) { - chunk = QSharedPointer(new FORMChunk()); - } else if (cid == CAMG_CHUNK) { - chunk = QSharedPointer(new CAMGChunk()); - } else if (cid == CMAP_CHUNK) { - chunk = QSharedPointer(new CMAPChunk()); + if (cid == ANNO_CHUNK) { + chunk = QSharedPointer(new ANNOChunk()); + } else if (cid == AUTH_CHUNK) { + chunk = QSharedPointer(new AUTHChunk()); } else if (cid == BMHD_CHUNK) { chunk = QSharedPointer(new BMHDChunk()); } else if (cid == BODY_CHUNK) { chunk = QSharedPointer(new BODYChunk()); - } else if (cid == DPI__CHUNK) { - chunk = QSharedPointer(new DPIChunk()); - } else if (cid == FOR4_CHUNK) { - chunk = QSharedPointer(new FOR4Chunk()); - } else if (cid == TBHD_CHUNK) { - chunk = QSharedPointer(new TBHDChunk()); - } else if (cid == RGBA_CHUNK) { - chunk = QSharedPointer(new RGBAChunk()); - } else if (cid == AUTH_CHUNK) { - chunk = QSharedPointer(new AUTHChunk()); + } else if (cid == CAMG_CHUNK) { + chunk = QSharedPointer(new CAMGChunk()); + } else if (cid == CMAP_CHUNK) { + chunk = QSharedPointer(new CMAPChunk()); + } else if (cid == COPY_CHUNK) { + chunk = QSharedPointer(new COPYChunk()); } else if (cid == DATE_CHUNK) { chunk = QSharedPointer(new DATEChunk()); + } else if (cid == DPI__CHUNK) { + chunk = QSharedPointer(new DPIChunk()); + } else if (cid == EXIF_CHUNK) { + chunk = QSharedPointer(new EXIFChunk()); + } else if (cid == FOR4_CHUNK) { + chunk = QSharedPointer(new FOR4Chunk()); + } else if (cid == FORM_CHUNK) { + chunk = QSharedPointer(new FORMChunk()); } else if (cid == FVER_CHUNK) { chunk = QSharedPointer(new FVERChunk()); } else if (cid == HIST_CHUNK) { chunk = QSharedPointer(new HISTChunk()); + } else if (cid == ICCP_CHUNK) { + chunk = QSharedPointer(new ICCPChunk()); + } else if (cid == NAME_CHUNK) { + chunk = QSharedPointer(new NAMEChunk()); + } else if (cid == RGBA_CHUNK) { + chunk = QSharedPointer(new RGBAChunk()); + } else if (cid == TBHD_CHUNK) { + chunk = QSharedPointer(new TBHDChunk()); } else if (cid == VERS_CHUNK) { chunk = QSharedPointer(new VERSChunk()); + } else if (cid == XMP0_CHUNK) { + chunk = QSharedPointer(new XMP0Chunk()); } else { // unknown chunk chunk = QSharedPointer(new IFFChunk()); qInfo() << "IFFChunk::innerFromDevice: unkwnown chunk" << cid; @@ -256,6 +302,12 @@ IFFChunk::ChunkList IFFChunk::innerFromDevice(QIODevice *d, bool *ok, qint32 ali return {}; } + // skip any non-IFF data at the end of the file. + // NOTE: there should be no more chunks after the first (root) + if (nextChunkPos == 0) { + nextChunkPos = chunk->nextChunkPos(); + } + list << chunk; } @@ -265,7 +317,7 @@ IFFChunk::ChunkList IFFChunk::innerFromDevice(QIODevice *d, bool *ok, qint32 ali IFFChunk::ChunkList IFFChunk::fromDevice(QIODevice *d, bool *ok) { - return innerFromDevice(d, ok, 2, 0); + return innerFromDevice(d, ok, nullptr); } @@ -340,12 +392,12 @@ quint8 BMHDChunk::bitplanes() const return quint8(data().at(8)); } -quint8 BMHDChunk::masking() const +BMHDChunk::Masking BMHDChunk::masking() const { if (!isValid()) { - return 0; + return BMHDChunk::Masking::None; } - return quint8(data().at(9)); + return BMHDChunk::Masking(quint8(data().at(9))); } BMHDChunk::Compression BMHDChunk::compression() const @@ -357,14 +409,6 @@ BMHDChunk::Compression BMHDChunk::compression() const } -quint8 BMHDChunk::padding() const -{ - if (!isValid()) { - return 0; - } - return quint8(data().at(11)); -} - qint16 BMHDChunk::transparency() const { if (!isValid()) { @@ -547,13 +591,13 @@ bool BODYChunk::isValid() const return chunkId() == BODYChunk::defaultChunkId(); } -QByteArray BODYChunk::strideRead(QIODevice *d, const BMHDChunk *header, const CAMGChunk *camg, const CMAPChunk *cmap) const +QByteArray BODYChunk::strideRead(QIODevice *d, const BMHDChunk *header, const CAMGChunk *camg, const CMAPChunk *cmap, bool isPbm) const { if (!isValid() || header == nullptr) { return {}; } - auto readSize = header->rowLen() * header->bitplanes(); + auto readSize = strideSize(header, isPbm); for(;!d->atEnd() && _readBuffer.size() < readSize;) { QByteArray buf(readSize, char()); qint64 rr = -1; @@ -562,7 +606,7 @@ QByteArray BODYChunk::strideRead(QIODevice *d, const BMHDChunk *header, const CA // not accurate: the RLE -128 code is not a noop. rr = packbitsDecompress(d, buf.data(), buf.size(), true); } else if (header->compression() == BMHDChunk::Compression::Uncompressed) { - rr = d->read(buf.data(), buf.size()); + rr = d->read(buf.data(), buf.size()); // never seen } if (rr != readSize) return {}; @@ -571,7 +615,7 @@ QByteArray BODYChunk::strideRead(QIODevice *d, const BMHDChunk *header, const CA auto planes = _readBuffer.left(readSize); _readBuffer.remove(0, readSize); - return BODYChunk::deinterleave(planes, header, camg, cmap); + return deinterleave(planes, header, camg, cmap, isPbm); } bool BODYChunk::resetStrideRead(QIODevice *d) const @@ -580,14 +624,31 @@ bool BODYChunk::resetStrideRead(QIODevice *d) const return seek(d); } -QByteArray BODYChunk::deinterleave(const QByteArray &planes, const BMHDChunk *header, const CAMGChunk *camg, const CMAPChunk *cmap) +quint32 BODYChunk::strideSize(const BMHDChunk *header, bool isPbm) const { - auto rowLen = qint32(header->rowLen()); - auto bitplanes = header->bitplanes(); - if (planes.size() != rowLen * bitplanes) { + auto rs = header->rowLen() * header->bitplanes(); + if (!isPbm) { + return rs; + } + + // I found two versions of PBM: one uses ILBM calculation, the other uses width-based. + // As it is a proprietary extension, one of them was probably generated incorrectly. + if (header->compression() == BMHDChunk::Compression::Uncompressed) { + if (rs * header->height() != bytes()) + rs = header->width() * header->bitplanes() / 8; + } + + return rs; +} + +QByteArray BODYChunk::deinterleave(const QByteArray &planes, const BMHDChunk *header, const CAMGChunk *camg, const CMAPChunk *cmap, bool isPbm) const +{ + if (planes.size() != strideSize(header, isPbm)) { return {}; } + auto rowLen = qint32(header->rowLen()); + auto bitplanes = header->bitplanes(); auto modeId = CAMGChunk::ModeIds(); if (camg) { modeId = camg->modeId(); @@ -609,7 +670,10 @@ QByteArray BODYChunk::deinterleave(const QByteArray &planes, const BMHDChunk *he case 6: case 7: case 8: - if (modeId == CAMGChunk::ModeId::Ham && cmap && bitplanes == 6) { + if (isPbm && bitplanes == 8) { + // The data are contiguous. + ba = planes; + } else if ((modeId & CAMGChunk::ModeId::Ham) && (cmap) && (bitplanes >= 5 && bitplanes <= 8)) { // From A Quick Introduction to IFF.txt: // // Amiga HAM (Hold and Modify) mode lets the Amiga display all 4096 RGB values. @@ -631,6 +695,7 @@ QByteArray BODYChunk::deinterleave(const QByteArray &planes, const BMHDChunk *he // 11 - hold previous. replacing Green component with bits from planes 0-3 ba = QByteArray(rowLen * 8 * 3, char()); auto pal = cmap->palette(); + auto max = (1 << (bitplanes - 2)) - 1; quint8 prev[3] = {}; for (qint32 i = 0, cnt = 0; i < rowLen; ++i) { for (qint32 j = 0; j < 8; ++j, ++cnt) { @@ -638,21 +703,20 @@ QByteArray BODYChunk::deinterleave(const QByteArray &planes, const BMHDChunk *he for (qint32 k = 0, msk = (1 << (7 - j)); k < bitplanes; ++k) { if ((planes.at(k * rowLen + i) & msk) == 0) continue; - if (k < 4) { + if (k < bitplanes - 2) idx |= 1 << k; - } else { + else ctl |= 1 << (bitplanes - k - 1); - } } switch (ctl) { case 1: // red - prev[0] = idx | (idx << 4); + prev[0] = idx * 255 / max; break; case 2: // blue - prev[2] = idx | (idx << 4); + prev[2] = idx * 255 / max; break; case 3: // green - prev[1] = idx | (idx << 4); + prev[1] = idx * 255 / max; break; default: if (idx < pal.size()) { @@ -670,7 +734,38 @@ QByteArray BODYChunk::deinterleave(const QByteArray &planes, const BMHDChunk *he ba[cnt3 + 2] = char(prev[2]); } } - } else if (modeId == CAMGChunk::ModeIds()) { + } else if ((modeId & CAMGChunk::ModeId::HalfBrite) && (cmap)) { + // In HALFBRITE mode, the Amiga interprets the bit in the + // last plane as HALFBRITE modification. The bits in the other planes are + // treated as normal color register numbers (RGB values for each color register + // is specified in the CMAP chunk). If the bit in the last plane is set (1), + // then that pixel is displayed at half brightness. This can provide up to 64 + // absolute colors. + ba = QByteArray(rowLen * 8 * 3, char()); + auto pal = cmap->palette(); + for (qint32 i = 0, cnt = 0; i < rowLen; ++i) { + for (qint32 j = 0; j < 8; ++j, ++cnt) { + quint8 idx = 0, ctl = 0; + for (qint32 k = 0, msk = (1 << (7 - j)); k < bitplanes; ++k) { + if ((planes.at(k * rowLen + i) & msk) == 0) + continue; + if (k < bitplanes - 1) + idx |= 1 << k; + else + ctl = 1; + } + if (idx < pal.size()) { + auto cnt3 = cnt * 3; + auto div = ctl ? 2 : 1; + ba[cnt3] = qRed(pal.at(idx)) / div; + ba[cnt3 + 1] = qGreen(pal.at(idx)) / div; + ba[cnt3 + 2] = qBlue(pal.at(idx)) / div; + } else { + qWarning() << "BODYChunk::deinterleave: palette index" << idx << "is out of range"; + } + } + } + } else { // From A Quick Introduction to IFF.txt: // // If the ILBM is not HAM or HALFBRITE, then after parsing and uncompacting if @@ -709,6 +804,12 @@ QByteArray BODYChunk::deinterleave(const QByteArray &planes, const BMHDChunk *he break; case 24: // rgb + case 32: // rgba + if (isPbm) { + // TODO: no testcase found + break; + } + // From A Quick Introduction to IFF.txt: // // If a deep ILBM (like 12 or 24 planes), there should be no CMAP @@ -775,8 +876,10 @@ bool FORMChunk::innerReadStructure(QIODevice *d) } _type = d->read(4); auto ok = true; - if (_type == QByteArray("ILBM")) { - setChunks(IFFChunk::innerFromDevice(d, &ok, alignBytes(), recursionCounter())); + if (_type == ILBM_FORM_TYPE) { + setChunks(IFFChunk::innerFromDevice(d, &ok, this)); + } else if (_type == PBM__FORM_TYPE) { + setChunks(IFFChunk::innerFromDevice(d, &ok, this)); } return ok; } @@ -805,25 +908,28 @@ QImage::Format FORMChunk::format() const // assume HAM and you'll probably be right. modeId = CAMGChunk::ModeIds(CAMGChunk::ModeId::Ham); } - if (h->bitplanes() == 24) { return QImage::Format_RGB888; } + if (h->bitplanes() == 32) { + return QImage::Format_RGBA8888; + } if (h->bitplanes() >= 2 && h->bitplanes() <= 8) { - // Currently supported modes: HAM6 and No HAM/HALFBRITE. - if (modeId != CAMGChunk::ModeIds() && (modeId != CAMGChunk::ModeId::Ham || h->bitplanes() != 6)) + if (!IFFChunk::search(SHAM_CHUNK, chunks()).isEmpty() || !IFFChunk::search(CTBL_CHUNK, chunks()).isEmpty()) { + // Images with the SHAM or CTBL chunk do not load correctly: it seems they contains + // a color table but I didn't find any specs. return QImage::Format_Invalid; - - if (modeId & CAMGChunk::ModeId::Ham) { - if (IFFChunk::search(SHAM_CHUNK, chunks()).isEmpty()) - return QImage::Format_RGB888; - else // Images with the SHAM chunk do not load correctly. - return QImage::Format_Invalid; - } else if (!cmaps.isEmpty()) { - return QImage::Format_Indexed8; - } else { - return QImage::Format_Grayscale8; } + + if (modeId & (CAMGChunk::ModeId::Ham | CAMGChunk::ModeId::HalfBrite)) { + return QImage::Format_RGB888; + } + + if (!cmaps.isEmpty()) { + return QImage::Format_Indexed8; + } + + return QImage::Format_Grayscale8; } if (h->bitplanes() == 1) { return QImage::Format_Mono; @@ -878,10 +984,10 @@ bool FOR4Chunk::innerReadStructure(QIODevice *d) } _type = d->read(4); auto ok = true; - if (_type == QByteArray("CIMG")) { - setChunks(IFFChunk::innerFromDevice(d, &ok, alignBytes(), recursionCounter())); - } else if (_type == QByteArray("TBMP")) { - setChunks(IFFChunk::innerFromDevice(d, &ok, alignBytes(), recursionCounter())); + if (_type == CIMG_FOR4_TYPE) { + setChunks(IFFChunk::innerFromDevice(d, &ok, this)); + } else if (_type == TBMP_FOR4_TYPE) { + setChunks(IFFChunk::innerFromDevice(d, &ok, this)); } return ok; } @@ -1076,16 +1182,20 @@ bool RGBAChunk::isTileCompressed(const TBHDChunk *header) const QPoint RGBAChunk::pos() const { - return _pos; + return _posPx; } QSize RGBAChunk::size() const { - return _size; + return _sizePx; } // Maya version of IFF uses a slightly different algorithm for RLE compression. -qint64 rleMayaDecompress(QIODevice *input, char *output, qint64 olen) +// To understand how it works I saved images with regular patterns from Photoshop +// and then checked the data. It is basically the same as packbits except for how +// the length is extracted: I don't know if it's a standard variant or not, so +// I'm keeping it private. +inline qint64 rleMayaDecompress(QIODevice *input, char *output, qint64 olen) { qint64 j = 0; for (qint64 rr = 0, available = olen; j < olen; available = olen - j) { @@ -1096,7 +1206,7 @@ qint64 rleMayaDecompress(QIODevice *input, char *output, qint64 olen) if (input->peek(&n, 1) != 1) { // end of data (or error) break; } - rr = qint64(n & 0x7f) + 1; + rr = qint64(n & 0x7F) + 1; if (rr > available) break; } @@ -1106,7 +1216,7 @@ qint64 rleMayaDecompress(QIODevice *input, char *output, qint64 olen) break; } - rr = qint64(n & 0x7f) + 1; + rr = qint64(n & 0x7F) + 1; if ((n & 0x80) == 0) { auto read = input->read(output + j, rr); if (rr != read) { @@ -1132,20 +1242,26 @@ QByteArray RGBAChunk::readStride(QIODevice *d, const TBHDChunk *header) const return {}; } - // detect if the tile is compressed (8 is the size of 4 uint16 before the tile data). - auto compressed = isTileCompressed(header); + // It seems that tiles are compressed independently only if there is space savings. + // The compression method specified in the header is only to indicate the type of + // compression if used. + if (!isTileCompressed(header)) { + // when not compressed, the line contains all channels + readSize *= header->bpc() * header->channels(); + QByteArray buf(readSize, char()); + auto rr = d->read(buf.data(), buf.size()); + if (rr != buf.size()) { + return {}; + } + return buf; + } + + // compressed for(;!d->atEnd() && _readBuffer.size() < readSize;) { QByteArray buf(readSize * size().height(), char()); qint64 rr = -1; - if (compressed) { - // It seems that tiles are compressed independently only if there is space savings. - // The compression method specified in the header is only to indicate the type of - // compression if used. - if (header->compression() == TBHDChunk::Compression::Rle) { - rr = rleMayaDecompress(d, buf.data(), buf.size()); - } - } else { - rr = d->read(buf.data(), buf.size()); + if (header->compression() == TBHDChunk::Compression::Rle) { + rr = rleMayaDecompress(d, buf.data(), buf.size()); } if (rr != buf.size()) { return {}; @@ -1159,6 +1275,18 @@ QByteArray RGBAChunk::readStride(QIODevice *d, const TBHDChunk *header) const return buff; } +/*! + * \brief compressedTile + * + * The compressed tile contains compressed data per channel. + * + * If 16 bit, high and low bytes are treated separately (so I have + * channels * 2 compressed data blocks). First the high ones, then the low + * ones (or vice versa): for the reconstruction I went by trial and error :) + * \param d The device + * \param header The header. + * \return The tile as Qt image. + */ QImage RGBAChunk::compressedTile(QIODevice *d, const TBHDChunk *header) const { QImage img(size(), header->format()); @@ -1184,7 +1312,7 @@ QImage RGBAChunk::compressedTile(QIODevice *d, const TBHDChunk *header) const } for (auto c = 0, cc = header->channels() * header->bpc(); c < cc; ++c) { #if Q_BYTE_ORDER == Q_BIG_ENDIAN - auto c_bcp = c / cs; + auto c_bcp = c / cs; // Not tried #else auto c_bcp = 1 - c / cs; #endif @@ -1205,6 +1333,15 @@ QImage RGBAChunk::compressedTile(QIODevice *d, const TBHDChunk *header) const return img; } +/*! + * \brief RGBAChunk::uncompressedTile + * + * The uncompressed tile scanline contains the data in + * B0 G0 R0 A0 B1 G1 R1 A1... Bn Gn Rn An format. + * \param d The device + * \param header The header. + * \return The tile as Qt image. + */ QImage RGBAChunk::uncompressedTile(QIODevice *d, const TBHDChunk *header) const { QImage img(size(), header->format()); @@ -1212,10 +1349,8 @@ QImage RGBAChunk::uncompressedTile(QIODevice *d, const TBHDChunk *header) const if (bpc == 1) { auto cs = header->channels(); - auto lineSize = img.width() * bpc * cs; - for (auto y = 0, h = img.height(); y < h; ++y) { - auto ba = d->read(lineSize); + auto ba = readStride(d, header); if (ba.isEmpty()) { return {}; } @@ -1229,13 +1364,12 @@ QImage RGBAChunk::uncompressedTile(QIODevice *d, const TBHDChunk *header) const } } else if (bpc == 2) { auto cs = header->channels(); - auto lineSize = img.width() * bpc * cs; if (cs < 4) { // alpha on 64-bit images must be 0xFF std::memset(img.bits(), 0xFF, img.sizeInBytes()); } for (auto y = 0, h = img.height(); y < h; ++y) { - auto ba = d->read(lineSize); + auto ba = readStride(d, header); if (ba.isEmpty()) { return {}; } @@ -1246,7 +1380,7 @@ QImage RGBAChunk::uncompressedTile(QIODevice *d, const TBHDChunk *header) const auto xcs = x * cs; auto xcs4 = x * 4; #if Q_BYTE_ORDER == Q_BIG_ENDIAN - scl[xcs4 + cs - c - 1] = src[xcs + c]; + scl[xcs4 + cs - c - 1] = src[xcs + c]; // Not tried #else scl[xcs4 + cs - c - 1] = (src[xcs + c] >> 8) | (src[xcs + c] << 8); #endif @@ -1289,12 +1423,42 @@ bool RGBAChunk::innerReadStructure(QIODevice *d) return false; } - _pos = QPoint(x0, y0); - _size = QSize(qint32(x1) - x0 + 1, qint32(y1) - y0 + 1); + _posPx = QPoint(x0, y0); + _sizePx = QSize(qint32(x1) - x0 + 1, qint32(y1) - y0 + 1); return true; } + +/* ****************** + * *** ANNO Chunk *** + * ****************** */ + +ANNOChunk::~ANNOChunk() +{ + +} + +ANNOChunk::ANNOChunk() +{ + +} + +bool ANNOChunk::isValid() const +{ + return chunkId() == AUTHChunk::defaultChunkId(); +} + +QString ANNOChunk::value() const +{ + return QString::fromLatin1(data()).replace(QStringLiteral("\0"), QStringLiteral(" ")).trimmed(); +} + +bool ANNOChunk::innerReadStructure(QIODevice *d) +{ + return cacheData(d); +} + /* ****************** * *** AUTH Chunk *** * ****************** */ @@ -1316,7 +1480,7 @@ bool AUTHChunk::isValid() const QString AUTHChunk::value() const { - return QString::fromLatin1(data()); + return QString::fromLatin1(data()).replace(QStringLiteral("\0"), QStringLiteral(" ")).trimmed(); } bool AUTHChunk::innerReadStructure(QIODevice *d) @@ -1324,6 +1488,37 @@ bool AUTHChunk::innerReadStructure(QIODevice *d) return cacheData(d); } + +/* ****************** + * *** COPY Chunk *** + * ****************** */ + +COPYChunk::~COPYChunk() +{ + +} + +COPYChunk::COPYChunk() +{ + +} + +bool COPYChunk::isValid() const +{ + return chunkId() == COPYChunk::defaultChunkId(); +} + +QString COPYChunk::value() const +{ + return QString::fromLatin1(data()).replace(QStringLiteral("\0"), QStringLiteral(" ")).trimmed(); +} + +bool COPYChunk::innerReadStructure(QIODevice *d) +{ + return cacheData(d); +} + + /* ****************** * *** DATE Chunk *** * ****************** */ @@ -1353,6 +1548,69 @@ bool DATEChunk::innerReadStructure(QIODevice *d) return cacheData(d); } + +/* ****************** + * *** EXIF Chunk *** + * ****************** */ + +EXIFChunk::~EXIFChunk() +{ + +} + +EXIFChunk::EXIFChunk() +{ + +} + +bool EXIFChunk::isValid() const +{ + if (!data().startsWith(QByteArray("Exif\0\0"))) { + return false; + } + return chunkId() == EXIFChunk::defaultChunkId(); +} + +MicroExif EXIFChunk::value() const +{ + return MicroExif::fromByteArray(data().mid(6)); +} + +bool EXIFChunk::innerReadStructure(QIODevice *d) +{ + return cacheData(d); +} + + +/* ****************** + * *** ICCP Chunk *** + * ****************** */ + +ICCPChunk::~ICCPChunk() +{ + +} + +ICCPChunk::ICCPChunk() +{ + +} + +bool ICCPChunk::isValid() const +{ + return chunkId() == ICCPChunk::defaultChunkId(); +} + +QColorSpace ICCPChunk::value() const +{ + return QColorSpace::fromIccProfile(data()); +} + +bool ICCPChunk::innerReadStructure(QIODevice *d) +{ + return cacheData(d); +} + /* ****************** * *** FVER Chunk *** * ****************** */ @@ -1406,6 +1664,37 @@ bool HISTChunk::innerReadStructure(QIODevice *d) return cacheData(d); } + +/* ****************** + * *** NAME Chunk *** + * ****************** */ + +NAMEChunk::~NAMEChunk() +{ + +} + +NAMEChunk::NAMEChunk() +{ + +} + +bool NAMEChunk::isValid() const +{ + return chunkId() == NAMEChunk::defaultChunkId(); +} + +QString NAMEChunk::value() const +{ + return QString::fromLatin1(data()).replace(QStringLiteral("\0"), QStringLiteral(" ")).trimmed(); +} + +bool NAMEChunk::innerReadStructure(QIODevice *d) +{ + return cacheData(d); +} + + /* ****************** * *** VERS Chunk *** * ****************** */ @@ -1434,3 +1723,34 @@ bool VERSChunk::innerReadStructure(QIODevice *d) { return cacheData(d); } + + +/* ****************** + * *** XMP0 Chunk *** + * ****************** */ + +XMP0Chunk::~XMP0Chunk() +{ + +} + +XMP0Chunk::XMP0Chunk() +{ + +} + +bool XMP0Chunk::isValid() const +{ + return chunkId() == XMP0Chunk::defaultChunkId(); +} + +QString XMP0Chunk::value() const +{ + return QString::fromUtf8(data()).replace(QStringLiteral("\0"), QStringLiteral(" ")).trimmed(); +} + +bool XMP0Chunk::innerReadStructure(QIODevice *d) +{ + return cacheData(d); +} + diff --git a/src/imageformats/chunks_p.h b/src/imageformats/chunks_p.h index c77deaa..73b1aa1 100644 --- a/src/imageformats/chunks_p.h +++ b/src/imageformats/chunks_p.h @@ -9,6 +9,7 @@ * Format specifications: * - https://wiki.amigaos.net/wiki/IFF_FORM_and_Chunk_Registry * - https://www.fileformat.info/format/iff/egff.htm + * - https://download.autodesk.com/us/maya/2010help/index.html (Developer resources -> File formats -> Maya IFF) */ #ifndef KIMG_CHUNKS_P_H @@ -22,6 +23,8 @@ #include #include +#include "microexif_p.h" + // Main chunks (Standard) #define CAT__CHUNK QByteArray("CAT ") #define FILL_CHUNK QByteArray(" ") @@ -30,7 +33,15 @@ #define PROP_CHUNK QByteArray("PROP") // Main chuncks (Maya) +#define CAT4_CHUNK QByteArray("CAT4") // 4 byte alignment #define FOR4_CHUNK QByteArray("FOR4") +#define LIS4_CHUNK QByteArray("LIS4") +#define PRO4_CHUNK QByteArray("PRO4") + +#define CAT8_CHUNK QByteArray("CAT8") // 8 byte alignment (never seen) +#define FOR8_CHUNK QByteArray("FOR8") +#define LIS8_CHUNK QByteArray("LIS8") +#define PRO8_CHUNK QByteArray("PRO8") // FORM ILBM IFF #define BMHD_CHUNK QByteArray("BMHD") @@ -38,6 +49,8 @@ #define CAMG_CHUNK QByteArray("CAMG") #define CMAP_CHUNK QByteArray("CMAP") #define DPI__CHUNK QByteArray("DPI ") + +#define CTBL_CHUNK QByteArray("CTBL") // undocumented #define SHAM_CHUNK QByteArray("SHAM") // undocumented // FOR4 CIMG IFF (Maya) @@ -45,11 +58,22 @@ #define TBHD_CHUNK QByteArray("TBHD") // FORx IFF (found on some IFF format specs) +#define ANNO_CHUNK QByteArray("ANNO") #define AUTH_CHUNK QByteArray("AUTH") +#define COPY_CHUNK QByteArray("(c) ") #define DATE_CHUNK QByteArray("DATE") +#define EXIF_CHUNK QByteArray("EXIF") // https://aminet.net/package/docs/misc/IFF-metadata +#define ICCP_CHUNK QByteArray("ICCP") // https://aminet.net/package/docs/misc/IFF-metadata #define FVER_CHUNK QByteArray("FVER") #define HIST_CHUNK QByteArray("HIST") +#define NAME_CHUNK QByteArray("NAME") #define VERS_CHUNK QByteArray("VERS") +#define XMP0_CHUNK QByteArray("XMP0") // https://aminet.net/package/docs/misc/IFF-metadata + +#define ILBM_FORM_TYPE QByteArray("ILBM") +#define PBM__FORM_TYPE QByteArray("PBM ") +#define CIMG_FOR4_TYPE QByteArray("CIMG") +#define TBMP_FOR4_TYPE QByteArray("TBMP") #define CHUNKID_DEFINE(a) static QByteArray defaultChunkId() { return a; } @@ -195,15 +219,18 @@ public: template static QList searchT(const IFFChunk *chunk) { QList list; - if (chunk == nullptr) + if (chunk == nullptr) { return list; + } auto cid = T::defaultChunkId(); - if (chunk->chunkId() == cid) + if (chunk->chunkId() == cid) { if (auto c = dynamic_cast(chunk)) list << c; + } auto tmp = chunk->chunks(); - for (auto &&c : tmp) + for (auto &&c : tmp) { list << searchT(c.data()); + } return list; } @@ -216,8 +243,9 @@ public: template static QList searchT(const ChunkList& chunks) { QList list; - for (auto &&chunk : chunks) + for (auto &&chunk : chunks) { list << searchT(chunk.data()); + } return list; } @@ -236,11 +264,14 @@ protected: * \brief setAlignBytes * \param bytes */ - void setAlignBytes(qint32 bytes) - { - _align = bytes; - } + void setAlignBytes(qint32 bytes); + /*! + * \brief nextChunkPos + * Calculates the position of the next chunk. The position is already aligned. + * \return The position of the next chunk from the beginning of the stream. + */ + qint64 nextChunkPos() const; /*! * \brief cacheData @@ -280,7 +311,7 @@ protected: return qint32(ui32(c1, c2, c3, c4)); } - static ChunkList innerFromDevice(QIODevice *d, bool *ok, qint32 alignBytes, qint32 recursionCnt); + static ChunkList innerFromDevice(QIODevice *d, bool *ok, IFFChunk *parent = nullptr); private: char _chunkId[4]; @@ -296,20 +327,31 @@ private: ChunkList _chunks; qint32 _recursionCnt; - - }; /*! - * \brief The IffBMHD class + * \brief The BMHDChunk class * Bitmap Header */ class BMHDChunk: public IFFChunk { public: enum Compression { - Uncompressed = 0, - Rle = 1 + Uncompressed = 0, /**< Image data are uncompressed. */ + Rle = 1 /**< Image data are RLE compressed. */ + }; + enum Masking { + None = 0, /**< Designates an opaque rectangular image. */ + HasMask = 1, /**< A mask plane is interleaved with the bitplanes in the BODY chunk. */ + HasTransparentColor = 2, /**< Pixels in the source planes matching transparentColor + are to be considered “transparent”. (Actually, transparentColor + isn’t a “color number” since it’s matched with numbers formed + by the source bitmap rather than the possibly deeper destination + bitmap. Note that having a transparent color implies ignoring + one of the color registers. */ + Lasso = 3 /**< The reader may construct a mask by lassoing the image as in MacPaint. + To do this, put a 1 pixel border of transparentColor around the image rectangle. + Then do a seed fill from this border. Filled pixels are to be transparent. */ }; virtual ~BMHDChunk() override; @@ -320,34 +362,88 @@ public: virtual bool isValid() const override; + /*! + * \brief width + * \return Width of the bitmap in pixels. + */ qint32 width() const; + /*! + * \brief height + * \return Height of the bitmap in pixels. + */ qint32 height() const; + /*! + * \brief size + * \return Size in pixels. + */ QSize size() const; + /*! + * \brief left + * \return The left position of the image. + */ qint32 left() const; + /*! + * \brief top + * \return The top position of the image. + */ qint32 top() const; + /*! + * \brief bitplanes + * \return The number of bit planes. + */ quint8 bitplanes() const; - quint8 masking() const; + /*! + * \brief masking + * \return Kind of masking is to be used for this image. + */ + Masking masking() const; + /*! + * \brief compression + * \return The type of compression used. + */ Compression compression() const; - quint8 padding() const; - + /*! + * \brief transparency + * \return Transparent "color number". + */ qint16 transparency() const; + /*! + * \brief xAspectRatio + * \return X pixel aspect. + */ quint8 xAspectRatio() const; + /*! + * \brief yAspectRatio + * \return Y pixel aspect. + */ quint8 yAspectRatio() const; + /*! + * \brief pageWidth + * \return Source "page" width in pixels. + */ quint16 pageWidth() const; + /*! + * \brief pageHeight + * \return Source "page" height in pixels. + */ quint16 pageHeight() const; + /*! + * \brief rowLen + * \return The row len of a plane. + */ quint32 rowLen() const; CHUNKID_DEFINE(BMHD_CHUNK) @@ -473,10 +569,11 @@ public: * \param header The bitmap header. * \param camg The CAMG chunk (optional) * \param cmap The CMAP chunk (optional) + * \param isPbm Set to true if the formType() == "PBM " * \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) const; + QByteArray strideRead(QIODevice *d, const BMHDChunk *header, const CAMGChunk *camg = nullptr, const CMAPChunk *cmap = nullptr, bool isPbm = false) const; /*! * \brief resetStrideRead @@ -490,7 +587,14 @@ public: bool resetStrideRead(QIODevice *d) const; private: - static QByteArray deinterleave(const QByteArray &planes, const BMHDChunk *header, const CAMGChunk *camg = nullptr, const CMAPChunk *cmap = nullptr); + /*! + * \brief strideSize + * \param isPbm Set true if the image is PBM. + * \return The size of data to have to decode an image row. + */ + quint32 strideSize(const BMHDChunk *header, bool isPbm) const; + + QByteArray deinterleave(const QByteArray &planes, const BMHDChunk *header, const CAMGChunk *camg = nullptr, const CMAPChunk *cmap = nullptr, bool isPbm = false) const; mutable QByteArray _readBuffer; }; @@ -563,12 +667,11 @@ class TBHDChunk : public IFFChunk { public: enum Flag { - Rgb = 0x01, - Alpha = 0x02, - ZBuffer = 0x04, - Black = 0x10, + Rgb = 0x01, /**< RGB image */ + Alpha = 0x02, /**< Image contains alpha channel */ + ZBuffer = 0x04, /**< If the image has a z-buffer, it is described by ZBUF blocks with the same structure as the RGBA blocks, RLE encoded. */ - RgbA = Rgb | Alpha + RgbA = Rgb | Alpha /**< RGBA image */ }; Q_DECLARE_FLAGS(Flags, Flag) @@ -715,13 +818,33 @@ private: QByteArray readStride(QIODevice *d, const TBHDChunk *header) const; private: - QPoint _pos; + QPoint _posPx; - QSize _size; + QSize _sizePx; mutable QByteArray _readBuffer; }; +/*! + * \brief The ANNOChunk class + */ +class ANNOChunk : public IFFChunk +{ +public: + virtual ~ANNOChunk() override; + ANNOChunk(); + ANNOChunk(const ANNOChunk& other) = default; + ANNOChunk& operator =(const ANNOChunk& other) = default; + + virtual bool isValid() const override; + + QString value() const; + + CHUNKID_DEFINE(ANNO_CHUNK) + +protected: + virtual bool innerReadStructure(QIODevice *d) override; +}; /*! * \brief The AUTHChunk class @@ -744,6 +867,27 @@ protected: virtual bool innerReadStructure(QIODevice *d) override; }; +/*! + * \brief The COPYChunk class + */ +class COPYChunk : public IFFChunk +{ +public: + virtual ~COPYChunk() override; + COPYChunk(); + COPYChunk(const COPYChunk& other) = default; + COPYChunk& operator =(const COPYChunk& other) = default; + + virtual bool isValid() const override; + + QString value() const; + + CHUNKID_DEFINE(COPY_CHUNK) + +protected: + virtual bool innerReadStructure(QIODevice *d) override; +}; + /*! * \brief The DATEChunk class */ @@ -765,6 +909,49 @@ protected: virtual bool innerReadStructure(QIODevice *d) override; }; +/*! + * \brief The EXIFChunk class + */ +class EXIFChunk : public IFFChunk +{ +public: + virtual ~EXIFChunk() override; + EXIFChunk(); + EXIFChunk(const EXIFChunk& other) = default; + EXIFChunk& operator =(const EXIFChunk& other) = default; + + virtual bool isValid() const override; + + MicroExif value() const; + + CHUNKID_DEFINE(EXIF_CHUNK) + +protected: + virtual bool innerReadStructure(QIODevice *d) override; +}; + +/*! + * \brief The ICCPChunk class + */ +class ICCPChunk : public IFFChunk +{ +public: + virtual ~ICCPChunk() override; + ICCPChunk(); + ICCPChunk(const ICCPChunk& other) = default; + ICCPChunk& operator =(const ICCPChunk& other) = default; + + virtual bool isValid() const override; + + QColorSpace value() const; + + CHUNKID_DEFINE(ICCP_CHUNK) + +protected: + virtual bool innerReadStructure(QIODevice *d) override; +}; + + /*! * \brief The FVERChunk class * @@ -809,6 +996,27 @@ protected: }; +/*! + * \brief The NAMEChunk class + */ +class NAMEChunk : public IFFChunk +{ +public: + virtual ~NAMEChunk() override; + NAMEChunk(); + NAMEChunk(const NAMEChunk& other) = default; + NAMEChunk& operator =(const NAMEChunk& other) = default; + + virtual bool isValid() const override; + + QString value() const; + + CHUNKID_DEFINE(NAME_CHUNK) + +protected: + virtual bool innerReadStructure(QIODevice *d) override; +}; + /*! * \brief The VERSChunk class */ @@ -830,4 +1038,26 @@ protected: virtual bool innerReadStructure(QIODevice *d) override; }; + +/*! + * \brief The XMP0Chunk class + */ +class XMP0Chunk : public IFFChunk +{ +public: + virtual ~XMP0Chunk() override; + XMP0Chunk(); + XMP0Chunk(const XMP0Chunk& other) = default; + XMP0Chunk& operator =(const XMP0Chunk& other) = default; + + virtual bool isValid() const override; + + QString value() const; + + CHUNKID_DEFINE(XMP0_CHUNK) + +protected: + virtual bool innerReadStructure(QIODevice *d) override; +}; + #endif // KIMG_CHUNKS_P_H diff --git a/src/imageformats/iff.cpp b/src/imageformats/iff.cpp index 2ffed32..ff6186d 100644 --- a/src/imageformats/iff.cpp +++ b/src/imageformats/iff.cpp @@ -107,11 +107,12 @@ bool IFFHandler::canRead(QIODevice *device) void addMetadata(QImage& img, const IFFChunk *form) { - auto dates = IFFChunk::searchT(form); - if (!dates.isEmpty()) { - auto dt = dates.first()->value(); - if (dt.isValid()) { - img.setText(QStringLiteral(META_KEY_CREATIONDATE), dt.toString(Qt::ISODate)); + // standard IFF metadata + auto annos = IFFChunk::searchT(form); + if (!annos.isEmpty()) { + auto anno = annos.first()->value(); + if (!anno.isEmpty()) { + img.setText(QStringLiteral(META_KEY_DESCRIPTION), anno); } } auto auths = IFFChunk::searchT(form); @@ -121,6 +122,29 @@ void addMetadata(QImage& img, const IFFChunk *form) img.setText(QStringLiteral(META_KEY_AUTHOR), auth); } } + auto dates = IFFChunk::searchT(form); + if (!dates.isEmpty()) { + auto dt = dates.first()->value(); + if (dt.isValid()) { + img.setText(QStringLiteral(META_KEY_CREATIONDATE), dt.toString(Qt::ISODate)); + } + } + auto copys = IFFChunk::searchT(form); + if (!copys.isEmpty()) { + auto cp = copys.first()->value(); + if (!cp.isEmpty()) { + img.setText(QStringLiteral(META_KEY_COPYRIGHT), cp); + } + } + auto names = IFFChunk::searchT(form); + if (!names.isEmpty()) { + auto name = names.first()->value(); + if (!name.isEmpty()) { + img.setText(QStringLiteral(META_KEY_TITLE), name); + } + } + + // software info auto vers = IFFChunk::searchT(form); if (!vers.isEmpty()) { auto ver = vers.first()->value(); @@ -128,6 +152,40 @@ void addMetadata(QImage& img, const IFFChunk *form) img.setText(QStringLiteral(META_KEY_SOFTWARE), ver); } } + + // SView5 metadata + auto exifs = IFFChunk::searchT(form); + if (!exifs.isEmpty()) { + auto exif = exifs.first()->value(); + exif.updateImageMetadata(img, false); + exif.updateImageResolution(img); + } + + auto xmp0s = IFFChunk::searchT(form); + if (!xmp0s.isEmpty()) { + auto xmp = xmp0s.first()->value(); + if (!xmp.isEmpty()) { + img.setText(QStringLiteral(META_KEY_XMP_ADOBE), xmp); + } + } + + auto iccps = IFFChunk::searchT(form); + if (!iccps.isEmpty()) { + auto cs = iccps.first()->value(); + if (cs.isValid()) { + img.setColorSpace(cs); + } + } + + // resolution -> leave after set of EXIF chunk + auto dpis = IFFChunk::searchT(form); + if (!dpis.isEmpty()) { + auto &&dpi = dpis.first(); + if (dpi->isValid()) { + img.setDotsPerMeterX(dpi->dotsPerMeterX()); + img.setDotsPerMeterY(dpi->dotsPerMeterY()); + } + } } bool IFFHandler::readStandardImage(QImage *image) @@ -153,16 +211,6 @@ bool IFFHandler::readStandardImage(QImage *image) return false; } - // resolution - auto dpis = IFFChunk::searchT(form); - if (!dpis.isEmpty()) { - auto &&dpi = dpis.first(); - if (dpi->isValid()) { - img.setDotsPerMeterX(dpi->dotsPerMeterX()); - img.setDotsPerMeterY(dpi->dotsPerMeterY()); - } - } - // set color table auto cmaps = IFFChunk::searchT(form); if (img.format() == QImage::Format_Indexed8) { @@ -190,9 +238,10 @@ bool IFFHandler::readStandardImage(QImage *image) qCWarning(LOG_IFFPLUGIN) << "IFFHandler::readStandardImage() error while reading image data"; return false; } + auto isPbm = form->formType() == PBM__FORM_TYPE; for (auto y = 0, h = img.height(); y < h; ++y) { auto line = reinterpret_cast(img.scanLine(y)); - auto ba = body->strideRead(device(), header, camg, cmap); + auto ba = body->strideRead(device(), header, camg, cmap, isPbm); if (ba.isEmpty()) { qCWarning(LOG_IFFPLUGIN) << "IFFHandler::readStandardImage() error while reading image scanline"; return false; @@ -201,6 +250,7 @@ bool IFFHandler::readStandardImage(QImage *image) } } + // set metadata (including image resolution) addMetadata(img, form); *image = img;