Compare commits

..

1 Commits

Author SHA1 Message Date
a9ccdce598 IFF: Fix possible stack overflow
(cherry picked from commit ea1983a7d1)
2025-07-11 15:13:32 +02:00
14 changed files with 143 additions and 775 deletions

View File

@ -1,6 +1,6 @@
cmake_minimum_required(VERSION 3.16) cmake_minimum_required(VERSION 3.16)
set(KF_VERSION "6.17.0") # handled by release scripts set(KF_VERSION "6.16.0") # handled by release scripts
set(KF_DEP_VERSION "6.16.0") # handled by release scripts set(KF_DEP_VERSION "6.16.0") # handled by release scripts
project(KImageFormats VERSION ${KF_VERSION}) project(KImageFormats VERSION ${KF_VERSION})

Binary file not shown.

View File

@ -1,31 +0,0 @@
[
{
"fileName" : "metadata.png",
"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.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

View File

@ -84,7 +84,7 @@ endif()
################################## ##################################
kimageformats_add_plugin(kimg_iff SOURCES iff.cpp chunks.cpp microexif.cpp) kimageformats_add_plugin(kimg_iff SOURCES iff.cpp chunks.cpp)
################################## ##################################

View File

@ -37,19 +37,8 @@ bool IFFChunk::operator ==(const IFFChunk &other) const
bool IFFChunk::isValid() const bool IFFChunk::isValid() const
{ {
auto cid = chunkId(); auto cid = chunkId();
if (cid.isEmpty()) {
return false;
}
// A “type ID”, “property name”, “FORM type”, or any other IFF
// identifier is a 32-bit value: the concatenation of four ASCII
// characters in the range “ ” (SP, hex 20) through “~” (hex 7E).
// Spaces (hex 20) should not precede printing characters;
// trailing spaces are OK. Control characters are forbidden.
if (cid.at(0) == ' ') {
return false;
}
for (auto &&c : cid) { for (auto &&c : cid) {
if (c < ' ' || c > '~') if (!((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c == ' ')))
return false; return false;
} }
return true; return true;
@ -69,7 +58,10 @@ bool IFFChunk::readStructure(QIODevice *d)
ok = ok && innerReadStructure(d); ok = ok && innerReadStructure(d);
} }
if (ok) { if (ok) {
ok = d->seek(nextChunkPos()); auto pos = _dataPos + _size;
if (auto align = pos % alignBytes())
pos += alignBytes() - align;
ok = pos < d->pos() ? false : d->seek(pos);
} }
return ok; return ok;
} }
@ -135,21 +127,18 @@ bool IFFChunk::readInfo(QIODevice *d)
QByteArray IFFChunk::readRawData(QIODevice *d, qint64 relPos, qint64 size) const QByteArray IFFChunk::readRawData(QIODevice *d, qint64 relPos, qint64 size) const
{ {
if (!seek(d, relPos)) { if (!seek(d, relPos))
return{}; return{};
} if (size == -1)
if (size == -1) {
size = _size; size = _size;
}
auto read = std::min(size, _size - relPos); auto read = std::min(size, _size - relPos);
return d->read(read); return d->read(read);
} }
bool IFFChunk::seek(QIODevice *d, qint64 relPos) const bool IFFChunk::seek(QIODevice *d, qint64 relPos) const
{ {
if (d == nullptr) { if (d == nullptr)
return false; return false;
}
return d->seek(_dataPos + relPos); return d->seek(_dataPos + relPos);
} }
@ -158,19 +147,6 @@ bool IFFChunk::innerReadStructure(QIODevice *)
return true; return true;
} }
void IFFChunk::setAlignBytes(qint32 bytes)
{
_align = bytes;
}
qint64 IFFChunk::nextChunkPos() const
{
auto pos = _dataPos + _size;
if (auto align = pos % alignBytes())
pos += alignBytes() - align;
return pos;
}
IFFChunk::ChunkList IFFChunk::search(const QByteArray &cid, const QSharedPointer<IFFChunk> &chunk) IFFChunk::ChunkList IFFChunk::search(const QByteArray &cid, const QSharedPointer<IFFChunk> &chunk)
{ {
return search(cid, ChunkList() << chunk); return search(cid, ChunkList() << chunk);
@ -189,9 +165,8 @@ IFFChunk::ChunkList IFFChunk::search(const QByteArray &cid, const ChunkList &chu
bool IFFChunk::cacheData(QIODevice *d) bool IFFChunk::cacheData(QIODevice *d)
{ {
if (bytes() > 8 * 1024 * 1024) { if (bytes() > 8 * 1024 * 1024)
return false; return false;
}
_data = readRawData(d); _data = readRawData(d);
return _data.size() == _size; return _data.size() == _size;
} }
@ -211,7 +186,7 @@ void IFFChunk::setRecursionCounter(qint32 cnt)
_recursionCnt = cnt; _recursionCnt = cnt;
} }
IFFChunk::ChunkList IFFChunk::innerFromDevice(QIODevice *d, bool *ok, IFFChunk *parent) IFFChunk::ChunkList IFFChunk::innerFromDevice(QIODevice *d, bool *ok, qint32 alignBytes, qint32 recursionCnt)
{ {
auto tmp = false; auto tmp = false;
if (ok == nullptr) { if (ok == nullptr) {
@ -223,63 +198,42 @@ IFFChunk::ChunkList IFFChunk::innerFromDevice(QIODevice *d, bool *ok, IFFChunk *
return {}; return {};
} }
auto alignBytes = qint32(2);
auto recursionCnt = qint32();
auto nextChunkPos = qint64();
if (parent) {
alignBytes = parent->alignBytes();
recursionCnt = parent->recursionCounter();
nextChunkPos = parent->nextChunkPos();
}
if (recursionCnt > RECURSION_PROTECTION) { if (recursionCnt > RECURSION_PROTECTION) {
return {}; return {};
} }
IFFChunk::ChunkList list; IFFChunk::ChunkList list;
for (; !d->atEnd() && (nextChunkPos == 0 || d->pos() < nextChunkPos);) { for (; !d->atEnd();) {
auto cid = d->peek(4); auto cid = d->peek(4);
QSharedPointer<IFFChunk> chunk; QSharedPointer<IFFChunk> chunk;
if (cid == ANNO_CHUNK) { if (cid == FORM_CHUNK) {
chunk = QSharedPointer<IFFChunk>(new ANNOChunk()); chunk = QSharedPointer<IFFChunk>(new FORMChunk());
} else if (cid == AUTH_CHUNK) {
chunk = QSharedPointer<IFFChunk>(new AUTHChunk());
} else if (cid == BMHD_CHUNK) {
chunk = QSharedPointer<IFFChunk>(new BMHDChunk());
} else if (cid == BODY_CHUNK) {
chunk = QSharedPointer<IFFChunk>(new BODYChunk());
} else if (cid == CAMG_CHUNK) { } else if (cid == CAMG_CHUNK) {
chunk = QSharedPointer<IFFChunk>(new CAMGChunk()); chunk = QSharedPointer<IFFChunk>(new CAMGChunk());
} else if (cid == CMAP_CHUNK) { } else if (cid == CMAP_CHUNK) {
chunk = QSharedPointer<IFFChunk>(new CMAPChunk()); chunk = QSharedPointer<IFFChunk>(new CMAPChunk());
} else if (cid == COPY_CHUNK) { } else if (cid == BMHD_CHUNK) {
chunk = QSharedPointer<IFFChunk>(new COPYChunk()); chunk = QSharedPointer<IFFChunk>(new BMHDChunk());
} else if (cid == DATE_CHUNK) { } else if (cid == BODY_CHUNK) {
chunk = QSharedPointer<IFFChunk>(new DATEChunk()); chunk = QSharedPointer<IFFChunk>(new BODYChunk());
} else if (cid == DPI__CHUNK) { } else if (cid == DPI__CHUNK) {
chunk = QSharedPointer<IFFChunk>(new DPIChunk()); chunk = QSharedPointer<IFFChunk>(new DPIChunk());
} else if (cid == EXIF_CHUNK) {
chunk = QSharedPointer<IFFChunk>(new EXIFChunk());
} else if (cid == FOR4_CHUNK) { } else if (cid == FOR4_CHUNK) {
chunk = QSharedPointer<IFFChunk>(new FOR4Chunk()); chunk = QSharedPointer<IFFChunk>(new FOR4Chunk());
} else if (cid == FORM_CHUNK) { } else if (cid == TBHD_CHUNK) {
chunk = QSharedPointer<IFFChunk>(new FORMChunk()); chunk = QSharedPointer<IFFChunk>(new TBHDChunk());
} else if (cid == RGBA_CHUNK) {
chunk = QSharedPointer<IFFChunk>(new RGBAChunk());
} else if (cid == AUTH_CHUNK) {
chunk = QSharedPointer<IFFChunk>(new AUTHChunk());
} else if (cid == DATE_CHUNK) {
chunk = QSharedPointer<IFFChunk>(new DATEChunk());
} else if (cid == FVER_CHUNK) { } else if (cid == FVER_CHUNK) {
chunk = QSharedPointer<IFFChunk>(new FVERChunk()); chunk = QSharedPointer<IFFChunk>(new FVERChunk());
} else if (cid == HIST_CHUNK) { } else if (cid == HIST_CHUNK) {
chunk = QSharedPointer<IFFChunk>(new HISTChunk()); chunk = QSharedPointer<IFFChunk>(new HISTChunk());
} else if (cid == ICCP_CHUNK) {
chunk = QSharedPointer<IFFChunk>(new ICCPChunk());
} else if (cid == NAME_CHUNK) {
chunk = QSharedPointer<IFFChunk>(new NAMEChunk());
} else if (cid == RGBA_CHUNK) {
chunk = QSharedPointer<IFFChunk>(new RGBAChunk());
} else if (cid == TBHD_CHUNK) {
chunk = QSharedPointer<IFFChunk>(new TBHDChunk());
} else if (cid == VERS_CHUNK) { } else if (cid == VERS_CHUNK) {
chunk = QSharedPointer<IFFChunk>(new VERSChunk()); chunk = QSharedPointer<IFFChunk>(new VERSChunk());
} else if (cid == XMP0_CHUNK) {
chunk = QSharedPointer<IFFChunk>(new XMP0Chunk());
} else { // unknown chunk } else { // unknown chunk
chunk = QSharedPointer<IFFChunk>(new IFFChunk()); chunk = QSharedPointer<IFFChunk>(new IFFChunk());
qInfo() << "IFFChunk::innerFromDevice: unkwnown chunk" << cid; qInfo() << "IFFChunk::innerFromDevice: unkwnown chunk" << cid;
@ -302,12 +256,6 @@ IFFChunk::ChunkList IFFChunk::innerFromDevice(QIODevice *d, bool *ok, IFFChunk *
return {}; return {};
} }
// skip any non-IFF data at the end of the file.
// NOTE: there should be no more chunks after the first (root)
if (nextChunkPos == 0) {
nextChunkPos = chunk->nextChunkPos();
}
list << chunk; list << chunk;
} }
@ -317,7 +265,7 @@ IFFChunk::ChunkList IFFChunk::innerFromDevice(QIODevice *d, bool *ok, IFFChunk *
IFFChunk::ChunkList IFFChunk::fromDevice(QIODevice *d, bool *ok) IFFChunk::ChunkList IFFChunk::fromDevice(QIODevice *d, bool *ok)
{ {
return innerFromDevice(d, ok, nullptr); return innerFromDevice(d, ok, 2, 0);
} }
@ -392,12 +340,12 @@ quint8 BMHDChunk::bitplanes() const
return quint8(data().at(8)); return quint8(data().at(8));
} }
BMHDChunk::Masking BMHDChunk::masking() const quint8 BMHDChunk::masking() const
{ {
if (!isValid()) { if (!isValid()) {
return BMHDChunk::Masking::None; return 0;
} }
return BMHDChunk::Masking(quint8(data().at(9))); return quint8(data().at(9));
} }
BMHDChunk::Compression BMHDChunk::compression() const BMHDChunk::Compression BMHDChunk::compression() const
@ -409,6 +357,14 @@ BMHDChunk::Compression BMHDChunk::compression() const
} }
quint8 BMHDChunk::padding() const
{
if (!isValid()) {
return 0;
}
return quint8(data().at(11));
}
qint16 BMHDChunk::transparency() const qint16 BMHDChunk::transparency() const
{ {
if (!isValid()) { if (!isValid()) {
@ -591,13 +547,13 @@ bool BODYChunk::isValid() const
return chunkId() == BODYChunk::defaultChunkId(); return chunkId() == BODYChunk::defaultChunkId();
} }
QByteArray BODYChunk::strideRead(QIODevice *d, const BMHDChunk *header, const CAMGChunk *camg, const CMAPChunk *cmap, bool isPbm) const QByteArray BODYChunk::strideRead(QIODevice *d, const BMHDChunk *header, const CAMGChunk *camg, const CMAPChunk *cmap) const
{ {
if (!isValid() || header == nullptr) { if (!isValid() || header == nullptr) {
return {}; return {};
} }
auto readSize = strideSize(header, isPbm); auto readSize = header->rowLen() * header->bitplanes();
for(;!d->atEnd() && _readBuffer.size() < readSize;) { for(;!d->atEnd() && _readBuffer.size() < readSize;) {
QByteArray buf(readSize, char()); QByteArray buf(readSize, char());
qint64 rr = -1; qint64 rr = -1;
@ -606,7 +562,7 @@ QByteArray BODYChunk::strideRead(QIODevice *d, const BMHDChunk *header, const CA
// not accurate: the RLE -128 code is not a noop. // not accurate: the RLE -128 code is not a noop.
rr = packbitsDecompress(d, buf.data(), buf.size(), true); rr = packbitsDecompress(d, buf.data(), buf.size(), true);
} else if (header->compression() == BMHDChunk::Compression::Uncompressed) { } else if (header->compression() == BMHDChunk::Compression::Uncompressed) {
rr = d->read(buf.data(), buf.size()); // never seen rr = d->read(buf.data(), buf.size());
} }
if (rr != readSize) if (rr != readSize)
return {}; return {};
@ -615,7 +571,7 @@ QByteArray BODYChunk::strideRead(QIODevice *d, const BMHDChunk *header, const CA
auto planes = _readBuffer.left(readSize); auto planes = _readBuffer.left(readSize);
_readBuffer.remove(0, readSize); _readBuffer.remove(0, readSize);
return deinterleave(planes, header, camg, cmap, isPbm); return BODYChunk::deinterleave(planes, header, camg, cmap);
} }
bool BODYChunk::resetStrideRead(QIODevice *d) const bool BODYChunk::resetStrideRead(QIODevice *d) const
@ -624,31 +580,14 @@ bool BODYChunk::resetStrideRead(QIODevice *d) const
return seek(d); return seek(d);
} }
quint32 BODYChunk::strideSize(const BMHDChunk *header, bool isPbm) const QByteArray BODYChunk::deinterleave(const QByteArray &planes, const BMHDChunk *header, const CAMGChunk *camg, const CMAPChunk *cmap)
{ {
auto rs = header->rowLen() * header->bitplanes(); auto rowLen = qint32(header->rowLen());
if (!isPbm) { auto bitplanes = header->bitplanes();
return rs; if (planes.size() != rowLen * bitplanes) {
}
// I found two versions of PBM: one uses ILBM calculation, the other uses width-based.
// As it is a proprietary extension, one of them was probably generated incorrectly.
if (header->compression() == BMHDChunk::Compression::Uncompressed) {
if (rs * header->height() != bytes())
rs = header->width() * header->bitplanes() / 8;
}
return rs;
}
QByteArray BODYChunk::deinterleave(const QByteArray &planes, const BMHDChunk *header, const CAMGChunk *camg, const CMAPChunk *cmap, bool isPbm) const
{
if (planes.size() != strideSize(header, isPbm)) {
return {}; return {};
} }
auto rowLen = qint32(header->rowLen());
auto bitplanes = header->bitplanes();
auto modeId = CAMGChunk::ModeIds(); auto modeId = CAMGChunk::ModeIds();
if (camg) { if (camg) {
modeId = camg->modeId(); modeId = camg->modeId();
@ -670,10 +609,7 @@ QByteArray BODYChunk::deinterleave(const QByteArray &planes, const BMHDChunk *he
case 6: case 6:
case 7: case 7:
case 8: case 8:
if (isPbm && bitplanes == 8) { if (modeId == CAMGChunk::ModeId::Ham && cmap && bitplanes == 6) {
// The data are contiguous.
ba = planes;
} else if ((modeId & CAMGChunk::ModeId::Ham) && (cmap) && (bitplanes >= 5 && bitplanes <= 8)) {
// From A Quick Introduction to IFF.txt: // From A Quick Introduction to IFF.txt:
// //
// Amiga HAM (Hold and Modify) mode lets the Amiga display all 4096 RGB values. // Amiga HAM (Hold and Modify) mode lets the Amiga display all 4096 RGB values.
@ -695,7 +631,6 @@ QByteArray BODYChunk::deinterleave(const QByteArray &planes, const BMHDChunk *he
// 11 - hold previous. replacing Green component with bits from planes 0-3 // 11 - hold previous. replacing Green component with bits from planes 0-3
ba = QByteArray(rowLen * 8 * 3, char()); ba = QByteArray(rowLen * 8 * 3, char());
auto pal = cmap->palette(); auto pal = cmap->palette();
auto max = (1 << (bitplanes - 2)) - 1;
quint8 prev[3] = {}; quint8 prev[3] = {};
for (qint32 i = 0, cnt = 0; i < rowLen; ++i) { for (qint32 i = 0, cnt = 0; i < rowLen; ++i) {
for (qint32 j = 0; j < 8; ++j, ++cnt) { for (qint32 j = 0; j < 8; ++j, ++cnt) {
@ -703,20 +638,21 @@ QByteArray BODYChunk::deinterleave(const QByteArray &planes, const BMHDChunk *he
for (qint32 k = 0, msk = (1 << (7 - j)); k < bitplanes; ++k) { for (qint32 k = 0, msk = (1 << (7 - j)); k < bitplanes; ++k) {
if ((planes.at(k * rowLen + i) & msk) == 0) if ((planes.at(k * rowLen + i) & msk) == 0)
continue; continue;
if (k < bitplanes - 2) if (k < 4) {
idx |= 1 << k; idx |= 1 << k;
else } else {
ctl |= 1 << (bitplanes - k - 1); ctl |= 1 << (bitplanes - k - 1);
} }
}
switch (ctl) { switch (ctl) {
case 1: // red case 1: // red
prev[0] = idx * 255 / max; prev[0] = idx | (idx << 4);
break; break;
case 2: // blue case 2: // blue
prev[2] = idx * 255 / max; prev[2] = idx | (idx << 4);
break; break;
case 3: // green case 3: // green
prev[1] = idx * 255 / max; prev[1] = idx | (idx << 4);
break; break;
default: default:
if (idx < pal.size()) { if (idx < pal.size()) {
@ -734,38 +670,7 @@ QByteArray BODYChunk::deinterleave(const QByteArray &planes, const BMHDChunk *he
ba[cnt3 + 2] = char(prev[2]); ba[cnt3 + 2] = char(prev[2]);
} }
} }
} else if ((modeId & CAMGChunk::ModeId::HalfBrite) && (cmap)) { } else if (modeId == CAMGChunk::ModeIds()) {
// In HALFBRITE mode, the Amiga interprets the bit in the
// last plane as HALFBRITE modification. The bits in the other planes are
// treated as normal color register numbers (RGB values for each color register
// is specified in the CMAP chunk). If the bit in the last plane is set (1),
// then that pixel is displayed at half brightness. This can provide up to 64
// absolute colors.
ba = QByteArray(rowLen * 8 * 3, char());
auto pal = cmap->palette();
for (qint32 i = 0, cnt = 0; i < rowLen; ++i) {
for (qint32 j = 0; j < 8; ++j, ++cnt) {
quint8 idx = 0, ctl = 0;
for (qint32 k = 0, msk = (1 << (7 - j)); k < bitplanes; ++k) {
if ((planes.at(k * rowLen + i) & msk) == 0)
continue;
if (k < bitplanes - 1)
idx |= 1 << k;
else
ctl = 1;
}
if (idx < pal.size()) {
auto cnt3 = cnt * 3;
auto div = ctl ? 2 : 1;
ba[cnt3] = qRed(pal.at(idx)) / div;
ba[cnt3 + 1] = qGreen(pal.at(idx)) / div;
ba[cnt3 + 2] = qBlue(pal.at(idx)) / div;
} else {
qWarning() << "BODYChunk::deinterleave: palette index" << idx << "is out of range";
}
}
}
} else {
// From A Quick Introduction to IFF.txt: // From A Quick Introduction to IFF.txt:
// //
// If the ILBM is not HAM or HALFBRITE, then after parsing and uncompacting if // If the ILBM is not HAM or HALFBRITE, then after parsing and uncompacting if
@ -804,12 +709,6 @@ QByteArray BODYChunk::deinterleave(const QByteArray &planes, const BMHDChunk *he
break; break;
case 24: // rgb case 24: // rgb
case 32: // rgba
if (isPbm) {
// TODO: no testcase found
break;
}
// From A Quick Introduction to IFF.txt: // From A Quick Introduction to IFF.txt:
// //
// If a deep ILBM (like 12 or 24 planes), there should be no CMAP // If a deep ILBM (like 12 or 24 planes), there should be no CMAP
@ -876,10 +775,8 @@ bool FORMChunk::innerReadStructure(QIODevice *d)
} }
_type = d->read(4); _type = d->read(4);
auto ok = true; auto ok = true;
if (_type == ILBM_FORM_TYPE) { if (_type == QByteArray("ILBM")) {
setChunks(IFFChunk::innerFromDevice(d, &ok, this)); setChunks(IFFChunk::innerFromDevice(d, &ok, alignBytes(), recursionCounter()));
} else if (_type == PBM__FORM_TYPE) {
setChunks(IFFChunk::innerFromDevice(d, &ok, this));
} }
return ok; return ok;
} }
@ -908,29 +805,26 @@ QImage::Format FORMChunk::format() const
// assume HAM and you'll probably be right. // assume HAM and you'll probably be right.
modeId = CAMGChunk::ModeIds(CAMGChunk::ModeId::Ham); modeId = CAMGChunk::ModeIds(CAMGChunk::ModeId::Ham);
} }
if (h->bitplanes() == 24) { if (h->bitplanes() == 24) {
return QImage::Format_RGB888; return QImage::Format_RGB888;
} }
if (h->bitplanes() == 32) {
return QImage::Format_RGBA8888;
}
if (h->bitplanes() >= 2 && h->bitplanes() <= 8) { if (h->bitplanes() >= 2 && h->bitplanes() <= 8) {
if (!IFFChunk::search(SHAM_CHUNK, chunks()).isEmpty() || !IFFChunk::search(CTBL_CHUNK, chunks()).isEmpty()) { // Currently supported modes: HAM6 and No HAM/HALFBRITE.
// Images with the SHAM or CTBL chunk do not load correctly: it seems they contains if (modeId != CAMGChunk::ModeIds() && (modeId != CAMGChunk::ModeId::Ham || h->bitplanes() != 6))
// a color table but I didn't find any specs.
return QImage::Format_Invalid; return QImage::Format_Invalid;
}
if (modeId & (CAMGChunk::ModeId::Ham | CAMGChunk::ModeId::HalfBrite)) { if (modeId & CAMGChunk::ModeId::Ham) {
if (IFFChunk::search(SHAM_CHUNK, chunks()).isEmpty())
return QImage::Format_RGB888; return QImage::Format_RGB888;
} else // Images with the SHAM chunk do not load correctly.
return QImage::Format_Invalid;
if (!cmaps.isEmpty()) { } else if (!cmaps.isEmpty()) {
return QImage::Format_Indexed8; return QImage::Format_Indexed8;
} } else {
return QImage::Format_Grayscale8; return QImage::Format_Grayscale8;
} }
}
if (h->bitplanes() == 1) { if (h->bitplanes() == 1) {
return QImage::Format_Mono; return QImage::Format_Mono;
} }
@ -984,10 +878,10 @@ bool FOR4Chunk::innerReadStructure(QIODevice *d)
} }
_type = d->read(4); _type = d->read(4);
auto ok = true; auto ok = true;
if (_type == CIMG_FOR4_TYPE) { if (_type == QByteArray("CIMG")) {
setChunks(IFFChunk::innerFromDevice(d, &ok, this)); setChunks(IFFChunk::innerFromDevice(d, &ok, alignBytes(), recursionCounter()));
} else if (_type == TBMP_FOR4_TYPE) { } else if (_type == QByteArray("TBMP")) {
setChunks(IFFChunk::innerFromDevice(d, &ok, this)); setChunks(IFFChunk::innerFromDevice(d, &ok, alignBytes(), recursionCounter()));
} }
return ok; return ok;
} }
@ -1182,20 +1076,16 @@ bool RGBAChunk::isTileCompressed(const TBHDChunk *header) const
QPoint RGBAChunk::pos() const QPoint RGBAChunk::pos() const
{ {
return _posPx; return _pos;
} }
QSize RGBAChunk::size() const QSize RGBAChunk::size() const
{ {
return _sizePx; return _size;
} }
// Maya version of IFF uses a slightly different algorithm for RLE compression. // Maya version of IFF uses a slightly different algorithm for RLE compression.
// To understand how it works I saved images with regular patterns from Photoshop qint64 rleMayaDecompress(QIODevice *input, char *output, qint64 olen)
// and then checked the data. It is basically the same as packbits except for how
// the length is extracted: I don't know if it's a standard variant or not, so
// I'm keeping it private.
inline qint64 rleMayaDecompress(QIODevice *input, char *output, qint64 olen)
{ {
qint64 j = 0; qint64 j = 0;
for (qint64 rr = 0, available = olen; j < olen; available = olen - j) { for (qint64 rr = 0, available = olen; j < olen; available = olen - j) {
@ -1206,7 +1096,7 @@ inline qint64 rleMayaDecompress(QIODevice *input, char *output, qint64 olen)
if (input->peek(&n, 1) != 1) { // end of data (or error) if (input->peek(&n, 1) != 1) { // end of data (or error)
break; break;
} }
rr = qint64(n & 0x7F) + 1; rr = qint64(n & 0x7f) + 1;
if (rr > available) if (rr > available)
break; break;
} }
@ -1216,7 +1106,7 @@ inline qint64 rleMayaDecompress(QIODevice *input, char *output, qint64 olen)
break; break;
} }
rr = qint64(n & 0x7F) + 1; rr = qint64(n & 0x7f) + 1;
if ((n & 0x80) == 0) { if ((n & 0x80) == 0) {
auto read = input->read(output + j, rr); auto read = input->read(output + j, rr);
if (rr != read) { if (rr != read) {
@ -1242,27 +1132,21 @@ QByteArray RGBAChunk::readStride(QIODevice *d, const TBHDChunk *header) const
return {}; return {};
} }
// It seems that tiles are compressed independently only if there is space savings. // detect if the tile is compressed (8 is the size of 4 uint16 before the tile data).
// The compression method specified in the header is only to indicate the type of auto compressed = isTileCompressed(header);
// compression if used.
if (!isTileCompressed(header)) {
// when not compressed, the line contains all channels
readSize *= header->bpc() * header->channels();
QByteArray buf(readSize, char());
auto rr = d->read(buf.data(), buf.size());
if (rr != buf.size()) {
return {};
}
return buf;
}
// compressed
for(;!d->atEnd() && _readBuffer.size() < readSize;) { for(;!d->atEnd() && _readBuffer.size() < readSize;) {
QByteArray buf(readSize * size().height(), char()); QByteArray buf(readSize * size().height(), char());
qint64 rr = -1; qint64 rr = -1;
if (compressed) {
// It seems that tiles are compressed independently only if there is space savings.
// The compression method specified in the header is only to indicate the type of
// compression if used.
if (header->compression() == TBHDChunk::Compression::Rle) { if (header->compression() == TBHDChunk::Compression::Rle) {
rr = rleMayaDecompress(d, buf.data(), buf.size()); rr = rleMayaDecompress(d, buf.data(), buf.size());
} }
} else {
rr = d->read(buf.data(), buf.size());
}
if (rr != buf.size()) { if (rr != buf.size()) {
return {}; return {};
} }
@ -1275,18 +1159,6 @@ QByteArray RGBAChunk::readStride(QIODevice *d, const TBHDChunk *header) const
return buff; return buff;
} }
/*!
* \brief compressedTile
*
* The compressed tile contains compressed data per channel.
*
* If 16 bit, high and low bytes are treated separately (so I have
* channels * 2 compressed data blocks). First the high ones, then the low
* ones (or vice versa): for the reconstruction I went by trial and error :)
* \param d The device
* \param header The header.
* \return The tile as Qt image.
*/
QImage RGBAChunk::compressedTile(QIODevice *d, const TBHDChunk *header) const QImage RGBAChunk::compressedTile(QIODevice *d, const TBHDChunk *header) const
{ {
QImage img(size(), header->format()); QImage img(size(), header->format());
@ -1312,7 +1184,7 @@ QImage RGBAChunk::compressedTile(QIODevice *d, const TBHDChunk *header) const
} }
for (auto c = 0, cc = header->channels() * header->bpc(); c < cc; ++c) { for (auto c = 0, cc = header->channels() * header->bpc(); c < cc; ++c) {
#if Q_BYTE_ORDER == Q_BIG_ENDIAN #if Q_BYTE_ORDER == Q_BIG_ENDIAN
auto c_bcp = c / cs; // Not tried auto c_bcp = c / cs;
#else #else
auto c_bcp = 1 - c / cs; auto c_bcp = 1 - c / cs;
#endif #endif
@ -1333,15 +1205,6 @@ QImage RGBAChunk::compressedTile(QIODevice *d, const TBHDChunk *header) const
return img; return img;
} }
/*!
* \brief RGBAChunk::uncompressedTile
*
* The uncompressed tile scanline contains the data in
* B0 G0 R0 A0 B1 G1 R1 A1... Bn Gn Rn An format.
* \param d The device
* \param header The header.
* \return The tile as Qt image.
*/
QImage RGBAChunk::uncompressedTile(QIODevice *d, const TBHDChunk *header) const QImage RGBAChunk::uncompressedTile(QIODevice *d, const TBHDChunk *header) const
{ {
QImage img(size(), header->format()); QImage img(size(), header->format());
@ -1349,8 +1212,10 @@ QImage RGBAChunk::uncompressedTile(QIODevice *d, const TBHDChunk *header) const
if (bpc == 1) { if (bpc == 1) {
auto cs = header->channels(); auto cs = header->channels();
auto lineSize = img.width() * bpc * cs;
for (auto y = 0, h = img.height(); y < h; ++y) { for (auto y = 0, h = img.height(); y < h; ++y) {
auto ba = readStride(d, header); auto ba = d->read(lineSize);
if (ba.isEmpty()) { if (ba.isEmpty()) {
return {}; return {};
} }
@ -1364,12 +1229,13 @@ QImage RGBAChunk::uncompressedTile(QIODevice *d, const TBHDChunk *header) const
} }
} else if (bpc == 2) { } else if (bpc == 2) {
auto cs = header->channels(); auto cs = header->channels();
auto lineSize = img.width() * bpc * cs;
if (cs < 4) { // alpha on 64-bit images must be 0xFF if (cs < 4) { // alpha on 64-bit images must be 0xFF
std::memset(img.bits(), 0xFF, img.sizeInBytes()); std::memset(img.bits(), 0xFF, img.sizeInBytes());
} }
for (auto y = 0, h = img.height(); y < h; ++y) { for (auto y = 0, h = img.height(); y < h; ++y) {
auto ba = readStride(d, header); auto ba = d->read(lineSize);
if (ba.isEmpty()) { if (ba.isEmpty()) {
return {}; return {};
} }
@ -1380,7 +1246,7 @@ QImage RGBAChunk::uncompressedTile(QIODevice *d, const TBHDChunk *header) const
auto xcs = x * cs; auto xcs = x * cs;
auto xcs4 = x * 4; auto xcs4 = x * 4;
#if Q_BYTE_ORDER == Q_BIG_ENDIAN #if Q_BYTE_ORDER == Q_BIG_ENDIAN
scl[xcs4 + cs - c - 1] = src[xcs + c]; // Not tried scl[xcs4 + cs - c - 1] = src[xcs + c];
#else #else
scl[xcs4 + cs - c - 1] = (src[xcs + c] >> 8) | (src[xcs + c] << 8); scl[xcs4 + cs - c - 1] = (src[xcs + c] >> 8) | (src[xcs + c] << 8);
#endif #endif
@ -1423,42 +1289,12 @@ bool RGBAChunk::innerReadStructure(QIODevice *d)
return false; return false;
} }
_posPx = QPoint(x0, y0); _pos = QPoint(x0, y0);
_sizePx = QSize(qint32(x1) - x0 + 1, qint32(y1) - y0 + 1); _size = QSize(qint32(x1) - x0 + 1, qint32(y1) - y0 + 1);
return true; return true;
} }
/* ******************
* *** ANNO Chunk ***
* ****************** */
ANNOChunk::~ANNOChunk()
{
}
ANNOChunk::ANNOChunk()
{
}
bool ANNOChunk::isValid() const
{
return chunkId() == AUTHChunk::defaultChunkId();
}
QString ANNOChunk::value() const
{
return QString::fromLatin1(data()).replace(QStringLiteral("\0"), QStringLiteral(" ")).trimmed();
}
bool ANNOChunk::innerReadStructure(QIODevice *d)
{
return cacheData(d);
}
/* ****************** /* ******************
* *** AUTH Chunk *** * *** AUTH Chunk ***
* ****************** */ * ****************** */
@ -1480,7 +1316,7 @@ bool AUTHChunk::isValid() const
QString AUTHChunk::value() const QString AUTHChunk::value() const
{ {
return QString::fromLatin1(data()).replace(QStringLiteral("\0"), QStringLiteral(" ")).trimmed(); return QString::fromLatin1(data());
} }
bool AUTHChunk::innerReadStructure(QIODevice *d) bool AUTHChunk::innerReadStructure(QIODevice *d)
@ -1488,37 +1324,6 @@ bool AUTHChunk::innerReadStructure(QIODevice *d)
return cacheData(d); return cacheData(d);
} }
/* ******************
* *** COPY Chunk ***
* ****************** */
COPYChunk::~COPYChunk()
{
}
COPYChunk::COPYChunk()
{
}
bool COPYChunk::isValid() const
{
return chunkId() == COPYChunk::defaultChunkId();
}
QString COPYChunk::value() const
{
return QString::fromLatin1(data()).replace(QStringLiteral("\0"), QStringLiteral(" ")).trimmed();
}
bool COPYChunk::innerReadStructure(QIODevice *d)
{
return cacheData(d);
}
/* ****************** /* ******************
* *** DATE Chunk *** * *** DATE Chunk ***
* ****************** */ * ****************** */
@ -1548,69 +1353,6 @@ bool DATEChunk::innerReadStructure(QIODevice *d)
return cacheData(d); return cacheData(d);
} }
/* ******************
* *** EXIF Chunk ***
* ****************** */
EXIFChunk::~EXIFChunk()
{
}
EXIFChunk::EXIFChunk()
{
}
bool EXIFChunk::isValid() const
{
if (!data().startsWith(QByteArray("Exif\0\0"))) {
return false;
}
return chunkId() == EXIFChunk::defaultChunkId();
}
MicroExif EXIFChunk::value() const
{
return MicroExif::fromByteArray(data().mid(6));
}
bool EXIFChunk::innerReadStructure(QIODevice *d)
{
return cacheData(d);
}
/* ******************
* *** ICCP Chunk ***
* ****************** */
ICCPChunk::~ICCPChunk()
{
}
ICCPChunk::ICCPChunk()
{
}
bool ICCPChunk::isValid() const
{
return chunkId() == ICCPChunk::defaultChunkId();
}
QColorSpace ICCPChunk::value() const
{
return QColorSpace::fromIccProfile(data());
}
bool ICCPChunk::innerReadStructure(QIODevice *d)
{
return cacheData(d);
}
/* ****************** /* ******************
* *** FVER Chunk *** * *** FVER Chunk ***
* ****************** */ * ****************** */
@ -1664,37 +1406,6 @@ bool HISTChunk::innerReadStructure(QIODevice *d)
return cacheData(d); return cacheData(d);
} }
/* ******************
* *** NAME Chunk ***
* ****************** */
NAMEChunk::~NAMEChunk()
{
}
NAMEChunk::NAMEChunk()
{
}
bool NAMEChunk::isValid() const
{
return chunkId() == NAMEChunk::defaultChunkId();
}
QString NAMEChunk::value() const
{
return QString::fromLatin1(data()).replace(QStringLiteral("\0"), QStringLiteral(" ")).trimmed();
}
bool NAMEChunk::innerReadStructure(QIODevice *d)
{
return cacheData(d);
}
/* ****************** /* ******************
* *** VERS Chunk *** * *** VERS Chunk ***
* ****************** */ * ****************** */
@ -1723,34 +1434,3 @@ bool VERSChunk::innerReadStructure(QIODevice *d)
{ {
return cacheData(d); return cacheData(d);
} }
/* ******************
* *** XMP0 Chunk ***
* ****************** */
XMP0Chunk::~XMP0Chunk()
{
}
XMP0Chunk::XMP0Chunk()
{
}
bool XMP0Chunk::isValid() const
{
return chunkId() == XMP0Chunk::defaultChunkId();
}
QString XMP0Chunk::value() const
{
return QString::fromUtf8(data()).replace(QStringLiteral("\0"), QStringLiteral(" ")).trimmed();
}
bool XMP0Chunk::innerReadStructure(QIODevice *d)
{
return cacheData(d);
}

View File

@ -9,7 +9,6 @@
* Format specifications: * Format specifications:
* - https://wiki.amigaos.net/wiki/IFF_FORM_and_Chunk_Registry * - https://wiki.amigaos.net/wiki/IFF_FORM_and_Chunk_Registry
* - https://www.fileformat.info/format/iff/egff.htm * - https://www.fileformat.info/format/iff/egff.htm
* - https://download.autodesk.com/us/maya/2010help/index.html (Developer resources -> File formats -> Maya IFF)
*/ */
#ifndef KIMG_CHUNKS_P_H #ifndef KIMG_CHUNKS_P_H
@ -23,8 +22,6 @@
#include <QSize> #include <QSize>
#include <QSharedPointer> #include <QSharedPointer>
#include "microexif_p.h"
// Main chunks (Standard) // Main chunks (Standard)
#define CAT__CHUNK QByteArray("CAT ") #define CAT__CHUNK QByteArray("CAT ")
#define FILL_CHUNK QByteArray(" ") #define FILL_CHUNK QByteArray(" ")
@ -33,15 +30,7 @@
#define PROP_CHUNK QByteArray("PROP") #define PROP_CHUNK QByteArray("PROP")
// Main chuncks (Maya) // Main chuncks (Maya)
#define CAT4_CHUNK QByteArray("CAT4") // 4 byte alignment
#define FOR4_CHUNK QByteArray("FOR4") #define FOR4_CHUNK QByteArray("FOR4")
#define LIS4_CHUNK QByteArray("LIS4")
#define PRO4_CHUNK QByteArray("PRO4")
#define CAT8_CHUNK QByteArray("CAT8") // 8 byte alignment (never seen)
#define FOR8_CHUNK QByteArray("FOR8")
#define LIS8_CHUNK QByteArray("LIS8")
#define PRO8_CHUNK QByteArray("PRO8")
// FORM ILBM IFF // FORM ILBM IFF
#define BMHD_CHUNK QByteArray("BMHD") #define BMHD_CHUNK QByteArray("BMHD")
@ -49,8 +38,6 @@
#define CAMG_CHUNK QByteArray("CAMG") #define CAMG_CHUNK QByteArray("CAMG")
#define CMAP_CHUNK QByteArray("CMAP") #define CMAP_CHUNK QByteArray("CMAP")
#define DPI__CHUNK QByteArray("DPI ") #define DPI__CHUNK QByteArray("DPI ")
#define CTBL_CHUNK QByteArray("CTBL") // undocumented
#define SHAM_CHUNK QByteArray("SHAM") // undocumented #define SHAM_CHUNK QByteArray("SHAM") // undocumented
// FOR4 CIMG IFF (Maya) // FOR4 CIMG IFF (Maya)
@ -58,22 +45,11 @@
#define TBHD_CHUNK QByteArray("TBHD") #define TBHD_CHUNK QByteArray("TBHD")
// FORx IFF (found on some IFF format specs) // FORx IFF (found on some IFF format specs)
#define ANNO_CHUNK QByteArray("ANNO")
#define AUTH_CHUNK QByteArray("AUTH") #define AUTH_CHUNK QByteArray("AUTH")
#define COPY_CHUNK QByteArray("(c) ")
#define DATE_CHUNK QByteArray("DATE") #define DATE_CHUNK QByteArray("DATE")
#define EXIF_CHUNK QByteArray("EXIF") // https://aminet.net/package/docs/misc/IFF-metadata
#define ICCP_CHUNK QByteArray("ICCP") // https://aminet.net/package/docs/misc/IFF-metadata
#define FVER_CHUNK QByteArray("FVER") #define FVER_CHUNK QByteArray("FVER")
#define HIST_CHUNK QByteArray("HIST") #define HIST_CHUNK QByteArray("HIST")
#define NAME_CHUNK QByteArray("NAME")
#define VERS_CHUNK QByteArray("VERS") #define VERS_CHUNK QByteArray("VERS")
#define XMP0_CHUNK QByteArray("XMP0") // https://aminet.net/package/docs/misc/IFF-metadata
#define ILBM_FORM_TYPE QByteArray("ILBM")
#define PBM__FORM_TYPE QByteArray("PBM ")
#define CIMG_FOR4_TYPE QByteArray("CIMG")
#define TBMP_FOR4_TYPE QByteArray("TBMP")
#define CHUNKID_DEFINE(a) static QByteArray defaultChunkId() { return a; } #define CHUNKID_DEFINE(a) static QByteArray defaultChunkId() { return a; }
@ -219,18 +195,15 @@ public:
template <class T> template <class T>
static QList<const T*> searchT(const IFFChunk *chunk) { static QList<const T*> searchT(const IFFChunk *chunk) {
QList<const T*> list; QList<const T*> list;
if (chunk == nullptr) { if (chunk == nullptr)
return list; return list;
}
auto cid = T::defaultChunkId(); auto cid = T::defaultChunkId();
if (chunk->chunkId() == cid) { if (chunk->chunkId() == cid)
if (auto c = dynamic_cast<const T*>(chunk)) if (auto c = dynamic_cast<const T*>(chunk))
list << c; list << c;
}
auto tmp = chunk->chunks(); auto tmp = chunk->chunks();
for (auto &&c : tmp) { for (auto &&c : tmp)
list << searchT<T>(c.data()); list << searchT<T>(c.data());
}
return list; return list;
} }
@ -243,9 +216,8 @@ public:
template <class T> template <class T>
static QList<const T*> searchT(const ChunkList& chunks) { static QList<const T*> searchT(const ChunkList& chunks) {
QList<const T*> list; QList<const T*> list;
for (auto &&chunk : chunks) { for (auto &&chunk : chunks)
list << searchT<T>(chunk.data()); list << searchT<T>(chunk.data());
}
return list; return list;
} }
@ -264,14 +236,11 @@ protected:
* \brief setAlignBytes * \brief setAlignBytes
* \param bytes * \param bytes
*/ */
void setAlignBytes(qint32 bytes); void setAlignBytes(qint32 bytes)
{
_align = bytes;
}
/*!
* \brief nextChunkPos
* Calculates the position of the next chunk. The position is already aligned.
* \return The position of the next chunk from the beginning of the stream.
*/
qint64 nextChunkPos() const;
/*! /*!
* \brief cacheData * \brief cacheData
@ -311,7 +280,7 @@ protected:
return qint32(ui32(c1, c2, c3, c4)); return qint32(ui32(c1, c2, c3, c4));
} }
static ChunkList innerFromDevice(QIODevice *d, bool *ok, IFFChunk *parent = nullptr); static ChunkList innerFromDevice(QIODevice *d, bool *ok, qint32 alignBytes, qint32 recursionCnt);
private: private:
char _chunkId[4]; char _chunkId[4];
@ -327,31 +296,20 @@ private:
ChunkList _chunks; ChunkList _chunks;
qint32 _recursionCnt; qint32 _recursionCnt;
}; };
/*! /*!
* \brief The BMHDChunk class * \brief The IffBMHD class
* Bitmap Header * Bitmap Header
*/ */
class BMHDChunk: public IFFChunk class BMHDChunk: public IFFChunk
{ {
public: public:
enum Compression { enum Compression {
Uncompressed = 0, /**< Image data are uncompressed. */ Uncompressed = 0,
Rle = 1 /**< Image data are RLE compressed. */ Rle = 1
};
enum Masking {
None = 0, /**< Designates an opaque rectangular image. */
HasMask = 1, /**< A mask plane is interleaved with the bitplanes in the BODY chunk. */
HasTransparentColor = 2, /**< Pixels in the source planes matching transparentColor
are to be considered “transparent”. (Actually, transparentColor
isnt a “color number” since its matched with numbers formed
by the source bitmap rather than the possibly deeper destination
bitmap. Note that having a transparent color implies ignoring
one of the color registers. */
Lasso = 3 /**< The reader may construct a mask by lassoing the image as in MacPaint.
To do this, put a 1 pixel border of transparentColor around the image rectangle.
Then do a seed fill from this border. Filled pixels are to be transparent. */
}; };
virtual ~BMHDChunk() override; virtual ~BMHDChunk() override;
@ -362,88 +320,34 @@ public:
virtual bool isValid() const override; virtual bool isValid() const override;
/*!
* \brief width
* \return Width of the bitmap in pixels.
*/
qint32 width() const; qint32 width() const;
/*!
* \brief height
* \return Height of the bitmap in pixels.
*/
qint32 height() const; qint32 height() const;
/*!
* \brief size
* \return Size in pixels.
*/
QSize size() const; QSize size() const;
/*!
* \brief left
* \return The left position of the image.
*/
qint32 left() const; qint32 left() const;
/*!
* \brief top
* \return The top position of the image.
*/
qint32 top() const; qint32 top() const;
/*!
* \brief bitplanes
* \return The number of bit planes.
*/
quint8 bitplanes() const; quint8 bitplanes() const;
/*! quint8 masking() const;
* \brief masking
* \return Kind of masking is to be used for this image.
*/
Masking masking() const;
/*!
* \brief compression
* \return The type of compression used.
*/
Compression compression() const; Compression compression() const;
/*! quint8 padding() const;
* \brief transparency
* \return Transparent "color number".
*/
qint16 transparency() const; qint16 transparency() const;
/*!
* \brief xAspectRatio
* \return X pixel aspect.
*/
quint8 xAspectRatio() const; quint8 xAspectRatio() const;
/*!
* \brief yAspectRatio
* \return Y pixel aspect.
*/
quint8 yAspectRatio() const; quint8 yAspectRatio() const;
/*!
* \brief pageWidth
* \return Source "page" width in pixels.
*/
quint16 pageWidth() const; quint16 pageWidth() const;
/*!
* \brief pageHeight
* \return Source "page" height in pixels.
*/
quint16 pageHeight() const; quint16 pageHeight() const;
/*!
* \brief rowLen
* \return The row len of a plane.
*/
quint32 rowLen() const; quint32 rowLen() const;
CHUNKID_DEFINE(BMHD_CHUNK) CHUNKID_DEFINE(BMHD_CHUNK)
@ -569,11 +473,10 @@ public:
* \param header The bitmap header. * \param header The bitmap header.
* \param camg The CAMG chunk (optional) * \param camg The CAMG chunk (optional)
* \param cmap The CMAP chunk (optional) * \param cmap The CMAP chunk (optional)
* \param isPbm Set to true if the formType() == "PBM "
* \return The scanline as requested for QImage. * \return The scanline as requested for QImage.
* \warning Call resetStrideRead() once before this one. * \warning Call resetStrideRead() once before this one.
*/ */
QByteArray strideRead(QIODevice *d, const BMHDChunk *header, const CAMGChunk *camg = nullptr, const CMAPChunk *cmap = nullptr, bool isPbm = false) const; QByteArray strideRead(QIODevice *d, const BMHDChunk *header, const CAMGChunk *camg = nullptr, const CMAPChunk *cmap = nullptr) const;
/*! /*!
* \brief resetStrideRead * \brief resetStrideRead
@ -587,14 +490,7 @@ public:
bool resetStrideRead(QIODevice *d) const; bool resetStrideRead(QIODevice *d) const;
private: private:
/*! static QByteArray deinterleave(const QByteArray &planes, const BMHDChunk *header, const CAMGChunk *camg = nullptr, const CMAPChunk *cmap = nullptr);
* \brief strideSize
* \param isPbm Set true if the image is PBM.
* \return The size of data to have to decode an image row.
*/
quint32 strideSize(const BMHDChunk *header, bool isPbm) const;
QByteArray deinterleave(const QByteArray &planes, const BMHDChunk *header, const CAMGChunk *camg = nullptr, const CMAPChunk *cmap = nullptr, bool isPbm = false) const;
mutable QByteArray _readBuffer; mutable QByteArray _readBuffer;
}; };
@ -667,11 +563,12 @@ class TBHDChunk : public IFFChunk
{ {
public: public:
enum Flag { enum Flag {
Rgb = 0x01, /**< RGB image */ Rgb = 0x01,
Alpha = 0x02, /**< Image contains alpha channel */ Alpha = 0x02,
ZBuffer = 0x04, /**< If the image has a z-buffer, it is described by ZBUF blocks with the same structure as the RGBA blocks, RLE encoded. */ ZBuffer = 0x04,
Black = 0x10,
RgbA = Rgb | Alpha /**< RGBA image */ RgbA = Rgb | Alpha
}; };
Q_DECLARE_FLAGS(Flags, Flag) Q_DECLARE_FLAGS(Flags, Flag)
@ -818,33 +715,13 @@ private:
QByteArray readStride(QIODevice *d, const TBHDChunk *header) const; QByteArray readStride(QIODevice *d, const TBHDChunk *header) const;
private: private:
QPoint _posPx; QPoint _pos;
QSize _sizePx; QSize _size;
mutable QByteArray _readBuffer; mutable QByteArray _readBuffer;
}; };
/*!
* \brief The ANNOChunk class
*/
class ANNOChunk : public IFFChunk
{
public:
virtual ~ANNOChunk() override;
ANNOChunk();
ANNOChunk(const ANNOChunk& other) = default;
ANNOChunk& operator =(const ANNOChunk& other) = default;
virtual bool isValid() const override;
QString value() const;
CHUNKID_DEFINE(ANNO_CHUNK)
protected:
virtual bool innerReadStructure(QIODevice *d) override;
};
/*! /*!
* \brief The AUTHChunk class * \brief The AUTHChunk class
@ -867,27 +744,6 @@ protected:
virtual bool innerReadStructure(QIODevice *d) override; virtual bool innerReadStructure(QIODevice *d) override;
}; };
/*!
* \brief The COPYChunk class
*/
class COPYChunk : public IFFChunk
{
public:
virtual ~COPYChunk() override;
COPYChunk();
COPYChunk(const COPYChunk& other) = default;
COPYChunk& operator =(const COPYChunk& other) = default;
virtual bool isValid() const override;
QString value() const;
CHUNKID_DEFINE(COPY_CHUNK)
protected:
virtual bool innerReadStructure(QIODevice *d) override;
};
/*! /*!
* \brief The DATEChunk class * \brief The DATEChunk class
*/ */
@ -909,49 +765,6 @@ protected:
virtual bool innerReadStructure(QIODevice *d) override; virtual bool innerReadStructure(QIODevice *d) override;
}; };
/*!
* \brief The EXIFChunk class
*/
class EXIFChunk : public IFFChunk
{
public:
virtual ~EXIFChunk() override;
EXIFChunk();
EXIFChunk(const EXIFChunk& other) = default;
EXIFChunk& operator =(const EXIFChunk& other) = default;
virtual bool isValid() const override;
MicroExif value() const;
CHUNKID_DEFINE(EXIF_CHUNK)
protected:
virtual bool innerReadStructure(QIODevice *d) override;
};
/*!
* \brief The ICCPChunk class
*/
class ICCPChunk : public IFFChunk
{
public:
virtual ~ICCPChunk() override;
ICCPChunk();
ICCPChunk(const ICCPChunk& other) = default;
ICCPChunk& operator =(const ICCPChunk& other) = default;
virtual bool isValid() const override;
QColorSpace value() const;
CHUNKID_DEFINE(ICCP_CHUNK)
protected:
virtual bool innerReadStructure(QIODevice *d) override;
};
/*! /*!
* \brief The FVERChunk class * \brief The FVERChunk class
* *
@ -996,27 +809,6 @@ protected:
}; };
/*!
* \brief The NAMEChunk class
*/
class NAMEChunk : public IFFChunk
{
public:
virtual ~NAMEChunk() override;
NAMEChunk();
NAMEChunk(const NAMEChunk& other) = default;
NAMEChunk& operator =(const NAMEChunk& other) = default;
virtual bool isValid() const override;
QString value() const;
CHUNKID_DEFINE(NAME_CHUNK)
protected:
virtual bool innerReadStructure(QIODevice *d) override;
};
/*! /*!
* \brief The VERSChunk class * \brief The VERSChunk class
*/ */
@ -1038,26 +830,4 @@ protected:
virtual bool innerReadStructure(QIODevice *d) override; virtual bool innerReadStructure(QIODevice *d) override;
}; };
/*!
* \brief The XMP0Chunk class
*/
class XMP0Chunk : public IFFChunk
{
public:
virtual ~XMP0Chunk() override;
XMP0Chunk();
XMP0Chunk(const XMP0Chunk& other) = default;
XMP0Chunk& operator =(const XMP0Chunk& other) = default;
virtual bool isValid() const override;
QString value() const;
CHUNKID_DEFINE(XMP0_CHUNK)
protected:
virtual bool innerReadStructure(QIODevice *d) override;
};
#endif // KIMG_CHUNKS_P_H #endif // KIMG_CHUNKS_P_H

View File

@ -107,12 +107,11 @@ bool IFFHandler::canRead(QIODevice *device)
void addMetadata(QImage& img, const IFFChunk *form) void addMetadata(QImage& img, const IFFChunk *form)
{ {
// standard IFF metadata auto dates = IFFChunk::searchT<DATEChunk>(form);
auto annos = IFFChunk::searchT<ANNOChunk>(form); if (!dates.isEmpty()) {
if (!annos.isEmpty()) { auto dt = dates.first()->value();
auto anno = annos.first()->value(); if (dt.isValid()) {
if (!anno.isEmpty()) { img.setText(QStringLiteral(META_KEY_CREATIONDATE), dt.toString(Qt::ISODate));
img.setText(QStringLiteral(META_KEY_DESCRIPTION), anno);
} }
} }
auto auths = IFFChunk::searchT<AUTHChunk>(form); auto auths = IFFChunk::searchT<AUTHChunk>(form);
@ -122,29 +121,6 @@ void addMetadata(QImage& img, const IFFChunk *form)
img.setText(QStringLiteral(META_KEY_AUTHOR), auth); img.setText(QStringLiteral(META_KEY_AUTHOR), auth);
} }
} }
auto dates = IFFChunk::searchT<DATEChunk>(form);
if (!dates.isEmpty()) {
auto dt = dates.first()->value();
if (dt.isValid()) {
img.setText(QStringLiteral(META_KEY_CREATIONDATE), dt.toString(Qt::ISODate));
}
}
auto copys = IFFChunk::searchT<COPYChunk>(form);
if (!copys.isEmpty()) {
auto cp = copys.first()->value();
if (!cp.isEmpty()) {
img.setText(QStringLiteral(META_KEY_COPYRIGHT), cp);
}
}
auto names = IFFChunk::searchT<NAMEChunk>(form);
if (!names.isEmpty()) {
auto name = names.first()->value();
if (!name.isEmpty()) {
img.setText(QStringLiteral(META_KEY_TITLE), name);
}
}
// software info
auto vers = IFFChunk::searchT<VERSChunk>(form); auto vers = IFFChunk::searchT<VERSChunk>(form);
if (!vers.isEmpty()) { if (!vers.isEmpty()) {
auto ver = vers.first()->value(); auto ver = vers.first()->value();
@ -152,40 +128,6 @@ void addMetadata(QImage& img, const IFFChunk *form)
img.setText(QStringLiteral(META_KEY_SOFTWARE), ver); img.setText(QStringLiteral(META_KEY_SOFTWARE), ver);
} }
} }
// SView5 metadata
auto exifs = IFFChunk::searchT<EXIFChunk>(form);
if (!exifs.isEmpty()) {
auto exif = exifs.first()->value();
exif.updateImageMetadata(img, false);
exif.updateImageResolution(img);
}
auto xmp0s = IFFChunk::searchT<XMP0Chunk>(form);
if (!xmp0s.isEmpty()) {
auto xmp = xmp0s.first()->value();
if (!xmp.isEmpty()) {
img.setText(QStringLiteral(META_KEY_XMP_ADOBE), xmp);
}
}
auto iccps = IFFChunk::searchT<ICCPChunk>(form);
if (!iccps.isEmpty()) {
auto cs = iccps.first()->value();
if (cs.isValid()) {
img.setColorSpace(cs);
}
}
// resolution -> leave after set of EXIF chunk
auto dpis = IFFChunk::searchT<DPIChunk>(form);
if (!dpis.isEmpty()) {
auto &&dpi = dpis.first();
if (dpi->isValid()) {
img.setDotsPerMeterX(dpi->dotsPerMeterX());
img.setDotsPerMeterY(dpi->dotsPerMeterY());
}
}
} }
bool IFFHandler::readStandardImage(QImage *image) bool IFFHandler::readStandardImage(QImage *image)
@ -211,6 +153,16 @@ bool IFFHandler::readStandardImage(QImage *image)
return false; return false;
} }
// resolution
auto dpis = IFFChunk::searchT<DPIChunk>(form);
if (!dpis.isEmpty()) {
auto &&dpi = dpis.first();
if (dpi->isValid()) {
img.setDotsPerMeterX(dpi->dotsPerMeterX());
img.setDotsPerMeterY(dpi->dotsPerMeterY());
}
}
// set color table // set color table
auto cmaps = IFFChunk::searchT<CMAPChunk>(form); auto cmaps = IFFChunk::searchT<CMAPChunk>(form);
if (img.format() == QImage::Format_Indexed8) { if (img.format() == QImage::Format_Indexed8) {
@ -238,10 +190,9 @@ bool IFFHandler::readStandardImage(QImage *image)
qCWarning(LOG_IFFPLUGIN) << "IFFHandler::readStandardImage() error while reading image data"; qCWarning(LOG_IFFPLUGIN) << "IFFHandler::readStandardImage() error while reading image data";
return false; return false;
} }
auto isPbm = form->formType() == PBM__FORM_TYPE;
for (auto y = 0, h = img.height(); y < h; ++y) { for (auto y = 0, h = img.height(); y < h; ++y) {
auto line = reinterpret_cast<char*>(img.scanLine(y)); auto line = reinterpret_cast<char*>(img.scanLine(y));
auto ba = body->strideRead(device(), header, camg, cmap, isPbm); auto ba = body->strideRead(device(), header, camg, cmap);
if (ba.isEmpty()) { if (ba.isEmpty()) {
qCWarning(LOG_IFFPLUGIN) << "IFFHandler::readStandardImage() error while reading image scanline"; qCWarning(LOG_IFFPLUGIN) << "IFFHandler::readStandardImage() error while reading image scanline";
return false; return false;
@ -250,7 +201,6 @@ bool IFFHandler::readStandardImage(QImage *image)
} }
} }
// set metadata (including image resolution)
addMetadata(img, form); addMetadata(img, form);
*image = img; *image = img;

