mirror of
https://invent.kde.org/frameworks/kimageformats.git
synced 2025-11-30 12:02:42 -05:00
IFF: add support for a different palette per line
This commit is contained in:
@ -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);
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
Reference in New Issue
Block a user