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
16 changed files with 149 additions and 784 deletions

View File

@ -1,6 +1,6 @@
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
project(KImageFormats VERSION ${KF_VERSION})

View File

@ -17,7 +17,7 @@ The following image formats have read-only support:
- Animated Windows cursors (ani)
- Camera RAW images (arw, cr2, cr3, dcs, dng, ...)
- Gimp (xcf)
- Interchange Format Files (iff, ilbm, lbm)
- Interchange Format Files (IFF)
- Krita (kra)
- OpenRaster (ora)
- Pixar raster (pxr)

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

@ -9,9 +9,6 @@
#include "packbits_p.h"
#include <QDebug>
#include <QLoggingCategory>
Q_DECLARE_LOGGING_CATEGORY(LOG_IFFPLUGIN)
#define RECURSION_PROTECTION 10
@ -40,19 +37,8 @@ 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 < ' ' || c > '~')
if (!((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c == ' ')))
return false;
}
return true;
@ -72,7 +58,10 @@ bool IFFChunk::readStructure(QIODevice *d)
ok = ok && innerReadStructure(d);
}
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;
}
@ -138,21 +127,18 @@ 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);
}
@ -161,19 +147,6 @@ 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);
@ -192,9 +165,8 @@ 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;
}
@ -214,7 +186,7 @@ void IFFChunk::setRecursionCounter(qint32 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;
if (ok == nullptr) {
@ -226,66 +198,45 @@ IFFChunk::ChunkList IFFChunk::innerFromDevice(QIODevice *d, bool *ok, IFFChunk *
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() && (nextChunkPos == 0 || d->pos() < nextChunkPos);) {
for (; !d->atEnd();) {
auto cid = d->peek(4);
QSharedPointer<IFFChunk> chunk;
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());
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());
} else if (cid == COPY_CHUNK) {
chunk = QSharedPointer<IFFChunk>(new COPYChunk());
} else if (cid == DATE_CHUNK) {
chunk = QSharedPointer<IFFChunk>(new DATEChunk());
} 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 == 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 == 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 == DATE_CHUNK) {
chunk = QSharedPointer<IFFChunk>(new DATEChunk());
} 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());
qCInfo(LOG_IFFPLUGIN) << "IFFChunk::innerFromDevice: unknown chunk" << cid;
qInfo() << "IFFChunk::innerFromDevice: unkwnown chunk" << cid;
}
// change the alignment to the one of main chunk (required for unknown Maya IFF chunks)
@ -305,12 +256,6 @@ IFFChunk::ChunkList IFFChunk::innerFromDevice(QIODevice *d, bool *ok, IFFChunk *
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;
}
@ -320,7 +265,7 @@ IFFChunk::ChunkList IFFChunk::innerFromDevice(QIODevice *d, bool *ok, IFFChunk *
IFFChunk::ChunkList IFFChunk::fromDevice(QIODevice *d, bool *ok)
{
return innerFromDevice(d, ok, nullptr);
return innerFromDevice(d, ok, 2, 0);
}
@ -395,12 +340,12 @@ quint8 BMHDChunk::bitplanes() const
return quint8(data().at(8));
}
BMHDChunk::Masking BMHDChunk::masking() const
quint8 BMHDChunk::masking() const
{
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
@ -412,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
{
if (!isValid()) {
@ -594,13 +547,13 @@ bool BODYChunk::isValid() const
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) {
return {};
}
auto readSize = strideSize(header, isPbm);
auto readSize = header->rowLen() * header->bitplanes();
for(;!d->atEnd() && _readBuffer.size() < readSize;) {
QByteArray buf(readSize, char());
qint64 rr = -1;
@ -609,7 +562,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()); // never seen
rr = d->read(buf.data(), buf.size());
}
if (rr != readSize)
return {};
@ -618,7 +571,7 @@ QByteArray BODYChunk::strideRead(QIODevice *d, const BMHDChunk *header, const CA
auto planes = _readBuffer.left(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
@ -627,31 +580,14 @@ bool BODYChunk::resetStrideRead(QIODevice *d) const
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();
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)) {
auto rowLen = qint32(header->rowLen());
auto bitplanes = header->bitplanes();
if (planes.size() != rowLen * bitplanes) {
return {};
}
auto rowLen = qint32(header->rowLen());
auto bitplanes = header->bitplanes();
auto modeId = CAMGChunk::ModeIds();
if (camg) {
modeId = camg->modeId();
@ -673,10 +609,7 @@ QByteArray BODYChunk::deinterleave(const QByteArray &planes, const BMHDChunk *he
case 6:
case 7:
case 8:
if (isPbm && bitplanes == 8) {
// The data are contiguous.
ba = planes;
} else if ((modeId & CAMGChunk::ModeId::Ham) && (cmap) && (bitplanes >= 5 && bitplanes <= 8)) {
if (modeId == CAMGChunk::ModeId::Ham && cmap && bitplanes == 6) {
// From A Quick Introduction to IFF.txt:
//
// Amiga HAM (Hold and Modify) mode lets the Amiga display all 4096 RGB values.
@ -698,7 +631,6 @@ 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) {
@ -706,20 +638,21 @@ 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 < bitplanes - 2)
if (k < 4) {
idx |= 1 << k;
else
} else {
ctl |= 1 << (bitplanes - k - 1);
}
}
switch (ctl) {
case 1: // red
prev[0] = idx * 255 / max;
prev[0] = idx | (idx << 4);
break;
case 2: // blue
prev[2] = idx * 255 / max;
prev[2] = idx | (idx << 4);
break;
case 3: // green
prev[1] = idx * 255 / max;
prev[1] = idx | (idx << 4);
break;
default:
if (idx < pal.size()) {
@ -727,7 +660,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";
qWarning() << "BODYChunk::deinterleave: palette index" << idx << "is out of range";
}
break;
}
@ -737,38 +670,7 @@ QByteArray BODYChunk::deinterleave(const QByteArray &planes, const BMHDChunk *he
ba[cnt3 + 2] = char(prev[2]);
}
}
} 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 {
qCWarning(LOG_IFFPLUGIN) << "BODYChunk::deinterleave: palette index" << idx << "is out of range";
}
}
}
} else {
} else if (modeId == CAMGChunk::ModeIds()) {
// From A Quick Introduction to IFF.txt:
//
// If the ILBM is not HAM or HALFBRITE, then after parsing and uncompacting if
@ -807,12 +709,6 @@ 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
@ -879,10 +775,8 @@ bool FORMChunk::innerReadStructure(QIODevice *d)
}
_type = d->read(4);
auto ok = true;
if (_type == ILBM_FORM_TYPE) {
setChunks(IFFChunk::innerFromDevice(d, &ok, this));
} else if (_type == PBM__FORM_TYPE) {
setChunks(IFFChunk::innerFromDevice(d, &ok, this));
if (_type == QByteArray("ILBM")) {
setChunks(IFFChunk::innerFromDevice(d, &ok, alignBytes(), recursionCounter()));
}
return ok;
}
@ -911,28 +805,25 @@ 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() == 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.
// 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 | CAMGChunk::ModeId::HalfBrite)) {
return QImage::Format_RGB888;
}
if (!cmaps.isEmpty()) {
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;
}
return QImage::Format_Grayscale8;
}
if (h->bitplanes() == 1) {
return QImage::Format_Mono;
@ -987,10 +878,10 @@ bool FOR4Chunk::innerReadStructure(QIODevice *d)
}
_type = d->read(4);
auto ok = true;
if (_type == CIMG_FOR4_TYPE) {
setChunks(IFFChunk::innerFromDevice(d, &ok, this));
} else if (_type == TBMP_FOR4_TYPE) {
setChunks(IFFChunk::innerFromDevice(d, &ok, this));
if (_type == QByteArray("CIMG")) {
setChunks(IFFChunk::innerFromDevice(d, &ok, alignBytes(), recursionCounter()));
} else if (_type == QByteArray("TBMP")) {
setChunks(IFFChunk::innerFromDevice(d, &ok, alignBytes(), recursionCounter()));
}
return ok;
}
@ -1185,20 +1076,16 @@ bool RGBAChunk::isTileCompressed(const TBHDChunk *header) const
QPoint RGBAChunk::pos() const
{
return _posPx;
return _pos;
}
QSize RGBAChunk::size() const
{
return _sizePx;
return _size;
}
// 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
// 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 rleMayaDecompress(QIODevice *input, char *output, qint64 olen)
{
qint64 j = 0;
for (qint64 rr = 0, available = olen; j < olen; available = olen - j) {
@ -1209,7 +1096,7 @@ inline 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;
}
@ -1219,7 +1106,7 @@ inline 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) {
@ -1245,26 +1132,20 @@ QByteArray RGBAChunk::readStride(QIODevice *d, const TBHDChunk *header) const
return {};
}
// 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
// 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 (header->compression() == TBHDChunk::Compression::Rle) {
rr = rleMayaDecompress(d, buf.data(), buf.size());
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) {
rr = rleMayaDecompress(d, buf.data(), buf.size());
}
} else {
rr = d->read(buf.data(), buf.size());
}
if (rr != buf.size()) {
return {};
@ -1278,18 +1159,6 @@ 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());
@ -1315,7 +1184,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; // Not tried
auto c_bcp = c / cs;
#else
auto c_bcp = 1 - c / cs;
#endif
@ -1336,15 +1205,6 @@ 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());
@ -1352,8 +1212,10 @@ 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 = readStride(d, header);
auto ba = d->read(lineSize);
if (ba.isEmpty()) {
return {};
}
@ -1367,12 +1229,13 @@ 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 = readStride(d, header);
auto ba = d->read(lineSize);
if (ba.isEmpty()) {
return {};
}
@ -1383,7 +1246,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]; // Not tried
scl[xcs4 + cs - c - 1] = src[xcs + c];
#else
scl[xcs4 + cs - c - 1] = (src[xcs + c] >> 8) | (src[xcs + c] << 8);
#endif
@ -1426,42 +1289,12 @@ bool RGBAChunk::innerReadStructure(QIODevice *d)
return false;
}
_posPx = QPoint(x0, y0);
_sizePx = QSize(qint32(x1) - x0 + 1, qint32(y1) - y0 + 1);
_pos = QPoint(x0, y0);
_size = 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 ***
* ****************** */
@ -1483,7 +1316,7 @@ bool AUTHChunk::isValid() const
QString AUTHChunk::value() const
{
return QString::fromLatin1(data()).replace(QStringLiteral("\0"), QStringLiteral(" ")).trimmed();
return QString::fromLatin1(data());
}
bool AUTHChunk::innerReadStructure(QIODevice *d)
@ -1491,37 +1324,6 @@ 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 ***
* ****************** */
@ -1551,69 +1353,6 @@ 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 ***
* ****************** */
@ -1667,37 +1406,6 @@ 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 ***
* ****************** */
@ -1726,34 +1434,3 @@ 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,7 +9,6 @@
* 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
@ -23,8 +22,6 @@
#include <QSize>
#include <QSharedPointer>
#include "microexif_p.h"
// Main chunks (Standard)
#define CAT__CHUNK QByteArray("CAT ")
#define FILL_CHUNK QByteArray(" ")
@ -33,15 +30,7 @@
#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")
@ -49,8 +38,6 @@
#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)
@ -58,22 +45,11 @@
#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; }
@ -219,18 +195,15 @@ 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;
}
@ -243,9 +216,8 @@ 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;
}
@ -264,14 +236,11 @@ protected:
* \brief setAlignBytes
* \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
@ -311,7 +280,7 @@ protected:
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:
char _chunkId[4];
@ -327,31 +296,20 @@ private:
ChunkList _chunks;
qint32 _recursionCnt;
};
/*!
* \brief The BMHDChunk class
* \brief The IffBMHD class
* Bitmap Header
*/
class BMHDChunk: public IFFChunk
{
public:
enum Compression {
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. */
Uncompressed = 0,
Rle = 1
};
virtual ~BMHDChunk() override;
@ -362,88 +320,34 @@ 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;
/*!
* \brief masking
* \return Kind of masking is to be used for this image.
*/
Masking masking() const;
quint8 masking() const;
/*!
* \brief compression
* \return The type of compression used.
*/
Compression compression() const;
/*!
* \brief transparency
* \return Transparent "color number".
*/
quint8 padding() const;
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)
@ -569,11 +473,10 @@ 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, bool isPbm = false) const;
QByteArray strideRead(QIODevice *d, const BMHDChunk *header, const CAMGChunk *camg = nullptr, const CMAPChunk *cmap = nullptr) const;
/*!
* \brief resetStrideRead
@ -587,14 +490,7 @@ public:
bool resetStrideRead(QIODevice *d) const;
private:
/*!
* \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;
static QByteArray deinterleave(const QByteArray &planes, const BMHDChunk *header, const CAMGChunk *camg = nullptr, const CMAPChunk *cmap = nullptr);
mutable QByteArray _readBuffer;
};
@ -667,11 +563,12 @@ class TBHDChunk : public IFFChunk
{
public:
enum Flag {
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. */
Rgb = 0x01,
Alpha = 0x02,
ZBuffer = 0x04,
Black = 0x10,
RgbA = Rgb | Alpha /**< RGBA image */
RgbA = Rgb | Alpha
};
Q_DECLARE_FLAGS(Flags, Flag)
@ -818,33 +715,13 @@ private:
QByteArray readStride(QIODevice *d, const TBHDChunk *header) const;
private:
QPoint _posPx;
QPoint _pos;
QSize _sizePx;
QSize _size;
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
@ -867,27 +744,6 @@ 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
*/
@ -909,49 +765,6 @@ 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
*
@ -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
*/
@ -1038,26 +830,4 @@ 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,12 +107,11 @@ bool IFFHandler::canRead(QIODevice *device)
void addMetadata(QImage& img, const IFFChunk *form)
{
// 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 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 auths = IFFChunk::searchT<AUTHChunk>(form);
@ -122,29 +121,6 @@ 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();
@ -152,40 +128,6 @@ 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)
@ -211,6 +153,16 @@ 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) {
@ -238,10 +190,9 @@ 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, isPbm);
auto ba = body->strideRead(device(), header, camg, cmap);
if (ba.isEmpty()) {
qCWarning(LOG_IFFPLUGIN) << "IFFHandler::readStandardImage() error while reading image scanline";
return false;
@ -250,7 +201,6 @@ bool IFFHandler::readStandardImage(QImage *image)
}
}
// set metadata (including image resolution)
addMetadata(img, form);
*image = img;
@ -384,7 +334,7 @@ QVariant IFFHandler::option(ImageOption option) const
QImageIOPlugin::Capabilities IFFPlugin::capabilities(QIODevice *device, const QByteArray &format) const
{
if (format == "iff" || format == "ilbm" || format == "lbm") {
if (format == "iff") {
return Capabilities(CanRead);
}
if (!format.isEmpty()) {

View File

@ -1,4 +1,4 @@
{
"Keys": [ "iff", "iff", "lbm", "ilbm" ],
"MimeTypes": [ "application/x-iff", "image/x-ilbm", "image/x-ilbm", "image/x-ilbm" ]
"Keys": [ "iff" ],
"MimeTypes": [ "application/x-iff" ]
}

View File

@ -1072,7 +1072,7 @@ QByteArray MicroExif::toByteArray(const QDataStream::ByteOrder &byteOrder, const
QByteArray ba;
{
QBuffer buf(&ba);
if (!write(&buf, byteOrder, version))
if (!write(&buf, byteOrder))
return {};
}
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) {
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 (head.colormap_type == 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_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)) {
return false;
}
return true;
}