mirror of
https://invent.kde.org/frameworks/kimageformats.git
synced 2025-07-15 11:14:18 -04:00
Compare commits
5 Commits
v6.16.0-rc
...
master
Author | SHA1 | Date | |
---|---|---|---|
0a9f9fe106 | |||
21b3b890ec | |||
aef4bd7e1c | |||
ea1983a7d1 | |||
775b53f1f6 |
@ -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})
|
||||
|
||||
|
BIN
autotests/read/iff/meta_rgba.iff
Normal file
BIN
autotests/read/iff/meta_rgba.iff
Normal file
Binary file not shown.
31
autotests/read/iff/meta_rgba.iff.json
Normal file
31
autotests/read/iff/meta_rgba.iff.json
Normal 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
|
||||
}
|
||||
}
|
||||
]
|
BIN
autotests/read/iff/meta_rgba.png
Normal file
BIN
autotests/read/iff/meta_rgba.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 28 KiB |
BIN
autotests/read/iff/testcard_pbm.iff
Normal file
BIN
autotests/read/iff/testcard_pbm.iff
Normal file
Binary file not shown.
BIN
autotests/read/iff/testcard_pbm.png
Normal file
BIN
autotests/read/iff/testcard_pbm.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.2 KiB |
BIN
autotests/read/iff/testcard_rlepbm.iff
Normal file
BIN
autotests/read/iff/testcard_rlepbm.iff
Normal file
Binary file not shown.
BIN
autotests/read/iff/testcard_rlepbm.png
Normal file
BIN
autotests/read/iff/testcard_rlepbm.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.2 KiB |
@ -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)
|
||||
|
||||
##################################
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
isn’t a “color number” since it’s 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
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user