IFF: add support for a different palette per line

This commit is contained in:
Mirco Miranda
2025-08-12 08:28:31 +02:00
parent 68cc915132
commit f63b082c85
5 changed files with 466 additions and 51 deletions

View File

@ -268,6 +268,8 @@ IFFChunk::ChunkList IFFChunk::innerFromDevice(QIODevice *d, bool *ok, IFFChunk *
chunk = QSharedPointer<IFFChunk>(new ANNOChunk());
} else if (cid == AUTH_CHUNK) {
chunk = QSharedPointer<IFFChunk>(new AUTHChunk());
} else if (cid == BEAM_CHUNK) {
chunk = QSharedPointer<IFFChunk>(new BEAMChunk());
} else if (cid == BMHD_CHUNK) {
chunk = QSharedPointer<IFFChunk>(new BMHDChunk());
} else if (cid == BODY_CHUNK) {
@ -282,6 +284,8 @@ IFFChunk::ChunkList IFFChunk::innerFromDevice(QIODevice *d, bool *ok, IFFChunk *
chunk = QSharedPointer<IFFChunk>(new CMYKChunk());
} else if (cid == COPY_CHUNK) {
chunk = QSharedPointer<IFFChunk>(new COPYChunk());
} else if (cid == CTBL_CHUNK) {
chunk = QSharedPointer<IFFChunk>(new CTBLChunk());
} else if (cid == DATE_CHUNK) {
chunk = QSharedPointer<IFFChunk>(new DATEChunk());
} else if (cid == DPI__CHUNK) {
@ -302,8 +306,12 @@ IFFChunk::ChunkList IFFChunk::innerFromDevice(QIODevice *d, bool *ok, IFFChunk *
chunk = QSharedPointer<IFFChunk>(new ICCPChunk());
} else if (cid == NAME_CHUNK) {
chunk = QSharedPointer<IFFChunk>(new NAMEChunk());
} else if (cid == RAST_CHUNK) {
chunk = QSharedPointer<IFFChunk>(new RASTChunk());
} else if (cid == RGBA_CHUNK) {
chunk = QSharedPointer<IFFChunk>(new RGBAChunk());
} else if (cid == SHAM_CHUNK) {
chunk = QSharedPointer<IFFChunk>(new SHAMChunk());
} else if (cid == TBHD_CHUNK) {
chunk = QSharedPointer<IFFChunk>(new TBHDChunk());
} else if (cid == VERS_CHUNK) {
@ -312,7 +320,7 @@ IFFChunk::ChunkList IFFChunk::innerFromDevice(QIODevice *d, bool *ok, IFFChunk *
chunk = QSharedPointer<IFFChunk>(new XMP0Chunk());
} else { // unknown chunk
chunk = QSharedPointer<IFFChunk>(new IFFChunk());
qCDebug(LOG_IFFPLUGIN) << "IFFChunk::innerFromDevice: unknown chunk" << cid;
qCDebug(LOG_IFFPLUGIN) << "IFFChunk::innerFromDevice(): unknown chunk" << cid;
}
// change the alignment to the one of main chunk (required for unknown Maya IFF chunks)
@ -779,7 +787,7 @@ inline qint64 rgbNDecompress(QIODevice *input, char *output, qint64 olen)
return j;
}
QByteArray BODYChunk::strideRead(QIODevice *d, const BMHDChunk *header, const CAMGChunk *camg, const CMAPChunk *cmap, const QByteArray& formType) const
QByteArray BODYChunk::strideRead(QIODevice *d, qint32 y, const BMHDChunk *header, const CAMGChunk *camg, const CMAPChunk *cmap, const IPALChunk *ipal, const QByteArray& formType) const
{
if (!isValid() || header == nullptr || d == nullptr) {
return {};
@ -812,7 +820,7 @@ QByteArray BODYChunk::strideRead(QIODevice *d, const BMHDChunk *header, const CA
} else if (header->compression() == BMHDChunk::Compression::Uncompressed) {
rr = d->read(buf.data(), buf.size()); // never seen
} else {
qCDebug(LOG_IFFPLUGIN) << "BODYChunk::strideRead: unknown compression" << header->compression();
qCDebug(LOG_IFFPLUGIN) << "BODYChunk::strideRead(): unknown compression" << header->compression();
}
if ((rr != readSize && lineCompressed) || (rr < 1))
return {};
@ -822,15 +830,15 @@ QByteArray BODYChunk::strideRead(QIODevice *d, const BMHDChunk *header, const CA
auto planes = _readBuffer.left(readSize);
_readBuffer.remove(0, readSize);
if (isPbm) {
return pbm(planes, header, camg, cmap);
return pbm(planes, y, header, camg, cmap, ipal);
}
if (isRgb8) {
return rgb8(planes, header, camg, cmap);
return rgb8(planes, y, header, camg, cmap, ipal);
}
if (isRgbN) {
return rgbN(planes, header, camg, cmap);
return rgbN(planes, y, header, camg, cmap, ipal);
}
return deinterleave(planes, header, camg, cmap);
return deinterleave(planes, y, header, camg, cmap, ipal);
}
bool BODYChunk::resetStrideRead(QIODevice *d) const
@ -879,7 +887,7 @@ quint32 BODYChunk::strideSize(const BMHDChunk *header, const QByteArray& formTyp
return header->rowLen() * header->bitplanes();
}
QByteArray BODYChunk::pbm(const QByteArray &planes, const BMHDChunk *header, const CAMGChunk *, const CMAPChunk *) const
QByteArray BODYChunk::pbm(const QByteArray &planes, qint32, const BMHDChunk *header, const CAMGChunk *, const CMAPChunk *, const IPALChunk *) const
{
if (planes.size() != strideSize(header, PBM__FORM_TYPE)) {
return {};
@ -891,7 +899,7 @@ QByteArray BODYChunk::pbm(const QByteArray &planes, const BMHDChunk *header, con
return {};
}
QByteArray BODYChunk::rgb8(const QByteArray &planes, const BMHDChunk *header, const CAMGChunk *, const CMAPChunk *) const
QByteArray BODYChunk::rgb8(const QByteArray &planes, qint32, const BMHDChunk *header, const CAMGChunk *, const CMAPChunk *, const IPALChunk *) const
{
if (planes.size() != strideSize(header, RGB8_FORM_TYPE)) {
return {};
@ -899,7 +907,7 @@ QByteArray BODYChunk::rgb8(const QByteArray &planes, const BMHDChunk *header, co
return planes;
}
QByteArray BODYChunk::rgbN(const QByteArray &planes, const BMHDChunk *header, const CAMGChunk *, const CMAPChunk *) const
QByteArray BODYChunk::rgbN(const QByteArray &planes, qint32, const BMHDChunk *header, const CAMGChunk *, const CMAPChunk *, const IPALChunk *) const
{
if (planes.size() != strideSize(header, RGBN_FORM_TYPE)) {
return {};
@ -907,7 +915,7 @@ QByteArray BODYChunk::rgbN(const QByteArray &planes, const BMHDChunk *header, co
return planes;
}
QByteArray BODYChunk::deinterleave(const QByteArray &planes, const BMHDChunk *header, const CAMGChunk *camg, const CMAPChunk *cmap) const
QByteArray BODYChunk::deinterleave(const QByteArray &planes, qint32 y, const BMHDChunk *header, const CAMGChunk *camg, const CMAPChunk *cmap, const IPALChunk *ipal) const
{
if (planes.size() != strideSize(header, ILBM_FORM_TYPE)) {
return {};
@ -950,6 +958,11 @@ QByteArray BODYChunk::deinterleave(const QByteArray &planes, const BMHDChunk *he
// 11 - hold previous. replacing Green component with bits from planes 0-3
ba = QByteArray(rowLen * 8 * 3, char());
auto pal = cmap->palette();
if (ipal) {
auto tmp = ipal->palette(y, header->height());
if (tmp.size() == pal.size())
pal = tmp;
}
auto max = (1 << (bitplanes - 2)) - 1;
quint8 prev[3] = {};
for (qint32 i = 0, cnt = 0; i < rowLen; ++i) {
@ -979,7 +992,7 @@ QByteArray BODYChunk::deinterleave(const QByteArray &planes, const BMHDChunk *he
prev[1] = qGreen(pal.at(idx));
prev[2] = qBlue(pal.at(idx));
} else {
qCWarning(LOG_IFFPLUGIN) << "BODYChunk::deinterleave: palette index" << idx << "is out of range";
qCWarning(LOG_IFFPLUGIN) << "BODYChunk::deinterleave(): palette index" << idx << "is out of range";
}
break;
}
@ -1015,7 +1028,7 @@ QByteArray BODYChunk::deinterleave(const QByteArray &planes, const BMHDChunk *he
if (idx < palSize) {
ba[cnt] = ctl ? idx + palSize : idx;
} else {
qCWarning(LOG_IFFPLUGIN) << "BODYChunk::deinterleave: palette index" << idx << "is out of range";
qCWarning(LOG_IFFPLUGIN) << "BODYChunk::deinterleave(): palette index" << idx << "is out of range";
}
}
}
@ -1174,7 +1187,7 @@ bool ABITChunk::isValid() const
return chunkId() == ABITChunk::defaultChunkId();
}
QByteArray ABITChunk::strideRead(QIODevice *d, const BMHDChunk *header, const CAMGChunk *camg, const CMAPChunk *cmap, const QByteArray& formType) const
QByteArray ABITChunk::strideRead(QIODevice *d, qint32 y, const BMHDChunk *header, const CAMGChunk *camg, const CMAPChunk *cmap, const IPALChunk *ipal, const QByteArray& formType) const
{
if (!isValid() || header == nullptr || d == nullptr) {
return {};
@ -1187,11 +1200,11 @@ QByteArray ABITChunk::strideRead(QIODevice *d, const BMHDChunk *header, const CA
auto ilbmLine = QByteArray(strideSize(header, formType), char());
auto rowSize = header->rowLen();
auto height = header->height();
if (_y >= height) {
if (y >= height) {
return {};
}
for (qint32 plane = 0, planes = qint32(header->bitplanes()); plane < planes; ++plane) {
if (!seek(d, qint64(plane) * rowSize * height + _y * rowSize))
if (!seek(d, qint64(plane) * rowSize * height + y * rowSize))
return {};
auto offset = qint64(plane) * rowSize;
if (offset + rowSize > ilbmLine.size())
@ -1199,8 +1212,6 @@ QByteArray ABITChunk::strideRead(QIODevice *d, const BMHDChunk *header, const CA
if (d->read(ilbmLine.data() + offset, rowSize) != rowSize)
return {};
}
// next line on the next run
++_y;
// decode the ILBM line
QBuffer buf;
@ -1208,12 +1219,11 @@ QByteArray ABITChunk::strideRead(QIODevice *d, const BMHDChunk *header, const CA
if (!buf.open(QBuffer::ReadOnly)) {
return {};
}
return BODYChunk::strideRead(&buf, header, camg, cmap, ILBM_FORM_TYPE);
return BODYChunk::strideRead(&buf, y, header, camg, cmap, ipal, ILBM_FORM_TYPE);
}
bool ABITChunk::resetStrideRead(QIODevice *d) const
{
_y = 0;
return BODYChunk::resetStrideRead(d);
}
@ -1243,6 +1253,41 @@ QImageIOHandler::Transformation IFOR_Chunk::transformation() const
return QImageIOHandler::Transformation::TransformationNone;
}
QImage::Format IFOR_Chunk::optionformat() const
{
auto fmt = this->format();
if (fmt == QImage::Format_Indexed8) {
if (searchIPal())
fmt = FORMAT_RGB_8BIT;
}
return fmt;
}
const IPALChunk *IFOR_Chunk::searchIPal() const
{
const IPALChunk *ipal = nullptr;
auto beam = IFFChunk::searchT<BEAMChunk>(this);
if (!beam.isEmpty()) {
ipal = beam.first();
}
auto ctbl = IFFChunk::searchT<CTBLChunk>(this);
if (!ctbl.isEmpty()) {
ipal = ctbl.first();
}
auto sham = IFFChunk::searchT<SHAMChunk>(this);
if (!sham.isEmpty()) {
ipal = sham.first();
}
auto rast = IFFChunk::searchT<RASTChunk>(this);
if (!rast.isEmpty()) {
ipal = rast.first();
}
if (ipal && ipal->isValid()) {
return ipal;
}
return nullptr;
}
/* ******************
* *** FORM Chunk ***
@ -1312,10 +1357,10 @@ QImage::Format FORMChunk::format() const
auto camgs = IFFChunk::searchT<CAMGChunk>(chunks());
auto modeId = BODYChunk::safeModeId(h, camgs.isEmpty() ? nullptr : camgs.first(), cmaps.isEmpty() ? nullptr : cmaps.first());
if (h->bitplanes() == 13) {
return QImage::Format_RGB888; // NOTE: with a little work you could use Format_RGB444
return FORMAT_RGB_8BIT; // NOTE: with a little work you could use Format_RGB444
}
if (h->bitplanes() == 24 || h->bitplanes() == 25) {
return QImage::Format_RGB888;
return FORMAT_RGB_8BIT;
}
if (h->bitplanes() == 48) {
return QImage::Format_RGBX64;
@ -1327,19 +1372,14 @@ QImage::Format FORMChunk::format() const
return QImage::Format_RGBA64;
}
if (h->bitplanes() >= 1 && h->bitplanes() <= 8) {
if (!IFFChunk::search(SHAM_CHUNK, chunks()).isEmpty()
|| !IFFChunk::search(RAST_CHUNK, chunks()).isEmpty()
|| !IFFChunk::search(CTBL_CHUNK, chunks()).isEmpty()
|| !IFFChunk::search(BEAM_CHUNK, chunks()).isEmpty()) {
// Images with the SHAM, RAST or BEAM/CTBL chunk do not load correctly:
// it seems they contains a color table but I didn't find any specs.
qCDebug(LOG_IFFPLUGIN) << "FORMChunk::format(): BEAM/CTBL/RAST/SHAM chunk is not supported";
if (!IFFChunk::search(PCHG_CHUNK, chunks()).isEmpty()) {
qCDebug(LOG_IFFPLUGIN) << "FORMChunk::format(): PCHG chunk is not supported";
return QImage::Format_Invalid;
}
if (h->bitplanes() >= BITPLANES_HAM_MIN && h->bitplanes() <= BITPLANES_HAM_MAX) {
if (modeId & CAMGChunk::ModeId::Ham)
return QImage::Format_RGB888;
return FORMAT_RGB_8BIT;
}
if (!cmaps.isEmpty()) {
@ -1348,7 +1388,7 @@ QImage::Format FORMChunk::format() const
return QImage::Format_Grayscale8;
}
qCDebug(LOG_IFFPLUGIN) << "FORMChunk::format: Unsupported" << h->bitplanes() << "bitplanes";
qCDebug(LOG_IFFPLUGIN) << "FORMChunk::format(): Unsupported" << h->bitplanes() << "bitplanes";
}
return QImage::Format_Invalid;
@ -1588,7 +1628,7 @@ QImage::Format TBHDChunk::format() const
if (bpc() == 2)
return QImage::Format_RGBX64;
else if (bpc() == 1)
return QImage::Format_RGB888;
return FORMAT_RGB_8BIT;
}
return QImage::Format_Invalid;
@ -2259,3 +2299,186 @@ bool XMP0Chunk::innerReadStructure(QIODevice *d)
{
return cacheData(d);
}
/* ******************
* *** BEAM Chunk ***
* ****************** */
BEAMChunk::~BEAMChunk()
{
}
BEAMChunk::BEAMChunk() : IPALChunk()
{
}
bool BEAMChunk::isValid() const
{
return chunkId() == BEAMChunk::defaultChunkId();
}
QList<QRgb> BEAMChunk::palette(qint32 y, qint32 height) const
{
if (height < 1) {
return {};
}
auto bpp = bytes() / height;
if (bytes() != height * bpp) {
return {};
}
auto col = qint32(bpp / 2);
auto &&dt = data();
QList<QRgb> pal;
for (auto c = 0; c < col; ++c) {
// 2 bytes per color (0x0R 0xGB)
auto idx = bpp * y + c * 2;
auto r = quint8(dt[idx] & 0x0F);
auto g = quint8(dt[idx + 1] & 0xF0);
auto b = quint8(dt[idx + 1] & 0x0F);
pal << qRgb(r | (r << 4), (g >> 4) | g, b | (b << 4));
}
return pal;
}
bool BEAMChunk::innerReadStructure(QIODevice *d)
{
return cacheData(d);
}
/* ******************
* *** CTBL Chunk ***
* ****************** */
CTBLChunk::~CTBLChunk()
{
}
CTBLChunk::CTBLChunk() : BEAMChunk()
{
}
bool CTBLChunk::isValid() const
{
return chunkId() == CTBLChunk::defaultChunkId();
}
/* ******************
* *** SHAM Chunk ***
* ****************** */
SHAMChunk::~SHAMChunk()
{
}
SHAMChunk::SHAMChunk() : IPALChunk()
{
}
bool SHAMChunk::isValid() const
{
if (bytes() < 2) {
return false;
}
auto &&dt = data();
if (dt[0] != 0 && dt[1] != 0) {
// In all the sham test cases I have them at zero...
// if they are different from zero I suppose they should
// be interpreted differently from what was done.
return false;
}
return chunkId() == SHAMChunk::defaultChunkId();
}
QList<QRgb> SHAMChunk::palette(qint32 y, qint32 height) const
{
if (height < 1) {
return {};
}
auto bpp = 32; // always 32 bytes per palette (16 colors)
auto div = 0;
if (bytes() == quint32(height * bpp + 2)) {
div = 1;
} else if (bytes() == quint32(height / 2 * bpp + 2)) {
div = 2;
}
if (div == 0) {
return {};
}
auto &&dt = data();
QList<QRgb> pal;
for (auto c = 0, col = bpp / 2, idx0 = y / div * bpp + 2; c < col; ++c) {
// 2 bytes per color (0x0R 0xGB)
auto idx = idx0 + c * 2;
auto r = quint8(dt[idx] & 0x0F);
auto g = quint8(dt[idx + 1] & 0xF0);
auto b = quint8(dt[idx + 1] & 0x0F);
pal << qRgb(r | (r << 4), (g >> 4) | g, b | (b << 4));
}
return pal;
}
bool SHAMChunk::innerReadStructure(QIODevice *d)
{
return cacheData(d);
}
/* ******************
* *** RAST Chunk ***
* ****************** */
RASTChunk::~RASTChunk()
{
}
RASTChunk::RASTChunk() : IPALChunk()
{
}
bool RASTChunk::isValid() const
{
return chunkId() == RASTChunk::defaultChunkId();
}
QList<QRgb> RASTChunk::palette(qint32 y, qint32 height) const
{
if (height < 1) {
return {};
}
auto bpp = bytes() / height;
if (bytes() != height * bpp) {
return {};
}
auto col = qint32(bpp / 2 - 1);
auto &&dt = data();
QList<QRgb> pal;
for (auto c = 0; c < col; ++c) {
auto idx = bpp * y + 2 + c * 2;
// The Atari ST uses 3 bits per color (512 colors) while the Atari STE
// uses 4 bits per color (4096 colors). This strange encoding with the
// least significant bit set as MSB is, I believe, to ensure hardware
// compatibility between the two machines.
#define H1L(a) ((quint8(a) & 0x7) << 1) | ((quint8(a) >> 3) & 1)
auto r = H1L(dt[idx]);
auto g = H1L(dt[idx + 1] >> 4);
auto b = H1L(dt[idx + 1]);
#undef H1L
pal << qRgb(r | (r << 4), (g << 4) | g, b | (b << 4));
}
return pal;
}
bool RASTChunk::innerReadStructure(QIODevice *d)
{
return cacheData(d);
}

View File

@ -55,10 +55,12 @@ Q_DECLARE_LOGGING_CATEGORY(LOG_IFFPLUGIN)
#define CMYK_CHUNK QByteArray("CMYK") // https://wiki.amigaos.net/wiki/ILBM_IFF_Interleaved_Bitmap#ILBM.CMYK
#define DPI__CHUNK QByteArray("DPI ")
#define BEAM_CHUNK QByteArray("BEAM") // undocumented (should be the same as CTBL)
#define CTBL_CHUNK QByteArray("CTBL") // undocumented
#define RAST_CHUNK QByteArray("RAST") // undocumented (Atari STE)
#define SHAM_CHUNK QByteArray("SHAM") // undocumented
// Different palette for scanline
#define BEAM_CHUNK QByteArray("BEAM")
#define CTBL_CHUNK QByteArray("CTBL") // same as BEAM
#define PCHG_CHUNK QByteArray("PCHG") // encoded in a unknown way (to be investigated)
#define RAST_CHUNK QByteArray("RAST") // Atari ST(E)
#define SHAM_CHUNK QByteArray("SHAM")
// FOR4 CIMG IFF (Maya)
#define RGBA_CHUNK QByteArray("RGBA")
@ -89,6 +91,12 @@ Q_DECLARE_LOGGING_CATEGORY(LOG_IFFPLUGIN)
#define CHUNKID_DEFINE(a) static QByteArray defaultChunkId() { return a; }
// The 8-bit RGB format must be one. If you change it here, you have also to use the same
// when converting an image with BEAM/CTBL/SHAM chunks otherwise the option(QImageIOHandler::ImageFormat)
// could returns a wrong value.
// Warning: Changing it requires changing the algorithms. Se, don't touch! :)
#define FORMAT_RGB_8BIT QImage::Format_RGB888
/*!
* \brief The IFFChunk class
*/
@ -341,6 +349,19 @@ private:
qint32 _recursionCnt;
};
/*!
* \brief The IPALChunk class
* Interface for additional per-line palette.
*/
class IPALChunk : public IFFChunk
{
public:
virtual ~IPALChunk() override {}
IPALChunk() : IFFChunk() {}
virtual QList<QRgb> palette(qint32 y, qint32 height) const = 0;
};
/*!
* \brief The BMHDChunk class
* Bitmap Header
@ -630,13 +651,20 @@ public:
* \brief readStride
* \param d The device.
* \param header The bitmap header.
* \param y The current scanline.
* \param camg The CAMG chunk (optional)
* \param cmap The CMAP chunk (optional)
* \param formType The type of the current form chunk.
* \return The scanline as requested for QImage.
* \warning Call resetStrideRead() once before this one.
*/
virtual QByteArray strideRead(QIODevice *d, const BMHDChunk *header, const CAMGChunk *camg = nullptr, const CMAPChunk *cmap = nullptr, const QByteArray& formType = ILBM_FORM_TYPE) const;
virtual QByteArray strideRead(QIODevice *d,
qint32 y,
const BMHDChunk *header,
const CAMGChunk *camg = nullptr,
const CMAPChunk *cmap = nullptr,
const IPALChunk *ipal = nullptr,
const QByteArray& formType = ILBM_FORM_TYPE) const;
/*!
* \brief resetStrideRead
@ -666,13 +694,13 @@ protected:
*/
quint32 strideSize(const BMHDChunk *header, const QByteArray& formType) const;
QByteArray deinterleave(const QByteArray &planes, const BMHDChunk *header, const CAMGChunk *camg = nullptr, const CMAPChunk *cmap = nullptr) const;
QByteArray deinterleave(const QByteArray &planes, qint32 y, const BMHDChunk *header, const CAMGChunk *camg = nullptr, const CMAPChunk *cmap = nullptr, const IPALChunk *ipal = nullptr) const;
QByteArray pbm(const QByteArray &planes, const BMHDChunk *header, const CAMGChunk *camg = nullptr, const CMAPChunk *cmap = nullptr) const;
QByteArray pbm(const QByteArray &planes, qint32 y, const BMHDChunk *header, const CAMGChunk *camg = nullptr, const CMAPChunk *cmap = nullptr, const IPALChunk *ipal = nullptr) const;
QByteArray rgb8(const QByteArray &planes, const BMHDChunk *header, const CAMGChunk *camg = nullptr, const CMAPChunk *cmap = nullptr) const;
QByteArray rgb8(const QByteArray &planes, qint32 y, const BMHDChunk *header, const CAMGChunk *camg = nullptr, const CMAPChunk *cmap = nullptr, const IPALChunk *ipal = nullptr) const;
QByteArray rgbN(const QByteArray &planes, const BMHDChunk *header, const CAMGChunk *camg = nullptr, const CMAPChunk *cmap = nullptr) const;
QByteArray rgbN(const QByteArray &planes, qint32 y, const BMHDChunk *header, const CAMGChunk *camg = nullptr, const CMAPChunk *cmap = nullptr, const IPALChunk *ipal = nullptr) const;
private:
mutable QByteArray _readBuffer;
@ -694,12 +722,15 @@ public:
CHUNKID_DEFINE(ABIT_CHUNK)
virtual QByteArray strideRead(QIODevice *d, const BMHDChunk *header, const CAMGChunk *camg = nullptr, const CMAPChunk *cmap = nullptr, const QByteArray& formType = ACBM_FORM_TYPE) const override;
virtual QByteArray strideRead(QIODevice *d,
qint32 y,
const BMHDChunk *header,
const CAMGChunk *camg = nullptr,
const CMAPChunk *cmap = nullptr,
const IPALChunk *ipal = nullptr,
const QByteArray& formType = ACBM_FORM_TYPE) const override;
virtual bool resetStrideRead(QIODevice *d) const override;
private:
mutable qint32 _y;
};
/*!
@ -742,6 +773,19 @@ public:
* \return The image size in pixels.
*/
virtual QSize size() const = 0;
/*!
* \brief optionformat
* \return The format retuned by the plugin after all conversions.
*/
QImage::Format optionformat() const;
/*!
* \brief searchIPal
* Search the palett per line chunk.
* \return The per line palette (BEAM, CTBL, SHAM, etc....).
*/
const IPALChunk *searchIPal() const;
};
/*!
@ -1250,4 +1294,92 @@ protected:
virtual bool innerReadStructure(QIODevice *d) override;
};
/*!
* *** UNDOCUMENTED CHUNKS ***
*/
/*!
* \brief The BEAMChunk class
*/
class BEAMChunk : public IPALChunk
{
public:
virtual ~BEAMChunk() override;
BEAMChunk();
BEAMChunk(const BEAMChunk& other) = default;
BEAMChunk& operator =(const BEAMChunk& other) = default;
virtual bool isValid() const override;
virtual QList<QRgb> palette(qint32 y, qint32 height) const override;
CHUNKID_DEFINE(BEAM_CHUNK)
protected:
virtual bool innerReadStructure(QIODevice *d) override;
};
/*!
* \brief The CTBLChunk class
*/
class CTBLChunk : public BEAMChunk
{
public:
virtual ~CTBLChunk() override;
CTBLChunk();
CTBLChunk(const CTBLChunk& other) = default;
CTBLChunk& operator =(const CTBLChunk& other) = default;
virtual bool isValid() const override;
CHUNKID_DEFINE(CTBL_CHUNK)
};
/*!
* \brief The SHAMChunk class
*/
class SHAMChunk : public IPALChunk
{
public:
virtual ~SHAMChunk() override;
SHAMChunk();
SHAMChunk(const SHAMChunk& other) = default;
SHAMChunk& operator =(const SHAMChunk& other) = default;
virtual bool isValid() const override;
virtual QList<QRgb> palette(qint32 y, qint32 height) const override;
CHUNKID_DEFINE(SHAM_CHUNK)
protected:
virtual bool innerReadStructure(QIODevice *d) override;
};
/*!
* \brief The RASTChunk class
* \note I found an Atari STE image with the RAST chunk outside
* the form chunk (Fish.neo.iff). To support it the IFF parser
* should be changed so, this kind of IFFs are shown wrong.
*/
class RASTChunk : public IPALChunk
{
public:
virtual ~RASTChunk() override;
RASTChunk();
RASTChunk(const RASTChunk& other) = default;
RASTChunk& operator =(const RASTChunk& other) = default;
virtual bool isValid() const override;
virtual QList<QRgb> palette(qint32 y, qint32 height) const override;
CHUNKID_DEFINE(RAST_CHUNK)
protected:
virtual bool innerReadStructure(QIODevice *d) override;
};
#endif // KIMG_CHUNKS_P_H

View File

@ -134,7 +134,7 @@ bool IFFHandler::canRead(QIODevice *device)
return ok;
}
void addMetadata(QImage& img, const IFFChunk *form)
static void addMetadata(QImage &img, const IFOR_Chunk *form)
{
// standard IFF metadata
auto annos = IFFChunk::searchT<ANNOChunk>(form);
@ -183,11 +183,12 @@ void addMetadata(QImage& img, const IFFChunk *form)
}
// SView5 metadata
auto resChanged = false;
auto exifs = IFFChunk::searchT<EXIFChunk>(form);
if (!exifs.isEmpty()) {
auto exif = exifs.first()->value();
exif.updateImageMetadata(img, false);
exif.updateImageResolution(img);
resChanged = exif.updateImageResolution(img);
}
auto xmp0s = IFFChunk::searchT<XMP0Chunk>(form);
@ -219,8 +220,59 @@ void addMetadata(QImage& img, const IFFChunk *form)
if (dpi->isValid()) {
img.setDotsPerMeterX(dpi->dotsPerMeterX());
img.setDotsPerMeterY(dpi->dotsPerMeterY());
resChanged = true;
}
}
// if no explicit resolution was found, apply the aspect ratio to the default one
if (!resChanged) {
auto headers = IFFChunk::searchT<BMHDChunk>(form);
if (!headers.isEmpty()) {
auto xr = headers.first()->xAspectRatio();
auto yr = headers.first()->yAspectRatio();
if (xr > 0 && yr > 0 && xr > yr) {
img.setDotsPerMeterX(img.dotsPerMeterX() * yr / xr);
} else if (xr > 0 && yr > 0 && xr < yr) {
img.setDotsPerMeterY(img.dotsPerMeterY() * xr / yr);
}
}
}
}
/*!
* \brief convertIPAL
* \param img The source image.
* \param ipal The per line palette.
* \return The new image converted or \a img if no conversion is needed or possible.
*/
static QImage convertIPAL(const QImage& img, const IPALChunk *ipal)
{
if (img.format() != QImage::Format_Indexed8) {
qDebug(LOG_IFFPLUGIN) << "convertIPAL(): the image is not indexed!";
return img;
}
auto tmp = img.convertToFormat(FORMAT_RGB_8BIT);
if (tmp.isNull()) {
qCritical(LOG_IFFPLUGIN) << "convertIPAL(): error while converting the image!";
return img;
}
for (auto y = 0, h = img.height(); y < h; ++y) {
auto src = reinterpret_cast<const quint8 *>(img.constScanLine(y));
auto dst = tmp.scanLine(y);
auto lpal = ipal->palette(y, h);
for (auto x = 0, w = img.width(); x < w; ++x) {
if (src[x] < lpal.size()) {
auto x3 = x * 3;
dst[x3] = qRed(lpal.at(src[x]));
dst[x3 + 1] = qGreen(lpal.at(src[x]));
dst[x3 + 2] = qBlue(lpal.at(src[x]));
}
}
}
return tmp;
}
bool IFFHandler::readStandardImage(QImage *image)
@ -272,6 +324,7 @@ bool IFFHandler::readStandardImage(QImage *image)
}
// reading image data
auto ipal = form->searchIPal();
auto bodies = IFFChunk::searchT<BODYChunk>(form);
if (bodies.isEmpty()) {
auto abits = IFFChunk::searchT<ABITChunk>(form);
@ -288,7 +341,7 @@ bool IFFHandler::readStandardImage(QImage *image)
}
for (auto y = 0, h = img.height(); y < h; ++y) {
auto line = reinterpret_cast<char*>(img.scanLine(y));
auto ba = body->strideRead(device(), header, camg, cmap, form->formType());
auto ba = body->strideRead(device(), y, header, camg, cmap, ipal, form->formType());
if (ba.isEmpty()) {
qCWarning(LOG_IFFPLUGIN) << "IFFHandler::readStandardImage() error while reading image scanline";
return false;
@ -297,6 +350,11 @@ bool IFFHandler::readStandardImage(QImage *image)
}
}
// BEAM / CTBL conversion (if not already done)
if (ipal && img.format() == QImage::Format_Indexed8) {
img = convertIPAL(img, ipal);
}
// set metadata (including image resolution)
addMetadata(img, form);
@ -423,7 +481,7 @@ QVariant IFFHandler::option(ImageOption option) const
}
if (option == QImageIOHandler::ImageFormat) {
return QVariant::fromValue(form->format());
return QVariant::fromValue(form->optionformat());
}
if (option == QImageIOHandler::ImageTransformation) {

View File

@ -1193,12 +1193,13 @@ void MicroExif::updateImageMetadata(QImage &targetImage, bool replaceExisting) c
}
}
void MicroExif::updateImageResolution(QImage &targetImage)
bool MicroExif::updateImageResolution(QImage &targetImage)
{
if (horizontalResolution() > 0)
targetImage.setDotsPerMeterX(qRound(horizontalResolution() / 25.4 * 1000));
if (verticalResolution() > 0)
targetImage.setDotsPerMeterY(qRound(verticalResolution() / 25.4 * 1000));
return (horizontalResolution() > 0) || (verticalResolution() > 0);
}
MicroExif MicroExif::fromByteArray(const QByteArray &ba, bool searchHeader)

View File

@ -339,8 +339,9 @@ public:
* \brief updateImageResolution
* Helper to set the EXIF resolution to the image. Resolution is set only if valid.
* \param targetImage The image to set resolution on.
* \return True if either the x-resolution or the y-resolution has been changed, otherwise false.
*/
void updateImageResolution(QImage &targetImage);
bool updateImageResolution(QImage &targetImage);
/*!
* \brief fromByteArray