Compare commits

..

5 Commits

Author SHA1 Message Date
0a9f9fe106 IFF: support for Ham8, HalfBride, Pbm and ILBM 32-bits modes 2025-07-15 07:38:19 +02:00
21b3b890ec tga: Be less strict about palette
There's images in the wild that claim to not have a palette
but still have one.
2025-07-09 13:17:20 +02:00
aef4bd7e1c Fix unused param in MicroExif::toByteArray() 2025-07-07 08:17:36 +02:00
ea1983a7d1 IFF: Fix possible stack overflow 2025-07-05 09:59:06 +00:00
775b53f1f6 Update version to 6.17.0 2025-07-04 18:28:45 +02:00
14 changed files with 811 additions and 147 deletions

View File

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

Binary file not shown.

View File

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

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

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

View File

@ -10,6 +10,8 @@
#include <QDebug>
#define RECURSION_PROTECTION 10
IFFChunk::~IFFChunk()
{
@ -20,6 +22,7 @@ IFFChunk::IFFChunk()
, _size{0}
, _align{2}
, _dataPos{0}
, _recursionCnt{0}
{
}
@ -34,8 +37,19 @@ bool IFFChunk::operator ==(const IFFChunk &other) const
bool IFFChunk::isValid() const
{
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) {
if (!((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c == ' ')))
if (c < ' ' || c > '~')
return false;
}
return true;
@ -49,12 +63,13 @@ qint32 IFFChunk::alignBytes() const
bool IFFChunk::readStructure(QIODevice *d)
{
auto ok = readInfo(d);
if (recursionCounter() > RECURSION_PROTECTION - 1) {
ok = ok && IFFChunk::innerReadStructure(d); // force default implementation (no more recursion)
} else {
ok = ok && innerReadStructure(d);
}
if (ok) {
auto pos = _dataPos + _size;
if (auto align = pos % alignBytes())
pos += alignBytes() - align;
ok = d->seek(pos);
ok = d->seek(nextChunkPos());
}
return ok;
}
@ -120,18 +135,21 @@ bool IFFChunk::readInfo(QIODevice *d)
QByteArray IFFChunk::readRawData(QIODevice *d, qint64 relPos, qint64 size) const
{
if (!seek(d, relPos))
if (!seek(d, relPos)) {
return{};
if (size == -1)
}
if (size == -1) {
size = _size;
}
auto read = std::min(size, _size - relPos);
return d->read(read);
}
bool IFFChunk::seek(QIODevice *d, qint64 relPos) const
{
if (d == nullptr)
if (d == nullptr) {
return false;
}
return d->seek(_dataPos + relPos);
}
@ -140,6 +158,19 @@ bool IFFChunk::innerReadStructure(QIODevice *)
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)
{
return search(cid, ChunkList() << chunk);
@ -158,8 +189,9 @@ IFFChunk::ChunkList IFFChunk::search(const QByteArray &cid, const ChunkList &chu
bool IFFChunk::cacheData(QIODevice *d)
{
if (bytes() > 8 * 1024 * 1024)
if (bytes() > 8 * 1024 * 1024) {
return false;
}
_data = readRawData(d);
return _data.size() == _size;
}
@ -169,7 +201,17 @@ void IFFChunk::setChunks(const ChunkList &chunks)
_chunks = chunks;
}
IFFChunk::ChunkList IFFChunk::innerFromDevice(QIODevice *d, bool *ok, qint32 alignBytes)
qint32 IFFChunk::recursionCounter() const
{
return _recursionCnt;
}
void IFFChunk::setRecursionCounter(qint32 cnt)
{
_recursionCnt = cnt;
}
IFFChunk::ChunkList IFFChunk::innerFromDevice(QIODevice *d, bool *ok, IFFChunk *parent)
{
auto tmp = false;
if (ok == nullptr) {
@ -181,38 +223,63 @@ IFFChunk::ChunkList IFFChunk::innerFromDevice(QIODevice *d, bool *ok, qint32 ali
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) {
return {};
}
IFFChunk::ChunkList list;
for (; !d->atEnd();) {
for (; !d->atEnd() && (nextChunkPos == 0 || d->pos() < nextChunkPos);) {
auto cid = d->peek(4);
QSharedPointer<IFFChunk> chunk;
if (cid == FORM_CHUNK) {
chunk = QSharedPointer<IFFChunk>(new FORMChunk());
} else if (cid == CAMG_CHUNK) {
chunk = QSharedPointer<IFFChunk>(new CAMGChunk());
} else if (cid == CMAP_CHUNK) {
chunk = QSharedPointer<IFFChunk>(new CMAPChunk());
if (cid == ANNO_CHUNK) {
chunk = QSharedPointer<IFFChunk>(new ANNOChunk());
} 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 == DPI__CHUNK) {
chunk = QSharedPointer<IFFChunk>(new DPIChunk());
} else if (cid == FOR4_CHUNK) {
chunk = QSharedPointer<IFFChunk>(new FOR4Chunk());
} else if (cid == TBHD_CHUNK) {
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 == CAMG_CHUNK) {
chunk = QSharedPointer<IFFChunk>(new CAMGChunk());
} else if (cid == CMAP_CHUNK) {
chunk = QSharedPointer<IFFChunk>(new CMAPChunk());
} else if (cid == COPY_CHUNK) {
chunk = QSharedPointer<IFFChunk>(new COPYChunk());
} else if (cid == DATE_CHUNK) {
chunk = QSharedPointer<IFFChunk>(new DATEChunk());
} else if (cid == DPI__CHUNK) {
chunk = QSharedPointer<IFFChunk>(new DPIChunk());
} else if (cid == EXIF_CHUNK) {
chunk = QSharedPointer<IFFChunk>(new EXIFChunk());
} else if (cid == FOR4_CHUNK) {
chunk = QSharedPointer<IFFChunk>(new FOR4Chunk());
} else if (cid == FORM_CHUNK) {
chunk = QSharedPointer<IFFChunk>(new FORMChunk());
} else if (cid == FVER_CHUNK) {
chunk = QSharedPointer<IFFChunk>(new FVERChunk());
} else if (cid == HIST_CHUNK) {
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) {
chunk = QSharedPointer<IFFChunk>(new VERSChunk());
} else if (cid == XMP0_CHUNK) {
chunk = QSharedPointer<IFFChunk>(new XMP0Chunk());
} else { // unknown chunk
chunk = QSharedPointer<IFFChunk>(new IFFChunk());
qInfo() << "IFFChunk::innerFromDevice: unkwnown chunk" << cid;
@ -229,11 +296,18 @@ IFFChunk::ChunkList IFFChunk::innerFromDevice(QIODevice *d, bool *ok, qint32 ali
chunk->setAlignBytes(alignBytes);
}
chunk->setRecursionCounter(recursionCnt + 1);
if (!chunk->readStructure(d)) {
*ok = false;
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;
}
@ -243,7 +317,7 @@ IFFChunk::ChunkList IFFChunk::innerFromDevice(QIODevice *d, bool *ok, qint32 ali
IFFChunk::ChunkList IFFChunk::fromDevice(QIODevice *d, bool *ok)
{
return innerFromDevice(d, ok, 2);
return innerFromDevice(d, ok, nullptr);
}
@ -318,12 +392,12 @@ quint8 BMHDChunk::bitplanes() const
return quint8(data().at(8));
}
quint8 BMHDChunk::masking() const
BMHDChunk::Masking BMHDChunk::masking() const
{
if (!isValid()) {
return 0;
return BMHDChunk::Masking::None;
}
return quint8(data().at(9));
return BMHDChunk::Masking(quint8(data().at(9)));
}
BMHDChunk::Compression BMHDChunk::compression() const
@ -335,14 +409,6 @@ BMHDChunk::Compression BMHDChunk::compression() const
}
quint8 BMHDChunk::padding() const
{
if (!isValid()) {
return 0;
}
return quint8(data().at(11));
}
qint16 BMHDChunk::transparency() const
{
if (!isValid()) {
@ -525,13 +591,13 @@ bool BODYChunk::isValid() const
return chunkId() == BODYChunk::defaultChunkId();
}
QByteArray BODYChunk::strideRead(QIODevice *d, const BMHDChunk *header, const CAMGChunk *camg, const CMAPChunk *cmap) const
QByteArray BODYChunk::strideRead(QIODevice *d, const BMHDChunk *header, const CAMGChunk *camg, const CMAPChunk *cmap, bool isPbm) const
{
if (!isValid() || header == nullptr) {
return {};
}
auto readSize = header->rowLen() * header->bitplanes();
auto readSize = strideSize(header, isPbm);
for(;!d->atEnd() && _readBuffer.size() < readSize;) {
QByteArray buf(readSize, char());
qint64 rr = -1;
@ -540,7 +606,7 @@ QByteArray BODYChunk::strideRead(QIODevice *d, const BMHDChunk *header, const CA
// not accurate: the RLE -128 code is not a noop.
rr = packbitsDecompress(d, buf.data(), buf.size(), true);
} else if (header->compression() == BMHDChunk::Compression::Uncompressed) {
rr = d->read(buf.data(), buf.size());
rr = d->read(buf.data(), buf.size()); // never seen
}
if (rr != readSize)
return {};
@ -549,7 +615,7 @@ QByteArray BODYChunk::strideRead(QIODevice *d, const BMHDChunk *header, const CA
auto planes = _readBuffer.left(readSize);
_readBuffer.remove(0, readSize);
return BODYChunk::deinterleave(planes, header, camg, cmap);
return deinterleave(planes, header, camg, cmap, isPbm);
}
bool BODYChunk::resetStrideRead(QIODevice *d) const
@ -558,14 +624,31 @@ bool BODYChunk::resetStrideRead(QIODevice *d) const
return seek(d);
}
QByteArray BODYChunk::deinterleave(const QByteArray &planes, const BMHDChunk *header, const CAMGChunk *camg, const CMAPChunk *cmap)
quint32 BODYChunk::strideSize(const BMHDChunk *header, bool isPbm) const
{
auto rowLen = qint32(header->rowLen());
auto bitplanes = header->bitplanes();
if (planes.size() != rowLen * bitplanes) {
auto rs = header->rowLen() * header->bitplanes();
if (!isPbm) {
return rs;
}
// 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 {};
}
auto rowLen = qint32(header->rowLen());
auto bitplanes = header->bitplanes();
auto modeId = CAMGChunk::ModeIds();
if (camg) {
modeId = camg->modeId();
@ -587,7 +670,10 @@ QByteArray BODYChunk::deinterleave(const QByteArray &planes, const BMHDChunk *he
case 6:
case 7:
case 8:
if (modeId == CAMGChunk::ModeId::Ham && cmap && bitplanes == 6) {
if (isPbm && bitplanes == 8) {
// The data are contiguous.
ba = planes;
} else if ((modeId & CAMGChunk::ModeId::Ham) && (cmap) && (bitplanes >= 5 && bitplanes <= 8)) {
// From A Quick Introduction to IFF.txt:
//
// Amiga HAM (Hold and Modify) mode lets the Amiga display all 4096 RGB values.
@ -609,6 +695,7 @@ 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();
auto max = (1 << (bitplanes - 2)) - 1;
quint8 prev[3] = {};
for (qint32 i = 0, cnt = 0; i < rowLen; ++i) {
for (qint32 j = 0; j < 8; ++j, ++cnt) {
@ -616,21 +703,20 @@ QByteArray BODYChunk::deinterleave(const QByteArray &planes, const BMHDChunk *he
for (qint32 k = 0, msk = (1 << (7 - j)); k < bitplanes; ++k) {
if ((planes.at(k * rowLen + i) & msk) == 0)
continue;
if (k < 4) {
if (k < bitplanes - 2)
idx |= 1 << k;
} else {
else
ctl |= 1 << (bitplanes - k - 1);
}
}
switch (ctl) {
case 1: // red
prev[0] = idx | (idx << 4);
prev[0] = idx * 255 / max;
break;
case 2: // blue
prev[2] = idx | (idx << 4);
prev[2] = idx * 255 / max;
break;
case 3: // green
prev[1] = idx | (idx << 4);
prev[1] = idx * 255 / max;
break;
default:
if (idx < pal.size()) {
@ -648,7 +734,38 @@ QByteArray BODYChunk::deinterleave(const QByteArray &planes, const BMHDChunk *he
ba[cnt3 + 2] = char(prev[2]);
}
}
} else if (modeId == CAMGChunk::ModeIds()) {
} else if ((modeId & CAMGChunk::ModeId::HalfBrite) && (cmap)) {
// 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:
//
// If the ILBM is not HAM or HALFBRITE, then after parsing and uncompacting if
@ -687,6 +804,12 @@ QByteArray BODYChunk::deinterleave(const QByteArray &planes, const BMHDChunk *he
break;
case 24: // rgb
case 32: // rgba
if (isPbm) {
// TODO: no testcase found
break;
}
// From A Quick Introduction to IFF.txt:
//
// If a deep ILBM (like 12 or 24 planes), there should be no CMAP
@ -753,8 +876,10 @@ bool FORMChunk::innerReadStructure(QIODevice *d)
}
_type = d->read(4);
auto ok = true;
if (_type == QByteArray("ILBM")) {
setChunks(IFFChunk::innerFromDevice(d, &ok, alignBytes()));
if (_type == ILBM_FORM_TYPE) {
setChunks(IFFChunk::innerFromDevice(d, &ok, this));
} else if (_type == PBM__FORM_TYPE) {
setChunks(IFFChunk::innerFromDevice(d, &ok, this));
}
return ok;
}
@ -783,25 +908,28 @@ QImage::Format FORMChunk::format() const
// assume HAM and you'll probably be right.
modeId = CAMGChunk::ModeIds(CAMGChunk::ModeId::Ham);
}
if (h->bitplanes() == 24) {
return QImage::Format_RGB888;
}
if (h->bitplanes() >= 2 && h->bitplanes() <= 8) {
// Currently supported modes: HAM6 and No HAM/HALFBRITE.
if (modeId != CAMGChunk::ModeIds() && (modeId != CAMGChunk::ModeId::Ham || h->bitplanes() != 6))
return QImage::Format_Invalid;
if (modeId & CAMGChunk::ModeId::Ham) {
if (IFFChunk::search(SHAM_CHUNK, chunks()).isEmpty())
return QImage::Format_RGB888;
else // Images with the SHAM chunk do not load correctly.
return QImage::Format_Invalid;
} else if (!cmaps.isEmpty()) {
return QImage::Format_Indexed8;
} else {
return QImage::Format_Grayscale8;
if (h->bitplanes() == 32) {
return QImage::Format_RGBA8888;
}
if (h->bitplanes() >= 2 && h->bitplanes() <= 8) {
if (!IFFChunk::search(SHAM_CHUNK, chunks()).isEmpty() || !IFFChunk::search(CTBL_CHUNK, chunks()).isEmpty()) {
// Images with the SHAM or CTBL chunk do not load correctly: it seems they contains
// a color table but I didn't find any specs.
return QImage::Format_Invalid;
}
if (modeId & (CAMGChunk::ModeId::Ham | CAMGChunk::ModeId::HalfBrite)) {
return QImage::Format_RGB888;
}
if (!cmaps.isEmpty()) {
return QImage::Format_Indexed8;
}
return QImage::Format_Grayscale8;
}
if (h->bitplanes() == 1) {
return QImage::Format_Mono;
@ -856,10 +984,10 @@ bool FOR4Chunk::innerReadStructure(QIODevice *d)
}
_type = d->read(4);
auto ok = true;
if (_type == QByteArray("CIMG")) {
setChunks(IFFChunk::innerFromDevice(d, &ok, alignBytes()));
} else if (_type == QByteArray("TBMP")) {
setChunks(IFFChunk::innerFromDevice(d, &ok, alignBytes()));
if (_type == CIMG_FOR4_TYPE) {
setChunks(IFFChunk::innerFromDevice(d, &ok, this));
} else if (_type == TBMP_FOR4_TYPE) {
setChunks(IFFChunk::innerFromDevice(d, &ok, this));
}
return ok;
}
@ -1054,16 +1182,20 @@ bool RGBAChunk::isTileCompressed(const TBHDChunk *header) const
QPoint RGBAChunk::pos() const
{
return _pos;
return _posPx;
}
QSize RGBAChunk::size() const
{
return _size;
return _sizePx;
}
// Maya version of IFF uses a slightly different algorithm for RLE compression.
qint64 rleMayaDecompress(QIODevice *input, char *output, qint64 olen)
// To understand how it works I saved images with regular patterns from Photoshop
// 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;
for (qint64 rr = 0, available = olen; j < olen; available = olen - j) {
@ -1074,7 +1206,7 @@ qint64 rleMayaDecompress(QIODevice *input, char *output, qint64 olen)
if (input->peek(&n, 1) != 1) { // end of data (or error)
break;
}
rr = qint64(n & 0x7f) + 1;
rr = qint64(n & 0x7F) + 1;
if (rr > available)
break;
}
@ -1084,7 +1216,7 @@ qint64 rleMayaDecompress(QIODevice *input, char *output, qint64 olen)
break;
}
rr = qint64(n & 0x7f) + 1;
rr = qint64(n & 0x7F) + 1;
if ((n & 0x80) == 0) {
auto read = input->read(output + j, rr);
if (rr != read) {
@ -1110,21 +1242,27 @@ QByteArray RGBAChunk::readStride(QIODevice *d, const TBHDChunk *header) const
return {};
}
// detect if the tile is compressed (8 is the size of 4 uint16 before the tile data).
auto compressed = isTileCompressed(header);
for(;!d->atEnd() && _readBuffer.size() < readSize;) {
QByteArray buf(readSize * size().height(), char());
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 (!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;) {
QByteArray buf(readSize * size().height(), char());
qint64 rr = -1;
if (header->compression() == TBHDChunk::Compression::Rle) {
rr = rleMayaDecompress(d, buf.data(), buf.size());
}
} else {
rr = d->read(buf.data(), buf.size());
}
if (rr != buf.size()) {
return {};
}
@ -1137,6 +1275,18 @@ QByteArray RGBAChunk::readStride(QIODevice *d, const TBHDChunk *header) const
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 img(size(), header->format());
@ -1162,7 +1312,7 @@ QImage RGBAChunk::compressedTile(QIODevice *d, const TBHDChunk *header) const
}
for (auto c = 0, cc = header->channels() * header->bpc(); c < cc; ++c) {
#if Q_BYTE_ORDER == Q_BIG_ENDIAN
auto c_bcp = c / cs;
auto c_bcp = c / cs; // Not tried
#else
auto c_bcp = 1 - c / cs;
#endif
@ -1183,6 +1333,15 @@ QImage RGBAChunk::compressedTile(QIODevice *d, const TBHDChunk *header) const
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 img(size(), header->format());
@ -1190,10 +1349,8 @@ QImage RGBAChunk::uncompressedTile(QIODevice *d, const TBHDChunk *header) const
if (bpc == 1) {
auto cs = header->channels();
auto lineSize = img.width() * bpc * cs;
for (auto y = 0, h = img.height(); y < h; ++y) {
auto ba = d->read(lineSize);
auto ba = readStride(d, header);
if (ba.isEmpty()) {
return {};
}
@ -1207,13 +1364,12 @@ QImage RGBAChunk::uncompressedTile(QIODevice *d, const TBHDChunk *header) const
}
} else if (bpc == 2) {
auto cs = header->channels();
auto lineSize = img.width() * bpc * cs;
if (cs < 4) { // alpha on 64-bit images must be 0xFF
std::memset(img.bits(), 0xFF, img.sizeInBytes());
}
for (auto y = 0, h = img.height(); y < h; ++y) {
auto ba = d->read(lineSize);
auto ba = readStride(d, header);
if (ba.isEmpty()) {
return {};
}
@ -1224,7 +1380,7 @@ QImage RGBAChunk::uncompressedTile(QIODevice *d, const TBHDChunk *header) const
auto xcs = x * cs;
auto xcs4 = x * 4;
#if Q_BYTE_ORDER == Q_BIG_ENDIAN
scl[xcs4 + cs - c - 1] = src[xcs + c];
scl[xcs4 + cs - c - 1] = src[xcs + c]; // Not tried
#else
scl[xcs4 + cs - c - 1] = (src[xcs + c] >> 8) | (src[xcs + c] << 8);
#endif
@ -1267,12 +1423,42 @@ bool RGBAChunk::innerReadStructure(QIODevice *d)
return false;
}
_pos = QPoint(x0, y0);
_size = QSize(qint32(x1) - x0 + 1, qint32(y1) - y0 + 1);
_posPx = QPoint(x0, y0);
_sizePx = QSize(qint32(x1) - x0 + 1, qint32(y1) - y0 + 1);
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 ***
* ****************** */
@ -1294,7 +1480,7 @@ bool AUTHChunk::isValid() const
QString AUTHChunk::value() const
{
return QString::fromLatin1(data());
return QString::fromLatin1(data()).replace(QStringLiteral("\0"), QStringLiteral(" ")).trimmed();
}
bool AUTHChunk::innerReadStructure(QIODevice *d)
@ -1302,6 +1488,37 @@ bool AUTHChunk::innerReadStructure(QIODevice *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 ***
* ****************** */
@ -1331,6 +1548,69 @@ bool DATEChunk::innerReadStructure(QIODevice *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 ***
* ****************** */
@ -1384,6 +1664,37 @@ bool HISTChunk::innerReadStructure(QIODevice *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 ***
* ****************** */
@ -1412,3 +1723,34 @@ bool VERSChunk::innerReadStructure(QIODevice *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,6 +9,7 @@
* Format specifications:
* - https://wiki.amigaos.net/wiki/IFF_FORM_and_Chunk_Registry
* - https://www.fileformat.info/format/iff/egff.htm
* - https://download.autodesk.com/us/maya/2010help/index.html (Developer resources -> File formats -> Maya IFF)
*/
#ifndef KIMG_CHUNKS_P_H
@ -22,6 +23,8 @@
#include <QSize>
#include <QSharedPointer>
#include "microexif_p.h"
// Main chunks (Standard)
#define CAT__CHUNK QByteArray("CAT ")
#define FILL_CHUNK QByteArray(" ")
@ -30,7 +33,15 @@
#define PROP_CHUNK QByteArray("PROP")
// Main chuncks (Maya)
#define CAT4_CHUNK QByteArray("CAT4") // 4 byte alignment
#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
#define BMHD_CHUNK QByteArray("BMHD")
@ -38,6 +49,8 @@
#define CAMG_CHUNK QByteArray("CAMG")
#define CMAP_CHUNK QByteArray("CMAP")
#define DPI__CHUNK QByteArray("DPI ")
#define CTBL_CHUNK QByteArray("CTBL") // undocumented
#define SHAM_CHUNK QByteArray("SHAM") // undocumented
// FOR4 CIMG IFF (Maya)
@ -45,11 +58,22 @@
#define TBHD_CHUNK QByteArray("TBHD")
// FORx IFF (found on some IFF format specs)
#define ANNO_CHUNK QByteArray("ANNO")
#define AUTH_CHUNK QByteArray("AUTH")
#define COPY_CHUNK QByteArray("(c) ")
#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 HIST_CHUNK QByteArray("HIST")
#define NAME_CHUNK QByteArray("NAME")
#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; }
@ -195,15 +219,18 @@ public:
template <class T>
static QList<const T*> searchT(const IFFChunk *chunk) {
QList<const T*> list;
if (chunk == nullptr)
if (chunk == nullptr) {
return list;
}
auto cid = T::defaultChunkId();
if (chunk->chunkId() == cid)
if (chunk->chunkId() == cid) {
if (auto c = dynamic_cast<const T*>(chunk))
list << c;
}
auto tmp = chunk->chunks();
for (auto &&c : tmp)
for (auto &&c : tmp) {
list << searchT<T>(c.data());
}
return list;
}
@ -216,8 +243,9 @@ public:
template <class T>
static QList<const T*> searchT(const ChunkList& chunks) {
QList<const T*> list;
for (auto &&chunk : chunks)
for (auto &&chunk : chunks) {
list << searchT<T>(chunk.data());
}
return list;
}
@ -236,11 +264,14 @@ protected:
* \brief setAlignBytes
* \param bytes
*/
void setAlignBytes(qint32 bytes)
{
_align = bytes;
}
void setAlignBytes(qint32 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
@ -256,6 +287,14 @@ protected:
*/
void setChunks(const ChunkList &chunks);
/*!
* \brief recursionCounter
* Protection against stack overflow due to broken data.
* \return The current recursion counter.
*/
qint32 recursionCounter() const;
void setRecursionCounter(qint32 cnt);
inline quint16 ui16(quint8 c1, quint8 c2) const {
return (quint16(c2) << 8) | quint16(c1);
}
@ -272,7 +311,7 @@ protected:
return qint32(ui32(c1, c2, c3, c4));
}
static ChunkList innerFromDevice(QIODevice *d, bool *ok, qint32 alignBytes);
static ChunkList innerFromDevice(QIODevice *d, bool *ok, IFFChunk *parent = nullptr);
private:
char _chunkId[4];
@ -287,19 +326,32 @@ private:
ChunkList _chunks;
qint32 _recursionCnt;
};
/*!
* \brief The IffBMHD class
* \brief The BMHDChunk class
* Bitmap Header
*/
class BMHDChunk: public IFFChunk
{
public:
enum Compression {
Uncompressed = 0,
Rle = 1
Uncompressed = 0, /**< Image data are uncompressed. */
Rle = 1 /**< Image data are RLE compressed. */
};
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;
@ -310,34 +362,88 @@ public:
virtual bool isValid() const override;
/*!
* \brief width
* \return Width of the bitmap in pixels.
*/
qint32 width() const;
/*!
* \brief height
* \return Height of the bitmap in pixels.
*/
qint32 height() const;
/*!
* \brief size
* \return Size in pixels.
*/
QSize size() const;
/*!
* \brief left
* \return The left position of the image.
*/
qint32 left() const;
/*!
* \brief top
* \return The top position of the image.
*/
qint32 top() const;
/*!
* \brief bitplanes
* \return The number of bit planes.
*/
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;
quint8 padding() const;
/*!
* \brief transparency
* \return Transparent "color number".
*/
qint16 transparency() const;
/*!
* \brief xAspectRatio
* \return X pixel aspect.
*/
quint8 xAspectRatio() const;
/*!
* \brief yAspectRatio
* \return Y pixel aspect.
*/
quint8 yAspectRatio() const;
/*!
* \brief pageWidth
* \return Source "page" width in pixels.
*/
quint16 pageWidth() const;
/*!
* \brief pageHeight
* \return Source "page" height in pixels.
*/
quint16 pageHeight() const;
/*!
* \brief rowLen
* \return The row len of a plane.
*/
quint32 rowLen() const;
CHUNKID_DEFINE(BMHD_CHUNK)
@ -463,10 +569,11 @@ public:
* \param header The bitmap header.
* \param camg The CAMG chunk (optional)
* \param cmap The CMAP chunk (optional)
* \param isPbm Set to true if the formType() == "PBM "
* \return The scanline as requested for QImage.
* \warning Call resetStrideRead() once before this one.
*/
QByteArray strideRead(QIODevice *d, const BMHDChunk *header, const CAMGChunk *camg = nullptr, const CMAPChunk *cmap = nullptr) const;
QByteArray strideRead(QIODevice *d, const BMHDChunk *header, const CAMGChunk *camg = nullptr, const CMAPChunk *cmap = nullptr, bool isPbm = false) const;
/*!
* \brief resetStrideRead
@ -480,7 +587,14 @@ public:
bool resetStrideRead(QIODevice *d) const;
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;
};
@ -553,12 +667,11 @@ class TBHDChunk : public IFFChunk
{
public:
enum Flag {
Rgb = 0x01,
Alpha = 0x02,
ZBuffer = 0x04,
Black = 0x10,
Rgb = 0x01, /**< RGB image */
Alpha = 0x02, /**< Image contains alpha channel */
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. */
RgbA = Rgb | Alpha
RgbA = Rgb | Alpha /**< RGBA image */
};
Q_DECLARE_FLAGS(Flags, Flag)
@ -705,13 +818,33 @@ private:
QByteArray readStride(QIODevice *d, const TBHDChunk *header) const;
private:
QPoint _pos;
QPoint _posPx;
QSize _size;
QSize _sizePx;
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
@ -734,6 +867,27 @@ protected:
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
*/
@ -755,6 +909,49 @@ protected:
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
*
@ -799,6 +996,27 @@ 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
*/
@ -820,4 +1038,26 @@ protected:
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

View File

@ -107,11 +107,12 @@ bool IFFHandler::canRead(QIODevice *device)
void addMetadata(QImage& img, const IFFChunk *form)
{
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));
// standard IFF metadata
auto annos = IFFChunk::searchT<ANNOChunk>(form);
if (!annos.isEmpty()) {
auto anno = annos.first()->value();
if (!anno.isEmpty()) {
img.setText(QStringLiteral(META_KEY_DESCRIPTION), anno);
}
}
auto auths = IFFChunk::searchT<AUTHChunk>(form);
@ -121,6 +122,29 @@ void addMetadata(QImage& img, const IFFChunk *form)
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);
if (!vers.isEmpty()) {
auto ver = vers.first()->value();
@ -128,6 +152,40 @@ void addMetadata(QImage& img, const IFFChunk *form)
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)
@ -153,16 +211,6 @@ bool IFFHandler::readStandardImage(QImage *image)
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
auto cmaps = IFFChunk::searchT<CMAPChunk>(form);
if (img.format() == QImage::Format_Indexed8) {
@ -190,9 +238,10 @@ bool IFFHandler::readStandardImage(QImage *image)
qCWarning(LOG_IFFPLUGIN) << "IFFHandler::readStandardImage() error while reading image data";
return false;
}
auto isPbm = form->formType() == PBM__FORM_TYPE;
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);
auto ba = body->strideRead(device(), header, camg, cmap, isPbm);
if (ba.isEmpty()) {
qCWarning(LOG_IFFPLUGIN) << "IFFHandler::readStandardImage() error while reading image scanline";
return false;
@ -201,6 +250,7 @@ bool IFFHandler::readStandardImage(QImage *image)
}
}
// set metadata (including image resolution)
addMetadata(img, form);
*image = img;

View File

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

View File

@ -119,10 +119,11 @@ static bool IsSupported(const TgaHeader &head)
if (head.pixel_size != 8 && head.pixel_size != 16 && head.pixel_size != 24 && head.pixel_size != 32) {
return false;
}
// 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_size != 0 || head.colormap_index != 0 || head.colormap_length != 0)) {
// 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 (head.colormap_type == 0 && (head.colormap_index != 0 || head.colormap_length != 0)) {
return false;
}
return true;
}