IFF: DEEP image support

This commit is contained in:
Mirco Miranda
2026-05-06 15:12:14 +02:00
committed by Mirco Miranda
parent 1206598337
commit 49a8fd38c8
7 changed files with 867 additions and 4 deletions

View File

@@ -403,6 +403,7 @@ The plugin supports the following image data:
- FORM IMAG (Compact Disc-Interactive): It supports CLut4, CLut7, CLut8, Rle7 - FORM IMAG (Compact Disc-Interactive): It supports CLut4, CLut7, CLut8, Rle7
and DYuv formats. and DYuv formats.
- FORM RGFX: It supports uncompressed images only. - 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 - FOR4 CIMG (Maya Image File Format): It supports 24/48-bit RGB and 32/64-bit
RGBA images. RGBA images.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

View File

@@ -295,6 +295,14 @@ IFFChunk::ChunkList IFFChunk::innerFromDevice(QIODevice *d, bool *ok, IFFChunk *
chunk = QSharedPointer<IFFChunk>(new CTBLChunk()); chunk = QSharedPointer<IFFChunk>(new CTBLChunk());
} else if (cid == DATE_CHUNK) { } else if (cid == DATE_CHUNK) {
chunk = QSharedPointer<IFFChunk>(new DATEChunk()); chunk = QSharedPointer<IFFChunk>(new DATEChunk());
} else if (cid == DBOD_CHUNK) {
chunk = QSharedPointer<IFFChunk>(new DBODChunk());
} else if (cid == DGBL_CHUNK) {
chunk = QSharedPointer<IFFChunk>(new DGBLChunk());
} else if (cid == DLOC_CHUNK) {
chunk = QSharedPointer<IFFChunk>(new DLOCChunk());
} else if (cid == DPEL_CHUNK) {
chunk = QSharedPointer<IFFChunk>(new DPELChunk());
} else if (cid == DPI__CHUNK) { } else if (cid == DPI__CHUNK) {
chunk = QSharedPointer<IFFChunk>(new DPIChunk()); chunk = QSharedPointer<IFFChunk>(new DPIChunk());
} else if (cid == EXIF_CHUNK) { } else if (cid == EXIF_CHUNK) {
@@ -341,6 +349,8 @@ IFFChunk::ChunkList IFFChunk::innerFromDevice(QIODevice *d, bool *ok, IFFChunk *
chunk = QSharedPointer<IFFChunk>(new SHAMChunk()); chunk = QSharedPointer<IFFChunk>(new SHAMChunk());
} else if (cid == TBHD_CHUNK) { } else if (cid == TBHD_CHUNK) {
chunk = QSharedPointer<IFFChunk>(new TBHDChunk()); chunk = QSharedPointer<IFFChunk>(new TBHDChunk());
} else if (cid == TVDC_CHUNK) {
chunk = QSharedPointer<IFFChunk>(new TVDCChunk());
} else if (cid == VDAT_CHUNK) { } else if (cid == VDAT_CHUNK) {
chunk = QSharedPointer<IFFChunk>(new VDATChunk()); chunk = QSharedPointer<IFFChunk>(new VDATChunk());
} else if (cid == VERS_CHUNK) { } else if (cid == VERS_CHUNK) {
@@ -1468,6 +1478,8 @@ bool FORMChunk::innerReadStructure(QIODevice *d)
setChunks(IFFChunk::innerFromDevice(d, &ok, this)); setChunks(IFFChunk::innerFromDevice(d, &ok, this));
} else if (_type == RGFX_FORM_TYPE) { } else if (_type == RGFX_FORM_TYPE) {
setChunks(IFFChunk::innerFromDevice(d, &ok, this)); setChunks(IFFChunk::innerFromDevice(d, &ok, this));
} else if (_type == DEEP_FORM_TYPE || _type == TVPP_FORM_TYPE) {
setChunks(IFFChunk::innerFromDevice(d, &ok, this));
} }
return ok; return ok;
} }
@@ -1585,6 +1597,72 @@ QImage::Format FORMChunk::rgfxFormat() const
return QImage::Format_Invalid; return QImage::Format_Invalid;
} }
QImage::Format FORMChunk::deepFormat() const
{
auto pels = IFFChunk::searchT<DPELChunk>(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 QByteArray FORMChunk::formType() const
{ {
return _type; return _type;
@@ -1596,6 +1674,8 @@ QImage::Format FORMChunk::format() const
return cdiFormat(); return cdiFormat();
} else if (formType() == RGFX_FORM_TYPE) { } else if (formType() == RGFX_FORM_TYPE) {
return rgfxFormat(); return rgfxFormat();
} else if (formType() == DEEP_FORM_TYPE || formType() == TVPP_FORM_TYPE) {
return deepFormat();
} }
return iffFormat(); return iffFormat();
} }
@@ -1612,6 +1692,15 @@ QSize FORMChunk::size() const
if (!rghds.isEmpty()) { if (!rghds.isEmpty()) {
return rghds.first()->size(); return rghds.first()->size();
} }
} else if (formType() == DEEP_FORM_TYPE || formType() == TVPP_FORM_TYPE) {
auto dlocs = IFFChunk::searchT<DLOCChunk>(chunks());
if (!dlocs.isEmpty()) {
return dlocs.first()->size();
}
auto dgbls = IFFChunk::searchT<DGBLChunk>(chunks());
if (!dgbls.isEmpty()) {
return dgbls.first()->size();
}
} else { } else {
auto bmhds = IFFChunk::searchT<BMHDChunk>(chunks()); auto bmhds = IFFChunk::searchT<BMHDChunk>(chunks());
if (!bmhds.isEmpty()) { if (!bmhds.isEmpty()) {
@@ -1735,6 +1824,8 @@ bool CATChunk::innerReadStructure(QIODevice *d)
setChunks(IFFChunk::innerFromDevice(d, &ok, this)); setChunks(IFFChunk::innerFromDevice(d, &ok, this));
} else if (_type == RGFX_FORM_TYPE) { } else if (_type == RGFX_FORM_TYPE) {
setChunks(IFFChunk::innerFromDevice(d, &ok, this)); setChunks(IFFChunk::innerFromDevice(d, &ok, this));
} else if (_type == DEEP_FORM_TYPE || _type == TVPP_FORM_TYPE) {
setChunks(IFFChunk::innerFromDevice(d, &ok, this));
} }
return ok; 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::Element> DPELChunk::elements() const
{
QList<DPELChunk::Element> 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<quint16> TVDCChunk::table() const
{
QList<quint16> 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<signed char>(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<signed char>(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<signed char>(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<quint16>& 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 *** * *** BEAM Chunk ***
* ****************** */ * ****************** */

View File

@@ -55,6 +55,10 @@ Q_DECLARE_LOGGING_CATEGORY(LOG_IFFPLUGIN)
#define CAMG_CHUNK QByteArray("CAMG") #define CAMG_CHUNK QByteArray("CAMG")
#define CMAP_CHUNK QByteArray("CMAP") #define CMAP_CHUNK QByteArray("CMAP")
#define CMYK_CHUNK QByteArray("CMYK") // https://wiki.amigaos.net/wiki/ILBM_IFF_Interleaved_Bitmap#ILBM.CMYK #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 DPI__CHUNK QByteArray("DPI ")
#define IDAT_CHUNK QByteArray("IDAT") #define IDAT_CHUNK QByteArray("IDAT")
#define IHDR_CHUNK QByteArray("IHDR") #define IHDR_CHUNK QByteArray("IHDR")
@@ -65,6 +69,7 @@ Q_DECLARE_LOGGING_CATEGORY(LOG_IFFPLUGIN)
#define RFLG_CHUNK QByteArray("RFLG") #define RFLG_CHUNK QByteArray("RFLG")
#define RGHD_CHUNK QByteArray("RGHD") #define RGHD_CHUNK QByteArray("RGHD")
#define RSCM_CHUNK QByteArray("RSCM") #define RSCM_CHUNK QByteArray("RSCM")
#define TVDC_CHUNK QByteArray("TVDC")
#define XBMI_CHUNK QByteArray("XBMI") #define XBMI_CHUNK QByteArray("XBMI")
#define YUVS_CHUNK QByteArray("YUVS") #define YUVS_CHUNK QByteArray("YUVS")
@@ -96,12 +101,14 @@ Q_DECLARE_LOGGING_CATEGORY(LOG_IFFPLUGIN)
// FORM types // FORM types
#define ACBM_FORM_TYPE QByteArray("ACBM") #define ACBM_FORM_TYPE QByteArray("ACBM")
#define DEEP_FORM_TYPE QByteArray("DEEP")
#define ILBM_FORM_TYPE QByteArray("ILBM") #define ILBM_FORM_TYPE QByteArray("ILBM")
#define IMAG_FORM_TYPE QByteArray("IMAG") #define IMAG_FORM_TYPE QByteArray("IMAG")
#define PBM__FORM_TYPE QByteArray("PBM ") #define PBM__FORM_TYPE QByteArray("PBM ")
#define RGB8_FORM_TYPE QByteArray("RGB8") #define RGB8_FORM_TYPE QByteArray("RGB8")
#define RGBN_FORM_TYPE QByteArray("RGBN") #define RGBN_FORM_TYPE QByteArray("RGBN")
#define RGFX_FORM_TYPE QByteArray("RGFX") #define RGFX_FORM_TYPE QByteArray("RGFX")
#define TVPP_FORM_TYPE QByteArray("TVPP") // same as DEEP
#define CIMG_FOR4_TYPE QByteArray("CIMG") #define CIMG_FOR4_TYPE QByteArray("CIMG")
#define TBMP_FOR4_TYPE QByteArray("TBMP") #define TBMP_FOR4_TYPE QByteArray("TBMP")
@@ -642,11 +649,23 @@ class CAMGChunk : public IFFChunk
{ {
public: public:
enum ModeId { enum ModeId {
LoResLace = 0x0004, GenLockVideo = 0x0002,
InterlacedDisplay = 0x0004,
DoubleScan = 0x0008,
SuperHighResolution = 0x0020,
PlayfieldBitplaneAdjust = 0x0040,
HalfBrite = 0x0080, HalfBrite = 0x0080,
LoResDpf = 0x0400, GenLockAudio = 0x0100,
Ham = 0x0800, DualPlayfield = 0x0400,
HiRes = 0x8000 HoldAndModify = 0x0800,
ExtendedMode = 0x1000,
ViewPortHide = 0x2000,
Sprites = 0x4000,
HighResolution = 0x8000,
// aliases
Lace = InterlacedDisplay,
Ham = HoldAndModify
}; };
Q_DECLARE_FLAGS(ModeIds, ModeId) Q_DECLARE_FLAGS(ModeIds, ModeId)
@@ -946,6 +965,8 @@ private:
QImage::Format cdiFormat() const; QImage::Format cdiFormat() const;
QImage::Format rgfxFormat() 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<Element> 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<quint16> 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 *** * *** UNDOCUMENTED CHUNKS ***
*/ */

View File

@@ -607,6 +607,69 @@ bool IFFHandler::readRGFXImage(QImage *image)
return true; return true;
} }
bool IFFHandler::readDEEPImage(QImage *image)
{
auto forms = d->searchForms<FORMChunk>();
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<DGBLChunk>(form);
if (headers.isEmpty()) {
return false;
}
// create the image
auto &&header = headers.first();
auto size = header->size();
auto locs = IFFChunk::searchT<DLOCChunk>(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<DBODChunk>(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<DPELChunk>(form);
if (pels.isEmpty()) {
// DPEL is used to calculate the image format, so here should not be empty.
return false;
}
auto tvdcs = IFFChunk::searchT<TVDCChunk>(form);
for (auto y = 0, h = img.height(); y < h; ++y) {
auto line = reinterpret_cast<char*>(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) bool IFFHandler::read(QImage *image)
{ {
if (!d->readStructure(device())) { if (!d->readStructure(device())) {
@@ -630,6 +693,10 @@ bool IFFHandler::read(QImage *image)
return true; return true;
} }
if (readDEEPImage(image)) {
return true;
}
qCWarning(LOG_IFFPLUGIN) << "IFFHandler::read(): no supported image found"; qCWarning(LOG_IFFPLUGIN) << "IFFHandler::read(): no supported image found";
return false; return false;
} }

View File

@@ -39,6 +39,8 @@ private:
bool readRGFXImage(QImage *image); bool readRGFXImage(QImage *image);
bool readDEEPImage(QImage *image);
private: private:
const QScopedPointer<IFFHandlerPrivate> d; const QScopedPointer<IFFHandlerPrivate> d;
}; };