diff --git a/README.md b/README.md index 88509a9..fd7a25c 100644 --- a/README.md +++ b/README.md @@ -359,6 +359,7 @@ The plugin supports the following image data: with color map only. - FORM IMAG (Compact Disc-Interactive): It supports CLut4, CLut7, CLut8, Rle7 and DYuv formats. +- FORM RGFX: It supports uncompressed images only. - 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_gray8_rgx.iff b/autotests/read/iff/sv5_gray8_rgx.iff new file mode 100644 index 0000000..bbb2dca Binary files /dev/null and b/autotests/read/iff/sv5_gray8_rgx.iff differ diff --git a/autotests/read/iff/sv5_gray8_rgx.png b/autotests/read/iff/sv5_gray8_rgx.png new file mode 100644 index 0000000..230154a Binary files /dev/null and b/autotests/read/iff/sv5_gray8_rgx.png differ diff --git a/autotests/read/iff/sv5_idx8_rgx.iff b/autotests/read/iff/sv5_idx8_rgx.iff new file mode 100644 index 0000000..f89b6f8 Binary files /dev/null and b/autotests/read/iff/sv5_idx8_rgx.iff differ diff --git a/autotests/read/iff/sv5_idx8_rgx.png b/autotests/read/iff/sv5_idx8_rgx.png new file mode 100644 index 0000000..0ab7282 Binary files /dev/null and b/autotests/read/iff/sv5_idx8_rgx.png differ diff --git a/autotests/read/iff/sv5_rgb16_rgx.iff b/autotests/read/iff/sv5_rgb16_rgx.iff new file mode 100644 index 0000000..1c934ad Binary files /dev/null and b/autotests/read/iff/sv5_rgb16_rgx.iff differ diff --git a/autotests/read/iff/sv5_rgb16_rgx.png b/autotests/read/iff/sv5_rgb16_rgx.png new file mode 100644 index 0000000..aff6939 Binary files /dev/null and b/autotests/read/iff/sv5_rgb16_rgx.png differ diff --git a/autotests/read/iff/sv5_rgb32_rgx.iff b/autotests/read/iff/sv5_rgb32_rgx.iff new file mode 100644 index 0000000..9727224 Binary files /dev/null and b/autotests/read/iff/sv5_rgb32_rgx.iff differ diff --git a/autotests/read/iff/sv5_rgb32_rgx.png b/autotests/read/iff/sv5_rgb32_rgx.png new file mode 100644 index 0000000..5cc9151 Binary files /dev/null and b/autotests/read/iff/sv5_rgb32_rgx.png differ diff --git a/autotests/read/iff/sv5_rgb8_rgx.iff b/autotests/read/iff/sv5_rgb8_rgx.iff new file mode 100644 index 0000000..7b9491c Binary files /dev/null and b/autotests/read/iff/sv5_rgb8_rgx.iff differ diff --git a/autotests/read/iff/sv5_rgb8_rgx.png b/autotests/read/iff/sv5_rgb8_rgx.png new file mode 100644 index 0000000..11b3080 Binary files /dev/null and b/autotests/read/iff/sv5_rgb8_rgx.png differ diff --git a/autotests/read/iff/sv5_rgba16_rgx.iff b/autotests/read/iff/sv5_rgba16_rgx.iff new file mode 100644 index 0000000..6157307 Binary files /dev/null and b/autotests/read/iff/sv5_rgba16_rgx.iff differ diff --git a/autotests/read/iff/sv5_rgba16_rgx.png b/autotests/read/iff/sv5_rgba16_rgx.png new file mode 100644 index 0000000..14c6152 Binary files /dev/null and b/autotests/read/iff/sv5_rgba16_rgx.png differ diff --git a/autotests/read/iff/sv5_rgba32_rgx.iff b/autotests/read/iff/sv5_rgba32_rgx.iff new file mode 100644 index 0000000..ae44471 Binary files /dev/null and b/autotests/read/iff/sv5_rgba32_rgx.iff differ diff --git a/autotests/read/iff/sv5_rgba32_rgx.iff.json b/autotests/read/iff/sv5_rgba32_rgx.iff.json new file mode 100644 index 0000000..aa21e25 --- /dev/null +++ b/autotests/read/iff/sv5_rgba32_rgx.iff.json @@ -0,0 +1,37 @@ +[ + { + "fileName" : "sv5_rgba32_rgx.png", + "colorSpace" : { + "description" : "TIFF ICC Profile", + "primaries" : "SRgb", + "transferFunction" : "SRgb", + "gamma" : 0 + }, + "metadata" : [ + { + "key" : "Author", + "value" : "KDE Project" + }, + { + "key" : "Copyright", + "value" : "@2025 KDE Project" + }, + { + "key" : "CreationDate", + "value" : "2025-01-14T10:34:51" + }, + { + "key" : "Description", + "value" : "TV broadcast test image." + }, + { + "key" : "Title", + "value" : "Test Card" + } + ], + "resolution" : { + "dotsPerMeterX" : 2835, + "dotsPerMeterY" : 2835 + } + } +] diff --git a/autotests/read/iff/sv5_rgba32_rgx.png b/autotests/read/iff/sv5_rgba32_rgx.png new file mode 100644 index 0000000..87e772c Binary files /dev/null and b/autotests/read/iff/sv5_rgba32_rgx.png differ diff --git a/autotests/read/iff/sv5_rgba8_rgx.iff b/autotests/read/iff/sv5_rgba8_rgx.iff new file mode 100644 index 0000000..f8a579e Binary files /dev/null and b/autotests/read/iff/sv5_rgba8_rgx.iff differ diff --git a/autotests/read/iff/sv5_rgba8_rgx.png b/autotests/read/iff/sv5_rgba8_rgx.png new file mode 100644 index 0000000..93c4bee Binary files /dev/null and b/autotests/read/iff/sv5_rgba8_rgx.png differ diff --git a/src/imageformats/chunks.cpp b/src/imageformats/chunks.cpp index c9439ce..5d0e717 100644 --- a/src/imageformats/chunks.cpp +++ b/src/imageformats/chunks.cpp @@ -1,6 +1,6 @@ /* This file is part of the KDE project - SPDX-FileCopyrightText: 2025 Mirco Miranda + SPDX-FileCopyrightText: 2025-2026 Mirco Miranda SPDX-License-Identifier: LGPL-2.0-or-later */ @@ -325,8 +325,18 @@ IFFChunk::ChunkList IFFChunk::innerFromDevice(QIODevice *d, bool *ok, IFFChunk * chunk = QSharedPointer(new PLTEChunk()); } else if (cid == RAST_CHUNK) { chunk = QSharedPointer(new RASTChunk()); + } else if (cid == RBOD_CHUNK) { + chunk = QSharedPointer(new RBODChunk()); + } else if (cid == RCOL_CHUNK) { + chunk = QSharedPointer(new RCOLChunk()); + } else if (cid == RFLG_CHUNK) { + chunk = QSharedPointer(new RFLGChunk()); } else if (cid == RGBA_CHUNK) { chunk = QSharedPointer(new RGBAChunk()); + } else if (cid == RGHD_CHUNK) { + chunk = QSharedPointer(new RGHDChunk()); + } else if (cid == RSCM_CHUNK) { + chunk = QSharedPointer(new RSCMChunk()); } else if (cid == SHAM_CHUNK) { chunk = QSharedPointer(new SHAMChunk()); } else if (cid == TBHD_CHUNK) { @@ -1448,6 +1458,8 @@ bool FORMChunk::innerReadStructure(QIODevice *d) setChunks(IFFChunk::innerFromDevice(d, &ok, this)); } else if (_type == IMAG_FORM_TYPE) { setChunks(IFFChunk::innerFromDevice(d, &ok, this)); + } else if (_type == RGFX_FORM_TYPE) { + setChunks(IFFChunk::innerFromDevice(d, &ok, this)); } return ok; } @@ -1535,6 +1547,36 @@ QImage::Format FORMChunk::cdiFormat() const return QImage::Format_Invalid; } +QImage::Format FORMChunk::rgfxFormat() const +{ + auto headers = IFFChunk::searchT(chunks()); + if (headers.isEmpty()) { + return QImage::Format_Invalid; + } + + if (auto &&h = headers.first()) { + auto rgfx_format = RGHDChunk::BitmapTypes(h->bitmapType() & 0x3FFFFFFF); + if (rgfx_format == RGHDChunk::BitmapType::Chunky8 || rgfx_format == RGHDChunk::BitmapType::Planar8) + return QImage::Format_Indexed8; + if (rgfx_format == RGHDChunk::BitmapType::Rgb15 || rgfx_format == RGHDChunk::BitmapType::Rgb16) + return QImage::Format_RGB555; // NOTE: RGB16 ignoring alpha due to missing compatible Qt format + if (rgfx_format == RGHDChunk::BitmapType::Rgb24) + return FORMAT_RGB_8BIT; + if (rgfx_format == RGHDChunk::BitmapType::Rgb32) + return FORMAT_RGBA_8BIT; + if (rgfx_format == RGHDChunk::BitmapType::Rgb48) + return QImage::Format_RGBX64; + if (rgfx_format == RGHDChunk::BitmapType::Rgb64) + return QImage::Format_RGBA64; + if (rgfx_format == RGHDChunk::BitmapType::Rgb96) + return QImage::Format_RGBX32FPx4; + if (rgfx_format == RGHDChunk::BitmapType::Rgb128) + return QImage::Format_RGBA32FPx4; + } + + return QImage::Format_Invalid; +} + QByteArray FORMChunk::formType() const { return _type; @@ -1544,6 +1586,8 @@ QImage::Format FORMChunk::format() const { if (formType() == IMAG_FORM_TYPE) { return cdiFormat(); + } else if (formType() == RGFX_FORM_TYPE) { + return rgfxFormat(); } return iffFormat(); } @@ -1555,6 +1599,11 @@ QSize FORMChunk::size() const if (!ihdrs.isEmpty()) { return ihdrs.first()->size(); } + } else if (formType() == RGFX_FORM_TYPE) { + auto rghds = IFFChunk::searchT(chunks()); + if (!rghds.isEmpty()) { + return rghds.first()->size(); + } } else { auto bmhds = IFFChunk::searchT(chunks()); if (!bmhds.isEmpty()) { @@ -1676,6 +1725,8 @@ bool CATChunk::innerReadStructure(QIODevice *d) setChunks(IFFChunk::innerFromDevice(d, &ok, this)); } else if (_type == IMAG_FORM_TYPE) { setChunks(IFFChunk::innerFromDevice(d, &ok, this)); + } else if (_type == RGFX_FORM_TYPE) { + setChunks(IFFChunk::innerFromDevice(d, &ok, this)); } return ok; } @@ -3047,6 +3098,473 @@ quint32 IDATChunk::strideSize(const IHDRChunk *header) const } +/* ****************** + * *** RGHD Chunk *** + * ****************** */ + +RGHDChunk::~RGHDChunk() +{ + +} + +RGHDChunk::RGHDChunk() +{ + +} + +bool RGHDChunk::isValid() const +{ + return dataBytes() >= 13 * sizeof(quint32) && chunkId() == RGHDChunk::defaultChunkId(); +} + +QSize RGHDChunk::size() const +{ + return QSize(width(), height()); +} + +qint32 RGHDChunk::leftEdge() const +{ + if (!isValid()) { + return 0; + } + return i32(data(), 0); +} + +qint32 RGHDChunk::topEdge() const +{ + if (!isValid()) { + return 0; + } + return i32(data(), 4); +} + +qint32 RGHDChunk::width() const +{ + if (!isValid()) { + return 0; + } + return i32(data(), 8); +} + +qint32 RGHDChunk::height() const +{ + if (!isValid()) { + return 0; + } + return i32(data(), 12); +} + +qint32 RGHDChunk::pageWidth() const +{ + if (!isValid()) { + return 0; + } + return i32(data(), 16); +} + +qint32 RGHDChunk::pageHeight() const +{ + if (!isValid()) { + return 0; + } + return i32(data(), 20); +} + +quint32 RGHDChunk::depth() const +{ + if (!isValid()) { + return 0; + } + return ui32(data(), 24); +} + +quint32 RGHDChunk::pixelBits() const +{ + if (!isValid()) { + return 0; + } + return ui32(data(), 28); +} + +quint32 RGHDChunk::bytesPerLine() const +{ + if (!isValid()) { + return 0; + } + return ui32(data(), 32); +} + +RGHDChunk::Compression RGHDChunk::compression() const +{ + if (!isValid()) { + return Compression::Uncompressed; + } + return Compression(ui32(data(), 36)); +} + +quint32 RGHDChunk::xAspect() const +{ + if (!isValid()) { + return 0; + } + return ui32(data(), 40); +} + +quint32 RGHDChunk::yAspect() const +{ + if (!isValid()) { + return 0; + } + return ui32(data(), 44); +} + +double RGHDChunk::aspectRatio() const +{ + if (auto xr = xAspect()) { + auto yr = yAspect(); + return double(yr) / double(xr); + } + return 1; +} + +RGHDChunk::BitmapTypes RGHDChunk::bitmapType() const +{ + if (!isValid()) { + return BitmapType::Planar8; + } + return BitmapTypes(ui32(data(), 48)); +} + +bool RGHDChunk::innerReadStructure(QIODevice *d) +{ + return cacheData(d); +} + + +/* ****************** + * *** RCOL Chunk *** + * ****************** */ + +RCOLChunk::~RCOLChunk() +{ + +} + +RCOLChunk::RCOLChunk() +{ + +} + +bool RCOLChunk::isValid() const +{ + return dataBytes() >= 776 && chunkId() == RCOLChunk::defaultChunkId(); +} + +qint32 RCOLChunk::count() const +{ + return isValid() ? 256 : 0; +} + +QList RCOLChunk::innerPalette() const +{ + if (!isValid()) { + return {}; + } + + QList l; + auto &&d = data(); + for (qint32 i = 0, n = count(); i < n; ++i) { + auto i3 = i * 3 + 8; + l << qRgb(d.at(i3), d.at(i3 + 1), d.at(i3 + 2)); + } + + if (ui32(data(), 0)) { + auto tr = ui32(data(), 4); + if (tr < l.size()) { + l[tr] &= 0x00FFFFFF; + } + } + + return l; +} + + +/* ****************** + * *** RFLG Chunk *** + * ****************** */ + +RFLGChunk::~RFLGChunk() +{ + +} + +RFLGChunk::RFLGChunk() +{ + +} + +bool RFLGChunk::isValid() const +{ + return dataBytes() >= 4 && chunkId() == RFLGChunk::defaultChunkId(); +} + +RFLGChunk::Flags RFLGChunk::flags() const +{ + if (!isValid()) { + return {}; + } + return Flags(ui32(data(), 0)); +} + +bool RFLGChunk::innerReadStructure(QIODevice *d) +{ + return cacheData(d); +} + + +/* ****************** + * *** RSCM Chunk *** + * ****************** */ + +RSCMChunk::~RSCMChunk() +{ + +} + +RSCMChunk::RSCMChunk() +{ + +} + +bool RSCMChunk::isValid() const +{ + return dataBytes() >= 12 && chunkId() == RSCMChunk::defaultChunkId(); +} + +quint32 RSCMChunk::viewMode() const +{ + if (!isValid()) { + return 0; + } + return ui32(data(), 0); +} + +quint32 RSCMChunk::localVM0() const +{ + if (!isValid()) { + return 0; + } + return ui32(data(), 4); +} + +quint32 RSCMChunk::localVM1() const +{ + if (!isValid()) { + return 0; + } + return ui32(data(), 8); +} + +bool RSCMChunk::innerReadStructure(QIODevice *d) +{ + return cacheData(d); +} + + +/* ****************** + * *** RBOD Chunk *** + * ****************** */ + +RBODChunk::~RBODChunk() +{ + +} + +RBODChunk::RBODChunk() +{ + +} + +bool RBODChunk::isValid() const +{ + return chunkId() == RBODChunk::defaultChunkId(); +} + +QByteArray RBODChunk::strideRead(QIODevice *d, qint32 y, const RGHDChunk *header, const RSCMChunk *rcsm, const RCOLChunk *rcol) const +{ + if (!isValid() || header == nullptr || d == nullptr) { + return {}; + } + + QByteArray planes; + auto readSize = strideSize(header); + for (auto nextPos = nextChunkPos(); !d->atEnd() && d->pos() < nextPos && planes.size() < readSize;) { + if (header->compression() == RGHDChunk::Compression::Uncompressed) { + planes = d->read(readSize); + } else { + qCDebug(LOG_IFFPLUGIN) << "RBODChunk::strideRead(): unknown compression" << header->compression(); + } + if (planes.size() != readSize) { + return {}; + } + } + + return deinterleave(planes, y, header, rcsm, rcol); +} + +bool RBODChunk::resetStrideRead(QIODevice *d) const +{ + return seek(d); +} + +QByteArray RBODChunk::deinterleave(const QByteArray &planes, qint32 y, const RGHDChunk *header, const RSCMChunk *rcsm, const RCOLChunk *rcol) const +{ + Q_UNUSED(y) + Q_UNUSED(rcsm) + Q_UNUSED(rcol) + if (planes.size() != strideSize(header)) { + return {}; + } + + QByteArray ba; + + auto width = header->width(); + auto rgfx_format = RGHDChunk::BitmapTypes(header->bitmapType() & 0x3FFFFFFF); + + if (rgfx_format == RGHDChunk::BitmapType::Chunky8) { + ba = planes; + } else if (rgfx_format == RGHDChunk::BitmapType::Planar8) { + // No test case: ignoring... + } else if (rgfx_format == RGHDChunk::BitmapType::Rgb15 || rgfx_format == RGHDChunk::BitmapType::Rgb16) { + ba = planes; + if (QSysInfo::ByteOrder == QSysInfo::LittleEndian) { + for (qint32 x = 0; x < width; ++x) { + auto x2 = x * 2; + ba[x2] = planes[x2 + 1]; + ba[x2 + 1] = planes[x2]; + } + } + } else if (rgfx_format == RGHDChunk::BitmapType::Rgb24) { + ba = planes; + } else if (rgfx_format == RGHDChunk::BitmapType::Rgb32) { + ba.resize(planes.size()); + auto invAlpha = header->bitmapType() & RGHDChunk::BitmapType::HasInvAlpha; + for (qint32 x = 0; x < width; ++x) { + auto x4 = x * 4; + ba[x4] = planes[x4 + 1]; + ba[x4 + 1] = planes[x4 + 2]; + ba[x4 + 2] = planes[x4 + 3]; + ba[x4 + 3] = invAlpha ? 255 - planes[x4] : planes[x4]; + } + } else if (rgfx_format == RGHDChunk::BitmapType::Rgb48) { + QDataStream in(planes); + in.setByteOrder(QDataStream::BigEndian); + QDataStream ou(&ba, QDataStream::WriteOnly); + ou.setByteOrder(QDataStream::ByteOrder(QSysInfo::ByteOrder)); + + quint16 r, g, b; + for (qint32 x = 0; x < width; ++x) { + in >> r >> g >> b; + ou << r << g << b << quint16(0xFFFF); + } + + if (in.status() != QDataStream::Ok || ou.status() != QDataStream::Ok) { + return {}; + } + } else if (rgfx_format == RGHDChunk::BitmapType::Rgb64) { + QDataStream in(planes); + in.setByteOrder(QDataStream::BigEndian); + QDataStream ou(&ba, QDataStream::WriteOnly); + ou.setByteOrder(QDataStream::ByteOrder(QSysInfo::ByteOrder)); + + auto invAlpha = header->bitmapType() & RGHDChunk::BitmapType::HasInvAlpha; + quint16 r, g, b, a; + for (qint32 x = 0; x < width; ++x) { + in >> a >> r >> g >> b; + if (invAlpha) + a = 0xFFFF - a; + ou << r << g << b << a; + } + + if (in.status() != QDataStream::Ok || ou.status() != QDataStream::Ok) { + return {}; + } + } else if (rgfx_format == RGHDChunk::BitmapType::Rgb96) { + QDataStream in(planes); + in.setByteOrder(QDataStream::BigEndian); + in.setFloatingPointPrecision(QDataStream::SinglePrecision); + QDataStream ou(&ba, QDataStream::WriteOnly); + ou.setByteOrder(QDataStream::ByteOrder(QSysInfo::ByteOrder)); + ou.setFloatingPointPrecision(QDataStream::SinglePrecision); + + float r, g, b; + for (qint32 x = 0; x < width; ++x) { + in >> r >> g >> b; + ou << r << g << b << float(1); + } + + if (in.status() != QDataStream::Ok || ou.status() != QDataStream::Ok) { + return {}; + } + } else if (rgfx_format == RGHDChunk::BitmapType::Rgb128) { + QDataStream in(planes); + in.setByteOrder(QDataStream::BigEndian); + in.setFloatingPointPrecision(QDataStream::SinglePrecision); + QDataStream ou(&ba, QDataStream::WriteOnly); + ou.setByteOrder(QDataStream::ByteOrder(QSysInfo::ByteOrder)); + ou.setFloatingPointPrecision(QDataStream::SinglePrecision); + + auto invAlpha = header->bitmapType() & RGHDChunk::BitmapType::HasInvAlpha; + float r, g, b, a; + for (qint32 x = 0; x < width; ++x) { + in >> a >> r >> g >> b; + if (invAlpha) + a = 1 - a; + ou << r << g << b << a; + } + + if (in.status() != QDataStream::Ok || ou.status() != QDataStream::Ok) { + return {}; + } + } + + return ba; +} + +quint32 RBODChunk::strideSize(const RGHDChunk *header) const +{ + auto rgfx_format = RGHDChunk::BitmapTypes(header->bitmapType() & 0x3FFFFFFF); + if (rgfx_format == RGHDChunk::BitmapType::Planar8) { + return (header->width() + 7) / 8; + } + if (rgfx_format == RGHDChunk::BitmapType::Chunky8) { + return header->width(); + } + if (rgfx_format == RGHDChunk::BitmapType::Rgb15 || rgfx_format == RGHDChunk::BitmapType::Rgb16) { + return header->width() * 2; + } + if (rgfx_format == RGHDChunk::BitmapType::Rgb24) { + return header->width() * 3; + } + if (rgfx_format == RGHDChunk::BitmapType::Rgb32) { + return header->width() * 4; + } + if (rgfx_format == RGHDChunk::BitmapType::Rgb48) { + return header->width() * 6; + } + if (rgfx_format == RGHDChunk::BitmapType::Rgb64) { + return header->width() * 8; + } + if (rgfx_format == RGHDChunk::BitmapType::Rgb96) { + return header->width() * 12; + } + if (rgfx_format == RGHDChunk::BitmapType::Rgb128) { + return header->width() * 16; + } + return 0; +} + + /* ****************** * *** BEAM Chunk *** * ****************** */ diff --git a/src/imageformats/chunks_p.h b/src/imageformats/chunks_p.h index fc8c04d..df7835e 100644 --- a/src/imageformats/chunks_p.h +++ b/src/imageformats/chunks_p.h @@ -1,6 +1,6 @@ /* This file is part of the KDE project - SPDX-FileCopyrightText: 2025 Mirco Miranda + SPDX-FileCopyrightText: 2025-2026 Mirco Miranda SPDX-License-Identifier: LGPL-2.0-or-later */ @@ -10,6 +10,7 @@ * - 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) + * - https://aminet.net/package/dev/misc/IFF-RGFX */ #ifndef KIMG_CHUNKS_P_H @@ -59,6 +60,11 @@ Q_DECLARE_LOGGING_CATEGORY(LOG_IFFPLUGIN) #define IHDR_CHUNK QByteArray("IHDR") #define IPAR_CHUNK QByteArray("IPAR") #define PLTE_CHUNK QByteArray("PLTE") +#define RBOD_CHUNK QByteArray("RBOD") +#define RCOL_CHUNK QByteArray("RCOL") +#define RFLG_CHUNK QByteArray("RFLG") +#define RGHD_CHUNK QByteArray("RGHD") +#define RSCM_CHUNK QByteArray("RSCM") #define XBMI_CHUNK QByteArray("XBMI") #define YUVS_CHUNK QByteArray("YUVS") @@ -91,10 +97,11 @@ Q_DECLARE_LOGGING_CATEGORY(LOG_IFFPLUGIN) // FORM types #define ACBM_FORM_TYPE QByteArray("ACBM") #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 IMAG_FORM_TYPE QByteArray("IMAG") +#define RGFX_FORM_TYPE QByteArray("RGFX") #define CIMG_FOR4_TYPE QByteArray("CIMG") #define TBMP_FOR4_TYPE QByteArray("TBMP") @@ -937,6 +944,8 @@ private: QImage::Format iffFormat() const; QImage::Format cdiFormat() const; + + QImage::Format rgfxFormat() const; }; @@ -1730,6 +1739,213 @@ protected: quint32 strideSize(const IHDRChunk *header) const; }; + +/*! + * *** RGFX IFF CHUNKS *** + */ + +/*! + * \brief The RGHDChunk class + */ +class RGHDChunk : public IFFChunk +{ +public: + enum Compression { + Uncompressed = 0, + Xpk = 1, /**< any XPK-packer */ + Zip = 2 /**< libzip (LZ77/ZIP) compression */ + }; + + enum BitmapType { + Planar8 = 0x0000, /**< unaligned planar 8 bit bitmap */ + Chunky8 = 0x0001, /**< unaligned chunky 8 bit bitmap */ + Rgb24 = 0x0002, /**< 3-byte 24 bit RGB triples */ + Rgb32 = 0x0004, /**< 4-byte 32 bit ARGB quadruples */ + Rgb15 = 0x0010, /**< 2-byte 15 bit RGB (x+3x5 bit integer) */ + Rgb16 = 0x0020, /**< 2-byte 16 bit ARGB (1+3x5 bit integer) */ + Rgb48 = 0x0040, /**< 6-byte 48 bit RGB (3x 16 bit integer) */ + Rgb64 = 0x0080, /**< 8-byte 64 bit ARGB (4x 16 bit integer) */ + Rgb96 = 0x0100, /**< 12-byte 96 bit RGB (3x 32 bit float) */ + Rgb128 = 0x0200, /**< 16-byte 128 bit ARGB (4x 32 bit float) */ + + HasAlpha = (1 << 30), /**< set if A is meaningful */ + HasInvAlpha = (1 << 31) /**< set if A is meaningful but inversed (A = 255 - alpha) */ + }; + Q_DECLARE_FLAGS(BitmapTypes, BitmapType) + + virtual ~RGHDChunk() override; + RGHDChunk(); + RGHDChunk(const RGHDChunk&) = default; + RGHDChunk& operator=(const RGHDChunk&) = default; + + CHUNKID_DEFINE(RGHD_CHUNK) + + virtual bool isValid() const override; + + QSize size() const; + + qint32 leftEdge() const; + + qint32 topEdge() const; + + qint32 width() const; + + qint32 height() const; + + qint32 pageWidth() const; + + qint32 pageHeight() const; + + quint32 depth() const; + + quint32 pixelBits() const; + + quint32 bytesPerLine() const; + + Compression compression() const; + + quint32 xAspect() const; + + quint32 yAspect() const; + + BitmapTypes bitmapType() const; + + double aspectRatio() const; + +protected: + virtual bool innerReadStructure(QIODevice *d) override; +}; + + +/*! + * \brief The RCOLChunk class + */ +class RCOLChunk : public CMAPChunk +{ +public: + virtual ~RCOLChunk() override; + RCOLChunk(); + RCOLChunk(const RCOLChunk& other) = default; + RCOLChunk& operator =(const RCOLChunk& other) = default; + + virtual bool isValid() const override; + + virtual qint32 count() const override; + + CHUNKID_DEFINE(RCOL_CHUNK) + +protected: + virtual QList innerPalette() const override; +}; + + +/*! + * \brief The RFLGChunk class + */ +class RFLGChunk : public IFFChunk +{ +public: + enum class Flag : quint32 { + FromGray = 0x08, /**< created from 8/16 bit gray source so R==G==B */ + From8Bit = 0x10, /**< created from 8 bit source, so (R,G,B)&0xFF00 == ... & 0x00FF */ + From4Bit = 0x20, /**< created from 4 bit source, so (R,G,B)&0xF0 == ... & 0x0F */ + From8BitAlpha = 0x40, /**< 16/32 bit alpha created from 8 bit alpha source */ + From16BitAlpha = 0x80 /**< 32 bit alpha created from 16 bit alpha source */ + }; + Q_DECLARE_FLAGS(Flags, Flag) + + virtual ~RFLGChunk() override; + RFLGChunk(); + RFLGChunk(const RFLGChunk&) = default; + RFLGChunk& operator=(const RFLGChunk&) = default; + + CHUNKID_DEFINE(RFLG_CHUNK) + + virtual bool isValid() const override; + + Flags flags() const; + +protected: + virtual bool innerReadStructure(QIODevice *d) override; +}; + + +/*! + * \brief The RSCMChunk class + */ +class RSCMChunk : public IFFChunk +{ +public: + virtual ~RSCMChunk() override; + RSCMChunk(); + RSCMChunk(const RSCMChunk&) = default; + RSCMChunk& operator=(const RSCMChunk&) = default; + + CHUNKID_DEFINE(RSCM_CHUNK) + + virtual bool isValid() const override; + + /*! + * \brief viewMode Default screenmode + * + * Since HAM modes only can be identified by their ID (or DIPF) you have to make sure, + * that rcsm_ViewMode is OR'ed with HAM_KEY for these (same for EHB and EXTRAHALFBRITE_KEY). + * + * Specific RTG ViewModes will lose their meaning, as soon as graphics are transferred between + * different systems, which is why the two LocalVM entries are considered obsolete. + * + * Always set the obsolete entries to 0xFFFFFFFF and avoid interpreting them. + * \return default screenmode + */ + quint32 viewMode() const; + + /*! + * \brief localVM0 + * \obsolete obsolete local RTG + */ + quint32 localVM0() const; + + /*! + * \brief localVM1 + * \obsolete obsolete local RTG + */ + quint32 localVM1() const; + +protected: + virtual bool innerReadStructure(QIODevice *d) override; +}; + + +/*! + * \brief The RBODChunk class + */ +class RBODChunk : public IFFChunk +{ +public: + virtual ~RBODChunk() override; + RBODChunk(); + RBODChunk(const RBODChunk&) = default; + RBODChunk& operator=(const RBODChunk&) = default; + + CHUNKID_DEFINE(RBOD_CHUNK) + + virtual bool isValid() const override; + + QByteArray strideRead(QIODevice *d, + qint32 y, + const RGHDChunk *header, + const RSCMChunk *rcsm = nullptr, + const RCOLChunk *rcol = nullptr) const; + + bool resetStrideRead(QIODevice *d) const; + +private: + QByteArray deinterleave(const QByteArray &planes, qint32 y, const RGHDChunk *header, const RSCMChunk *rcsm = nullptr, const RCOLChunk *rcol = nullptr) const; + + quint32 strideSize(const RGHDChunk *header) const; +}; + + /*! * *** UNDOCUMENTED CHUNKS *** */ diff --git a/src/imageformats/iff.cpp b/src/imageformats/iff.cpp index f02d7a8..093ff93 100644 --- a/src/imageformats/iff.cpp +++ b/src/imageformats/iff.cpp @@ -267,6 +267,11 @@ static void addMetadata(QImage &img, const IFOR_Chunk *form) if (!params.isEmpty()) { img.setDotsPerMeterY(img.dotsPerMeterY() * params.first()->aspectRatio()); } + } else if (form->formType() == RGFX_FORM_TYPE) { + auto headers = IFFChunk::searchT(form); + if (!headers.isEmpty()) { + img.setDotsPerMeterY(img.dotsPerMeterY() * headers.first()->aspectRatio()); + } } else { auto headers = IFFChunk::searchT(form); if (!headers.isEmpty()) { @@ -543,6 +548,69 @@ bool IFFHandler::readCDIImage(QImage *image) return true; } +bool IFFHandler::readRGFXImage(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 img = imageAlloc(header->size(), form->format()); + if (img.isNull()) { + qCWarning(LOG_IFFPLUGIN) << "IFFHandler::readRGFXImage(): error while allocating the image"; + return false; + } + + // set the palette + if (img.format() == QImage::Format_Indexed8) { + auto pltes = IFFChunk::searchT(form); + if (!pltes.isEmpty()) { + img.setColorTable(pltes.first()->palette()); + } + } + + // 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::readRGFXImage(): error while reading image data"; + return false; + } + auto rcsms = IFFChunk::searchT(form); + auto rcols = 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, + rcsms.isEmpty() ? nullptr : rcsms.first(), + rcols.isEmpty() ? nullptr : rcols.first()); + if (ba.isEmpty()) { + qCWarning(LOG_IFFPLUGIN) << "IFFHandler::readRGFXImage(): 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())) { @@ -562,6 +630,10 @@ bool IFFHandler::read(QImage *image) return true; } + if (readRGFXImage(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 76cde44..145f632 100644 --- a/src/imageformats/iff_p.h +++ b/src/imageformats/iff_p.h @@ -37,6 +37,8 @@ private: bool readCDIImage(QImage *image); + bool readRGFXImage(QImage *image); + private: const QScopedPointer d; };