IFF: add uncompressed RGFX support
@@ -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.
|
||||
|
||||
|
||||
BIN
autotests/read/iff/sv5_gray8_rgx.iff
Normal file
BIN
autotests/read/iff/sv5_gray8_rgx.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
BIN
autotests/read/iff/sv5_idx8_rgx.iff
Normal file
BIN
autotests/read/iff/sv5_idx8_rgx.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
autotests/read/iff/sv5_rgb16_rgx.iff
Normal file
BIN
autotests/read/iff/sv5_rgb16_rgx.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
autotests/read/iff/sv5_rgb32_rgx.iff
Normal file
BIN
autotests/read/iff/sv5_rgb32_rgx.png
Normal file
|
After Width: | Height: | Size: 7.2 KiB |
BIN
autotests/read/iff/sv5_rgb8_rgx.iff
Normal file
BIN
autotests/read/iff/sv5_rgb8_rgx.png
Normal file
|
After Width: | Height: | Size: 6.7 KiB |
BIN
autotests/read/iff/sv5_rgba16_rgx.iff
Normal file
BIN
autotests/read/iff/sv5_rgba16_rgx.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
autotests/read/iff/sv5_rgba32_rgx.iff
Normal file
37
autotests/read/iff/sv5_rgba32_rgx.iff.json
Normal 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
|
||||
}
|
||||
}
|
||||
]
|
||||
BIN
autotests/read/iff/sv5_rgba32_rgx.png
Normal file
|
After Width: | Height: | Size: 9.3 KiB |
BIN
autotests/read/iff/sv5_rgba8_rgx.iff
Normal file
BIN
autotests/read/iff/sv5_rgba8_rgx.png
Normal file
|
After Width: | Height: | Size: 9.2 KiB |
@@ -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 ***
|
||||
* ****************** */
|
||||
|
||||
@@ -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 ***
|
||||
*/
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -37,6 +37,8 @@ private:
|
||||
|
||||
bool readCDIImage(QImage *image);
|
||||
|
||||
bool readRGFXImage(QImage *image);
|
||||
|
||||
private:
|
||||
const QScopedPointer<IFFHandlerPrivate> d;
|
||||
};
|
||||
|
||||