IFF: add uncompressed RGFX support

This commit is contained in:
Mirco Miranda
2026-01-27 11:52:15 +01:00
parent 32773e5f0c
commit e0f1ba640a
22 changed files with 849 additions and 3 deletions

View File

@@ -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.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

View File

@@ -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
}
}
]

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

View File

@@ -1,6 +1,6 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2025 Mirco Miranda <mircomir@outlook.com>
SPDX-FileCopyrightText: 2025-2026 Mirco Miranda <mircomir@outlook.com>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
@@ -325,8 +325,18 @@ IFFChunk::ChunkList IFFChunk::innerFromDevice(QIODevice *d, bool *ok, IFFChunk *
chunk = QSharedPointer<IFFChunk>(new PLTEChunk());
} else if (cid == RAST_CHUNK) {
chunk = QSharedPointer<IFFChunk>(new RASTChunk());
} else if (cid == RBOD_CHUNK) {
chunk = QSharedPointer<IFFChunk>(new RBODChunk());
} else if (cid == RCOL_CHUNK) {
chunk = QSharedPointer<IFFChunk>(new RCOLChunk());
} else if (cid == RFLG_CHUNK) {
chunk = QSharedPointer<IFFChunk>(new RFLGChunk());
} else if (cid == RGBA_CHUNK) {
chunk = QSharedPointer<IFFChunk>(new RGBAChunk());
} else if (cid == RGHD_CHUNK) {
chunk = QSharedPointer<IFFChunk>(new RGHDChunk());
} else if (cid == RSCM_CHUNK) {
chunk = QSharedPointer<IFFChunk>(new RSCMChunk());
} else if (cid == SHAM_CHUNK) {
chunk = QSharedPointer<IFFChunk>(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<RGHDChunk>(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<RGHDChunk>(chunks());
if (!rghds.isEmpty()) {
return rghds.first()->size();
}
} else {
auto bmhds = IFFChunk::searchT<BMHDChunk>(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<QRgb> RCOLChunk::innerPalette() const
{
if (!isValid()) {
return {};
}
QList<QRgb> 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 ***
* ****************** */

View File

@@ -1,6 +1,6 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2025 Mirco Miranda <mircomir@outlook.com>
SPDX-FileCopyrightText: 2025-2026 Mirco Miranda <mircomir@outlook.com>
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<QRgb> 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 ***
*/

View File

@@ -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<RGHDChunk>(form);
if (!headers.isEmpty()) {
img.setDotsPerMeterY(img.dotsPerMeterY() * headers.first()->aspectRatio());
}
} else {
auto headers = IFFChunk::searchT<BMHDChunk>(form);
if (!headers.isEmpty()) {
@@ -543,6 +548,69 @@ bool IFFHandler::readCDIImage(QImage *image)
return true;
}
bool IFFHandler::readRGFXImage(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<RGHDChunk>(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<RCOLChunk>(form);
if (!pltes.isEmpty()) {
img.setColorTable(pltes.first()->palette());
}
}
// decoding the image
auto bodies = IFFChunk::searchT<RBODChunk>(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<RSCMChunk>(form);
auto rcols = IFFChunk::searchT<RCOLChunk>(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,
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;
}

View File

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