View File

@ -1072,7 +1072,7 @@ QByteArray MicroExif::toByteArray(const QDataStream::ByteOrder &byteOrder, const
QByteArray ba; QByteArray ba;
{ {
QBuffer buf(&ba); QBuffer buf(&ba);
if (!write(&buf, byteOrder, version)) if (!write(&buf, byteOrder))
return {}; return {};
} }
return ba; return ba;

View File

@ -119,11 +119,10 @@ static bool IsSupported(const TgaHeader &head)
if (head.pixel_size != 8 && head.pixel_size != 16 && head.pixel_size != 24 && head.pixel_size != 32) { if (head.pixel_size != 8 && head.pixel_size != 16 && head.pixel_size != 24 && head.pixel_size != 32) {
return false; return false;
} }
// If the colormap_type field is set to zero, indicating that no color map exists, then colormap_index and colormap_length should be set to zero. // If the colormap_type field is set to zero, indicating that no color map exists, then colormap_size, colormap_index and colormap_length should be set to zero.
if (head.colormap_type == 0 && (head.colormap_index != 0 || head.colormap_length != 0)) { if (head.colormap_type == 0 && (head.colormap_size != 0 || head.colormap_index != 0 || head.colormap_length != 0)) {
return false; return false;
} }
return true; return true;
} }