diff --git a/README.md b/README.md index a42a642..ee08f9c 100644 --- a/README.md +++ b/README.md @@ -403,6 +403,7 @@ The plugin supports the following image data: - FORM IMAG (Compact Disc-Interactive): It supports CLut4, CLut7, CLut8, Rle7 and DYuv formats. - FORM RGFX: It supports uncompressed images only. +- FORM DEEP: It supports uncompressed, RLE and TVDC images. - FOR4 CIMG (Maya Image File Format): It supports 24/48-bit RGB and 32/64-bit RGBA images. diff --git a/autotests/read/iff/sv5_unc_deep.iff b/autotests/read/iff/sv5_unc_deep.iff new file mode 100644 index 0000000..d8bb39d Binary files /dev/null and b/autotests/read/iff/sv5_unc_deep.iff differ diff --git a/autotests/read/iff/sv5_unc_deep.png b/autotests/read/iff/sv5_unc_deep.png new file mode 100644 index 0000000..3874cce Binary files /dev/null and b/autotests/read/iff/sv5_unc_deep.png differ diff --git a/src/imageformats/chunks.cpp b/src/imageformats/chunks.cpp index ccecd89..82e9e0f 100644 --- a/src/imageformats/chunks.cpp +++ b/src/imageformats/chunks.cpp @@ -295,6 +295,14 @@ IFFChunk::ChunkList IFFChunk::innerFromDevice(QIODevice *d, bool *ok, IFFChunk * chunk = QSharedPointer(new CTBLChunk()); } else if (cid == DATE_CHUNK) { chunk = QSharedPointer(new DATEChunk()); + } else if (cid == DBOD_CHUNK) { + chunk = QSharedPointer(new DBODChunk()); + } else if (cid == DGBL_CHUNK) { + chunk = QSharedPointer(new DGBLChunk()); + } else if (cid == DLOC_CHUNK) { + chunk = QSharedPointer(new DLOCChunk()); + } else if (cid == DPEL_CHUNK) { + chunk = QSharedPointer(new DPELChunk()); } else if (cid == DPI__CHUNK) { chunk = QSharedPointer(new DPIChunk()); } else if (cid == EXIF_CHUNK) { @@ -341,6 +349,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 == TVDC_CHUNK) { + chunk = QSharedPointer(new TVDCChunk()); } else if (cid == VDAT_CHUNK) { chunk = QSharedPointer(new VDATChunk()); } else if (cid == VERS_CHUNK) { @@ -1468,6 +1478,8 @@ bool FORMChunk::innerReadStructure(QIODevice *d) setChunks(IFFChunk::innerFromDevice(d, &ok, this)); } else if (_type == RGFX_FORM_TYPE) { setChunks(IFFChunk::innerFromDevice(d, &ok, this)); + } else if (_type == DEEP_FORM_TYPE || _type == TVPP_FORM_TYPE) { + setChunks(IFFChunk::innerFromDevice(d, &ok, this)); } return ok; } @@ -1585,6 +1597,72 @@ QImage::Format FORMChunk::rgfxFormat() const return QImage::Format_Invalid; } +QImage::Format FORMChunk::deepFormat() const +{ + auto pels = IFFChunk::searchT(chunks()); + if (pels.isEmpty()) { + return QImage::Format_Invalid; + } + auto list = pels.first()->elements(); + + // support for same depth on all elements + auto depth = -1; + for (auto &&el : list) { + if (depth < 0) + depth = el.depth; + if (depth != el.depth) + return QImage::Format_Invalid; + } + + // calculate the image format + if (list.size() == 4) { + if (list.at(0).type == DPELChunk::Red && + list.at(1).type == DPELChunk::Green && + list.at(2).type == DPELChunk::Blue && + list.at(3).type == DPELChunk::Alpha) { + if (depth == 8) + return FORMAT_RGBA_8BIT; + else if (depth == 16) + return QImage::Format_RGBA64; + } else if (list.at(0).type == DPELChunk::Cyan && + list.at(1).type == DPELChunk::Magenta && + list.at(2).type == DPELChunk::Yellow && + list.at(3).type == DPELChunk::Black) { + if (depth == 8) + return QImage::Format_CMYK8888; + } else if (list.at(0).type == DPELChunk::Red && + list.at(1).type == DPELChunk::Green && + list.at(2).type == DPELChunk::Blue) { + // unknown type of channel 4 -> ignoring it + if (depth == 8) + return QImage::Format_RGBX8888; + else if (depth == 16) + return QImage::Format_RGBX64; + } + } else if (list.size() == 3) { + if (list.at(0).type == DPELChunk::Red && + list.at(1).type == DPELChunk::Green && + list.at(2).type == DPELChunk::Blue) { + if (depth == 8) + return FORMAT_RGB_8BIT; + } else if (list.at(0).type == DPELChunk::Blue && + list.at(1).type == DPELChunk::Green && + list.at(2).type == DPELChunk::Red) { + if (depth == 8) + return QImage::Format_BGR888; + } + } else if (list.size() == 1) { + if (depth == 1) + return QImage::Format_Mono; + else if (depth == 8) + return QImage::Format_Grayscale8; + else if (depth == 16) + return QImage::Format_Grayscale16; + } + + return QImage::Format_Invalid; +} + QByteArray FORMChunk::formType() const { return _type; @@ -1596,6 +1674,8 @@ QImage::Format FORMChunk::format() const return cdiFormat(); } else if (formType() == RGFX_FORM_TYPE) { return rgfxFormat(); + } else if (formType() == DEEP_FORM_TYPE || formType() == TVPP_FORM_TYPE) { + return deepFormat(); } return iffFormat(); } @@ -1612,6 +1692,15 @@ QSize FORMChunk::size() const if (!rghds.isEmpty()) { return rghds.first()->size(); } + } else if (formType() == DEEP_FORM_TYPE || formType() == TVPP_FORM_TYPE) { + auto dlocs = IFFChunk::searchT(chunks()); + if (!dlocs.isEmpty()) { + return dlocs.first()->size(); + } + auto dgbls = IFFChunk::searchT(chunks()); + if (!dgbls.isEmpty()) { + return dgbls.first()->size(); + } } else { auto bmhds = IFFChunk::searchT(chunks()); if (!bmhds.isEmpty()) { @@ -1735,6 +1824,8 @@ bool CATChunk::innerReadStructure(QIODevice *d) setChunks(IFFChunk::innerFromDevice(d, &ok, this)); } else if (_type == RGFX_FORM_TYPE) { setChunks(IFFChunk::innerFromDevice(d, &ok, this)); + } else if (_type == DEEP_FORM_TYPE || _type == TVPP_FORM_TYPE) { + setChunks(IFFChunk::innerFromDevice(d, &ok, this)); } return ok; } @@ -3574,6 +3665,427 @@ quint32 RBODChunk::strideSize(const RGHDChunk *header) const } +/* ****************** + * *** DGBL Chunk *** + * ****************** */ + +DGBLChunk::~DGBLChunk() +{ + +} + +DGBLChunk::DGBLChunk() + : IFFChunk() +{ + +} + +DGBLChunk::Compression DGBLChunk::compression() const +{ + if (!isValid()) { + return Compression::Uncompressed; + } + return Compression(ui16(data(), 4)); + +} + +qint32 DGBLChunk::width() const +{ + if (!isValid()) { + return 0; + } + return qint32(ui16(data(), 0)); +} + +qint32 DGBLChunk::height() const +{ + if (!isValid()) { + return 0; + } + return qint32(ui16(data(), 2)); +} + +quint8 DGBLChunk::xAspectRatio() const +{ + if (!isValid()) { + return 0; + } + return quint8(data().at(6)); +} + +quint8 DGBLChunk::yAspectRatio() const +{ + if (!isValid()) { + return 0; + } + return quint8(data().at(7)); +} + +bool DGBLChunk::isValid() const +{ + if (dataBytes() < 8) { + return false; + } + return chunkId() == DGBLChunk::defaultChunkId(); +} + +bool DGBLChunk::innerReadStructure(QIODevice *d) +{ + return cacheData(d); +} + + +/* ****************** + * *** DPEL Chunk *** + * ****************** */ + +DPELChunk::~DPELChunk() +{ + +} + +DPELChunk::DPELChunk() + : IFFChunk() +{ + +} + +qint32 DPELChunk::count() const +{ + if (dataBytes() < 4) { + return 0; + } + auto cnt = i32(data(), 0); + if (cnt < 0 || cnt > 128) { + // an image should have 3, 4 or 5 channels: + // 128 is enough to give an error. + cnt = 0; + } + return cnt; +} + +qint32 DPELChunk::depth() const +{ + auto depth = 0; + auto list = elements(); + for (auto &&el : list) { + depth += el.depth; + } + return depth; +} + +QList DPELChunk::elements() const +{ + QList list; + if (isValid()) { + for (auto n = count(), i = 0; i < n; ++i) { + auto idx = 4 + i * 4; + list << DPELChunk::Element(DataType(ui16(data(), idx)), + ui16(data(), idx + 2)); + } + } + return list; +} + +bool DPELChunk::isValid() const +{ + if (dataBytes() < quint32(4 + count() * 4)) { + return false; + } + return chunkId() == DPELChunk::defaultChunkId(); +} + +bool DPELChunk::innerReadStructure(QIODevice *d) +{ + return cacheData(d); +} + + +/* ****************** + * *** DLOC Chunk *** + * ****************** */ + +DLOCChunk::~DLOCChunk() +{ + +} + +DLOCChunk::DLOCChunk() + : IFFChunk() +{ + +} + +qint32 DLOCChunk::width() const +{ + if (!isValid()) { + return 0; + } + return qint32(ui16(data(), 0)); +} + +qint32 DLOCChunk::height() const +{ + if (!isValid()) { + return 0; + } + return qint32(ui16(data(), 2)); +} + +QSize DLOCChunk::size() const +{ + return QSize(width(), height()); +} + +qint32 DLOCChunk::xOffset() const +{ + if (!isValid()) { + return 0; + } + return qint32(i16(data(), 4)); +} + +qint32 DLOCChunk::yOffset() const +{ + if (!isValid()) { + return 0; + } + return qint32(i16(data(), 6)); +} + +bool DLOCChunk::isValid() const +{ + if (dataBytes() < 8) { + return false; + } + return chunkId() == DLOCChunk::defaultChunkId(); +} + +bool DLOCChunk::innerReadStructure(QIODevice *d) +{ + return cacheData(d); +} + + +/* ****************** + * *** TVDC Chunk *** + * ****************** */ + +TVDCChunk::~TVDCChunk() +{ + +} + +TVDCChunk::TVDCChunk() + : IFFChunk() +{ + +} + +qint32 TVDCChunk::count() const +{ + if (!isValid()) { + return 0; + } + return dataBytes() / 2; +} + +QList TVDCChunk::table() const +{ + QList list; + if (isValid()) { + for (auto n = count(), i = 0; i < n; ++i) { + list << ui16(data(), i * 2); + } + } + return list; +} + +bool TVDCChunk::isValid() const +{ + if (dataBytes() < 32) { + return false; + } + return chunkId() == TVDCChunk::defaultChunkId(); +} + +bool TVDCChunk::innerReadStructure(QIODevice *d) +{ + return cacheData(d); +} + + +/* ****************** + * *** DBOD Chunk *** + * ****************** */ + +DBODChunk::~DBODChunk() +{ + +} + +DBODChunk::DBODChunk() + : IFFChunk() +{ + +} + +bool DBODChunk::isValid() const +{ + return chunkId() == DBODChunk::defaultChunkId(); +} + +/*! + * \brief rleDeepDecompress + * Each run contains a pixel (all components) + */ +inline qint64 rleDeepDecompress(QIODevice *input, char *output, qint64 olen, qint32 pixelBytes) +{ + qint64 j = 0; + pixelBytes = std::max(1, pixelBytes); + for (qint64 rr = 0, available = olen; j < olen; available = olen - j) { + char n; + + // check the output buffer space for the next run + if (available < 129 * pixelBytes) { + if (input->peek(&n, 1) != 1) { // end of data (or error) + break; + } + if ((static_cast(n) >= 0 ? qint64(n) + 1 : qint64(1 - n)) * pixelBytes > available) + break; + } + + // decompress + if (input->read(&n, 1) != 1) { // end of data (or error) + break; + } + + if (static_cast(n) >= 0) { + rr = input->read(output + j, (qint64(n) + 1) * pixelBytes); + if (rr == -1) { + return -1; + } + } + else { + auto buf = input->read(pixelBytes); + if (buf.size() != pixelBytes) { + break; + } + rr = qint64(1 - static_cast(n)); + for (qint64 i = 0; i < rr; ++i) { + std::memcpy(output + j + i * pixelBytes, buf.data(), buf.size()); + } + rr *= pixelBytes; + } + + j += rr; + } + return j; +} + +/*! + * \brief tvdcDeepDecompress + * the compression is made line by line for each elementof the chunk DPEL. + * For RGBA for example we have a Red line, a Green line, and so on. + */ +inline qint64 tvdcDeepDecompress(QIODevice *input, char *output, qint64 olen, const QList& table) +{ + if (table.size() != 16) { + return -1; + } + + quint8 v = 0; + quint8 last = 0; + for (qint64 i = 0, pos = 0, lastRead = -1; i < olen; ++i) { + if ((pos >> 1) != lastRead) { + char n; + if (input->read(&n, 1) != 1) { + return -1; + } + lastRead = (pos >> 1); + last = quint8(n); + } + quint8 d = last; + if (pos++ & 1) { + d &= 0xf; + } else { + d >>= 4; + } + v += table.at(d); + output[i] = char(v); + if (!table.at(d)) { + if ((pos >> 1) != lastRead) { + char n; + if (input->read(&n, 1) != 1) { + return -1; + } + lastRead = (pos >> 1); + last = quint8(n); + } + d = last; + if (pos++ & 1) { + d &= 0xf; + } else { + d >>= 4; + } + while (d--) { + if (i < olen - 1) { + output[++i] = char(v); + continue; + } + return -1; + } + } + } + return olen; +} + +QByteArray DBODChunk::strideRead(QIODevice *d, qint32, const DGBLChunk *header, const DPELChunk *pel, const DLOCChunk *loc, const TVDCChunk *tvdc) const +{ + auto size = strideSize(header, pel, loc); + if (size == 0) { + return {}; + } + + qint64 rr = 0; + QByteArray planes(size, char()); + if (header->compression() == DGBLChunk::Compression::Uncompressed) { + rr = d->read(planes.data(), planes.size()); + } else if (header->compression() == DGBLChunk::Compression::Rle) { + rr = rleDeepDecompress(d, planes.data(), planes.size(), pel->depth() / 8); + } else if (header->compression() == DGBLChunk::Compression::TvDeepCompression) { + if (tvdc) { // TVDC is planar, so I have to convert to chunky + auto table = tvdc->table(); + for (auto i = 0, n = pel->count(); i < n; ++i) { + QByteArray ba(size / n, char()); + rr += tvdcDeepDecompress(d, ba.data(), ba.size(), tvdc->table()); + for (auto j = 0, m = int(ba.size()); j < m; ++j) { + planes[i + j * n] = ba.at(j); + } + } + } + } else { + qCDebug(LOG_IFFPLUGIN) << "DBODChunk::strideRead(): unknown compression" << header->compression(); + } + + // Uncompressed, Rle and TvDeepCompression: one line at a time. + if (rr != size) + return {}; + return planes; +} + +bool DBODChunk::resetStrideRead(QIODevice *d) const +{ + return seek(d); +} + +quint32 DBODChunk::strideSize(const DGBLChunk *header, const DPELChunk *pel, const DLOCChunk *loc) const +{ + auto width = loc ? loc->width() : header->width(); + return (width * pel->depth() + 7) / 8; +} + + /* ****************** * *** BEAM Chunk *** * ****************** */ diff --git a/src/imageformats/chunks_p.h b/src/imageformats/chunks_p.h index df7835e..0a7ba46 100644 --- a/src/imageformats/chunks_p.h +++ b/src/imageformats/chunks_p.h @@ -55,6 +55,10 @@ Q_DECLARE_LOGGING_CATEGORY(LOG_IFFPLUGIN) #define CAMG_CHUNK QByteArray("CAMG") #define CMAP_CHUNK QByteArray("CMAP") #define CMYK_CHUNK QByteArray("CMYK") // https://wiki.amigaos.net/wiki/ILBM_IFF_Interleaved_Bitmap#ILBM.CMYK +#define DBOD_CHUNK QByteArray("DBOD") +#define DGBL_CHUNK QByteArray("DGBL") +#define DLOC_CHUNK QByteArray("DLOC") +#define DPEL_CHUNK QByteArray("DPEL") #define DPI__CHUNK QByteArray("DPI ") #define IDAT_CHUNK QByteArray("IDAT") #define IHDR_CHUNK QByteArray("IHDR") @@ -65,6 +69,7 @@ Q_DECLARE_LOGGING_CATEGORY(LOG_IFFPLUGIN) #define RFLG_CHUNK QByteArray("RFLG") #define RGHD_CHUNK QByteArray("RGHD") #define RSCM_CHUNK QByteArray("RSCM") +#define TVDC_CHUNK QByteArray("TVDC") #define XBMI_CHUNK QByteArray("XBMI") #define YUVS_CHUNK QByteArray("YUVS") @@ -96,12 +101,14 @@ Q_DECLARE_LOGGING_CATEGORY(LOG_IFFPLUGIN) // FORM types #define ACBM_FORM_TYPE QByteArray("ACBM") +#define DEEP_FORM_TYPE QByteArray("DEEP") #define ILBM_FORM_TYPE QByteArray("ILBM") #define IMAG_FORM_TYPE QByteArray("IMAG") #define PBM__FORM_TYPE QByteArray("PBM ") #define RGB8_FORM_TYPE QByteArray("RGB8") #define RGBN_FORM_TYPE QByteArray("RGBN") #define RGFX_FORM_TYPE QByteArray("RGFX") +#define TVPP_FORM_TYPE QByteArray("TVPP") // same as DEEP #define CIMG_FOR4_TYPE QByteArray("CIMG") #define TBMP_FOR4_TYPE QByteArray("TBMP") @@ -642,11 +649,23 @@ class CAMGChunk : public IFFChunk { public: enum ModeId { - LoResLace = 0x0004, + GenLockVideo = 0x0002, + InterlacedDisplay = 0x0004, + DoubleScan = 0x0008, + SuperHighResolution = 0x0020, + PlayfieldBitplaneAdjust = 0x0040, HalfBrite = 0x0080, - LoResDpf = 0x0400, - Ham = 0x0800, - HiRes = 0x8000 + GenLockAudio = 0x0100, + DualPlayfield = 0x0400, + HoldAndModify = 0x0800, + ExtendedMode = 0x1000, + ViewPortHide = 0x2000, + Sprites = 0x4000, + HighResolution = 0x8000, + + // aliases + Lace = InterlacedDisplay, + Ham = HoldAndModify }; Q_DECLARE_FLAGS(ModeIds, ModeId) @@ -946,6 +965,8 @@ private: QImage::Format cdiFormat() const; QImage::Format rgfxFormat() const; + + QImage::Format deepFormat() const; }; @@ -1946,6 +1967,266 @@ private: }; +/*! + * *** DEEP IFF CHUNKS *** + */ + +/*! + * \brief The DGBLChunk class + */ +class DGBLChunk : public IFFChunk +{ +public: + enum Compression : quint16 { + Uncompressed = 0, + Rle = 1, + Huffman = 2, + DynamicHuffman = 3, + Jpeg = 4, + TvDeepCompression = 5 + }; + + virtual ~DGBLChunk() override; + DGBLChunk(); + DGBLChunk(const DGBLChunk& other) = default; + DGBLChunk& operator =(const DGBLChunk& other) = default; + + /*! + * \brief compression + * \return The type of compression used. + */ + Compression compression() const; + + /*! + * \brief width + * \return Width of the source display in pixels. + */ + qint32 width() const; + + /*! + * \brief height + * \return Height of the source display in pixels. + */ + qint32 height() const; + + /*! + * \brief size + * \return Size of the source display in pixels. + */ + QSize size() const + { + return QSize(width(), height()); + } + + /*! + * \brief xAspectRatio + * \return X pixel aspect. + */ + quint8 xAspectRatio() const; + + /*! + * \brief yAspectRatio + * \return Y pixel aspect. + */ + quint8 yAspectRatio() const; + + virtual bool isValid() const override; + + CHUNKID_DEFINE(DGBL_CHUNK) + +protected: + virtual bool innerReadStructure(QIODevice *d) override; +}; + + +/*! + * \brief The DPELChunk class + */ +class DPELChunk : public IFFChunk +{ +public: + enum DataType : quint16 { + Invalid = 0, + Red = 1, + Green = 2, + Blue = 3, + Alpha = 4, + Yellow = 5, + Cyan = 6, + Magenta = 7, + Black = 8, + Mask = 9, + ZBuffer = 10, + Opacity = 11, + LinearKey = 12, + BinaryKey = 13 + }; + + struct Element { + Element(const DataType& t = DataType::Invalid, quint16 d = 0) : type(t), depth(d) {} + DataType type; + quint16 depth; + }; + + virtual ~DPELChunk() override; + DPELChunk(); + DPELChunk(const DPELChunk& other) = default; + DPELChunk& operator =(const DPELChunk& other) = default; + + /*! + * \brief count + * \return The number of elements + */ + qint32 count() const; + + /*! + * \brief depth + * \return The pixel depth. + */ + qint32 depth() const; + + /*! + * \brief elements + * Elements needed to identify the content of every pixel. + * Pixels will always be padded to byte boundaries. + * \return The list of elements. An empty list on error. + */ + QList elements() const; + + virtual bool isValid() const override; + + CHUNKID_DEFINE(DPEL_CHUNK) + +protected: + virtual bool innerReadStructure(QIODevice *d) override; +}; + + +/*! + * \brief The DLOCChunk class + */ +class DLOCChunk : public IFFChunk +{ +public: + virtual ~DLOCChunk() override; + DLOCChunk(); + DLOCChunk(const DLOCChunk& other) = default; + DLOCChunk& operator =(const DLOCChunk& other) = default; + + /*! + * \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 xOffset + * X offset of origin in source image. + */ + qint32 xOffset() const; + + /*! + * \brief yOffset + * \returnX offset of origin in source image. + */ + qint32 yOffset() const; + + virtual bool isValid() const override; + + CHUNKID_DEFINE(DLOC_CHUNK) + +protected: + virtual bool innerReadStructure(QIODevice *d) override; +}; + + +/*! + * \brief The TVDCChunk class + */ +class TVDCChunk : public IFFChunk +{ +public: + virtual ~TVDCChunk() override; + TVDCChunk(); + TVDCChunk(const TVDCChunk& other) = default; + TVDCChunk& operator =(const TVDCChunk& other) = default; + + qint32 count() const; + + QList table() const; + + virtual bool isValid() const override; + + CHUNKID_DEFINE(TVDC_CHUNK) + +protected: + virtual bool innerReadStructure(QIODevice *d) override; +}; + + +/*! + * \brief The DBODChunk class + */ +class DBODChunk : public IFFChunk +{ +public: + virtual ~DBODChunk() override; + DBODChunk(); + DBODChunk(const DBODChunk& other) = default; + DBODChunk& operator =(const DBODChunk& other) = default; + + virtual bool isValid() const override; + + CHUNKID_DEFINE(DBOD_CHUNK) + + /*! + * \brief readStride + * \param d The device. + * \param y The current scanline. + * \param header The bitmap header. + * \param pel The pixel elements info. + * \param loc The display location (optional). + * \return The scanline as requested for QImage. + * \warning Call resetStrideRead() once before this one. + */ + QByteArray strideRead(QIODevice *d, + qint32 y, + const DGBLChunk *header, + const DPELChunk *pel, + const DLOCChunk *loc = nullptr, + const TVDCChunk *tvdc = nullptr) const; + + /*! + * \brief resetStrideRead + * Reset the stride read set the position at the beginning of the data and reset all buffers. + * \param d The device. + * \return True on success, otherwise false. + * \sa strideRead + * \note Must be called once before strideRead(). + */ + bool resetStrideRead(QIODevice *d) const; + +protected: + /*! + * \brief strideSize + * \return The size of data to have to decode an image row. + */ + quint32 strideSize(const DGBLChunk *header, const DPELChunk *pel, const DLOCChunk *loc = nullptr) const; +}; + + /*! * *** UNDOCUMENTED CHUNKS *** */ diff --git a/src/imageformats/iff.cpp b/src/imageformats/iff.cpp index 881f072..d46f094 100644 --- a/src/imageformats/iff.cpp +++ b/src/imageformats/iff.cpp @@ -607,6 +607,69 @@ bool IFFHandler::readRGFXImage(QImage *image) return true; } +bool IFFHandler::readDEEPImage(QImage *image) +{ + auto forms = d->searchForms(); + if (forms.isEmpty()) { + return false; + } + auto cin = qBound(0, currentImageNumber(), int(forms.size() - 1)); + auto &&form = forms.at(cin); + + // show the first one (I don't have a sample with many images) + auto headers = IFFChunk::searchT(form); + if (headers.isEmpty()) { + return false; + } + // create the image + auto &&header = headers.first(); + auto size = header->size(); + auto locs = IFFChunk::searchT(form); + if (!locs.isEmpty()) { + size = locs.first()->size(); + } + auto img = imageAlloc(size, form->format()); + if (img.isNull()) { + qCWarning(LOG_IFFPLUGIN) << "IFFHandler::readDEEPImage(): error while allocating the image"; + return false; + } + + // decoding the image + auto bodies = IFFChunk::searchT(form); + if (bodies.isEmpty()) { + img.fill(0); + } else { + auto &&body = bodies.first(); + if (!body->resetStrideRead(device())) { + qCWarning(LOG_IFFPLUGIN) << "IFFHandler::readDEEPImage(): error while reading image data"; + return false; + } + auto pels = IFFChunk::searchT(form); + if (pels.isEmpty()) { + // DPEL is used to calculate the image format, so here should not be empty. + return false; + } + auto tvdcs = IFFChunk::searchT(form); + for (auto y = 0, h = img.height(); y < h; ++y) { + auto line = reinterpret_cast(img.scanLine(y)); + auto ba = body->strideRead(device(), y, header, pels.first(), + locs.isEmpty() ? nullptr : locs.first(), + tvdcs.isEmpty() ? nullptr : tvdcs.first()); + if (ba.isEmpty()) { + qCWarning(LOG_IFFPLUGIN) << "IFFHandler::readDEEPImage(): error while reading image scanline"; + return false; + } + memcpy(line, ba.constData(), std::min(img.bytesPerLine(), ba.size())); + } + } + + // set metadata (including image resolution) + addMetadata(img, form); + + *image = img; + return true; +} + bool IFFHandler::read(QImage *image) { if (!d->readStructure(device())) { @@ -630,6 +693,10 @@ bool IFFHandler::read(QImage *image) return true; } + if (readDEEPImage(image)) { + return true; + } + qCWarning(LOG_IFFPLUGIN) << "IFFHandler::read(): no supported image found"; return false; } diff --git a/src/imageformats/iff_p.h b/src/imageformats/iff_p.h index 145f632..a975962 100644 --- a/src/imageformats/iff_p.h +++ b/src/imageformats/iff_p.h @@ -39,6 +39,8 @@ private: bool readRGFXImage(QImage *image); + bool readDEEPImage(QImage *image); + private: const QScopedPointer d; };