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)
|
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
|
set(KF_DEP_VERSION "6.16.0") # handled by release scripts
|
||||||
project(KImageFormats VERSION ${KF_VERSION})
|
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>
|
#include <QDebug>
|
||||||
|
|
||||||
|
#define RECURSION_PROTECTION 10
|
||||||
|
|
||||||
IFFChunk::~IFFChunk()
|
IFFChunk::~IFFChunk()
|
||||||
{
|
{
|
||||||
|
|
||||||
@ -20,6 +22,7 @@ IFFChunk::IFFChunk()
|
|||||||
, _size{0}
|
, _size{0}
|
||||||
, _align{2}
|
, _align{2}
|
||||||
, _dataPos{0}
|
, _dataPos{0}
|
||||||
|
, _recursionCnt{0}
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -34,8 +37,19 @@ bool IFFChunk::operator ==(const IFFChunk &other) const
|
|||||||
bool IFFChunk::isValid() const
|
bool IFFChunk::isValid() const
|
||||||
{
|
{
|
||||||
auto cid = chunkId();
|
auto cid = chunkId();
|
||||||
|
if (cid.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// A “type ID”, “property name”, “FORM type”, or any other IFF
|
||||||
|
// identifier is a 32-bit value: the concatenation of four ASCII
|
||||||
|
// characters in the range “ ” (SP, hex 20) through “~” (hex 7E).
|
||||||
|
// Spaces (hex 20) should not precede printing characters;
|
||||||
|
// trailing spaces are OK. Control characters are forbidden.
|
||||||
|
if (cid.at(0) == ' ') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
for (auto &&c : cid) {
|
for (auto &&c : cid) {
|
||||||
if (!((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c == ' ')))
|
if (c < ' ' || c > '~')
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@ -49,12 +63,13 @@ qint32 IFFChunk::alignBytes() const
|
|||||||
bool IFFChunk::readStructure(QIODevice *d)
|
bool IFFChunk::readStructure(QIODevice *d)
|
||||||
{
|
{
|
||||||
auto ok = readInfo(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);
|
ok = ok && innerReadStructure(d);
|
||||||
|
}
|
||||||
if (ok) {
|
if (ok) {
|
||||||
auto pos = _dataPos + _size;
|
ok = d->seek(nextChunkPos());
|
||||||
if (auto align = pos % alignBytes())
|
|
||||||
pos += alignBytes() - align;
|
|
||||||
ok = d->seek(pos);
|
|
||||||
}
|
}
|
||||||
return ok;
|
return ok;
|
||||||
}
|
}
|
||||||
@ -120,18 +135,21 @@ bool IFFChunk::readInfo(QIODevice *d)
|
|||||||
|
|
||||||
QByteArray IFFChunk::readRawData(QIODevice *d, qint64 relPos, qint64 size) const
|
QByteArray IFFChunk::readRawData(QIODevice *d, qint64 relPos, qint64 size) const
|
||||||
{
|
{
|
||||||
if (!seek(d, relPos))
|
if (!seek(d, relPos)) {
|
||||||
return{};
|
return{};
|
||||||
if (size == -1)
|
}
|
||||||
|
if (size == -1) {
|
||||||
size = _size;
|
size = _size;
|
||||||
|
}
|
||||||
auto read = std::min(size, _size - relPos);
|
auto read = std::min(size, _size - relPos);
|
||||||
return d->read(read);
|
return d->read(read);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IFFChunk::seek(QIODevice *d, qint64 relPos) const
|
bool IFFChunk::seek(QIODevice *d, qint64 relPos) const
|
||||||
{
|
{
|
||||||
if (d == nullptr)
|
if (d == nullptr) {
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
return d->seek(_dataPos + relPos);
|
return d->seek(_dataPos + relPos);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -140,6 +158,19 @@ bool IFFChunk::innerReadStructure(QIODevice *)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void IFFChunk::setAlignBytes(qint32 bytes)
|
||||||
|
{
|
||||||
|
_align = bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
qint64 IFFChunk::nextChunkPos() const
|
||||||
|
{
|
||||||
|
auto pos = _dataPos + _size;
|
||||||
|
if (auto align = pos % alignBytes())
|
||||||
|
pos += alignBytes() - align;
|
||||||
|
return pos;
|
||||||
|
}
|
||||||
|
|
||||||
IFFChunk::ChunkList IFFChunk::search(const QByteArray &cid, const QSharedPointer<IFFChunk> &chunk)
|
IFFChunk::ChunkList IFFChunk::search(const QByteArray &cid, const QSharedPointer<IFFChunk> &chunk)
|
||||||
{
|
{
|
||||||
return search(cid, ChunkList() << chunk);
|
return search(cid, ChunkList() << chunk);
|
||||||
@ -158,8 +189,9 @@ IFFChunk::ChunkList IFFChunk::search(const QByteArray &cid, const ChunkList &chu
|
|||||||
|
|
||||||
bool IFFChunk::cacheData(QIODevice *d)
|
bool IFFChunk::cacheData(QIODevice *d)
|
||||||
{
|
{
|
||||||
if (bytes() > 8 * 1024 * 1024)
|
if (bytes() > 8 * 1024 * 1024) {
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
_data = readRawData(d);
|
_data = readRawData(d);
|
||||||
return _data.size() == _size;
|
return _data.size() == _size;
|
||||||
}
|
}
|
||||||
@ -169,7 +201,17 @@ void IFFChunk::setChunks(const ChunkList &chunks)
|
|||||||
_chunks = 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;
|
auto tmp = false;
|
||||||
if (ok == nullptr) {
|
if (ok == nullptr) {
|
||||||
@ -181,38 +223,63 @@ IFFChunk::ChunkList IFFChunk::innerFromDevice(QIODevice *d, bool *ok, qint32 ali
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto alignBytes = qint32(2);
|
||||||
|
auto recursionCnt = qint32();
|
||||||
|
auto nextChunkPos = qint64();
|
||||||
|
if (parent) {
|
||||||
|
alignBytes = parent->alignBytes();
|
||||||
|
recursionCnt = parent->recursionCounter();
|
||||||
|
nextChunkPos = parent->nextChunkPos();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (recursionCnt > RECURSION_PROTECTION) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
IFFChunk::ChunkList list;
|
IFFChunk::ChunkList list;
|
||||||
for (; !d->atEnd();) {
|
for (; !d->atEnd() && (nextChunkPos == 0 || d->pos() < nextChunkPos);) {
|
||||||
auto cid = d->peek(4);
|
auto cid = d->peek(4);
|
||||||
QSharedPointer<IFFChunk> chunk;
|
QSharedPointer<IFFChunk> chunk;
|
||||||
if (cid == FORM_CHUNK) {
|
if (cid == ANNO_CHUNK) {
|
||||||
chunk = QSharedPointer<IFFChunk>(new FORMChunk());
|
chunk = QSharedPointer<IFFChunk>(new ANNOChunk());
|
||||||
} else if (cid == CAMG_CHUNK) {
|
} else if (cid == AUTH_CHUNK) {
|
||||||
chunk = QSharedPointer<IFFChunk>(new CAMGChunk());
|
chunk = QSharedPointer<IFFChunk>(new AUTHChunk());
|
||||||
} else if (cid == CMAP_CHUNK) {
|
|
||||||
chunk = QSharedPointer<IFFChunk>(new CMAPChunk());
|
|
||||||
} else if (cid == BMHD_CHUNK) {
|
} else if (cid == BMHD_CHUNK) {
|
||||||
chunk = QSharedPointer<IFFChunk>(new BMHDChunk());
|
chunk = QSharedPointer<IFFChunk>(new BMHDChunk());
|
||||||
} else if (cid == BODY_CHUNK) {
|
} else if (cid == BODY_CHUNK) {
|
||||||
chunk = QSharedPointer<IFFChunk>(new BODYChunk());
|
chunk = QSharedPointer<IFFChunk>(new BODYChunk());
|
||||||
} else if (cid == DPI__CHUNK) {
|
} else if (cid == CAMG_CHUNK) {
|
||||||
chunk = QSharedPointer<IFFChunk>(new DPIChunk());
|
chunk = QSharedPointer<IFFChunk>(new CAMGChunk());
|
||||||
} else if (cid == FOR4_CHUNK) {
|
} else if (cid == CMAP_CHUNK) {
|
||||||
chunk = QSharedPointer<IFFChunk>(new FOR4Chunk());
|
chunk = QSharedPointer<IFFChunk>(new CMAPChunk());
|
||||||
} else if (cid == TBHD_CHUNK) {
|
} else if (cid == COPY_CHUNK) {
|
||||||
chunk = QSharedPointer<IFFChunk>(new TBHDChunk());
|
chunk = QSharedPointer<IFFChunk>(new COPYChunk());
|
||||||
} 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) {
|
} else if (cid == DATE_CHUNK) {
|
||||||
chunk = QSharedPointer<IFFChunk>(new DATEChunk());
|
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) {
|
} else if (cid == FVER_CHUNK) {
|
||||||
chunk = QSharedPointer<IFFChunk>(new FVERChunk());
|
chunk = QSharedPointer<IFFChunk>(new FVERChunk());
|
||||||
} else if (cid == HIST_CHUNK) {
|
} else if (cid == HIST_CHUNK) {
|
||||||
chunk = QSharedPointer<IFFChunk>(new HISTChunk());
|
chunk = QSharedPointer<IFFChunk>(new HISTChunk());
|
||||||
|
} else if (cid == ICCP_CHUNK) {
|
||||||
|
chunk = QSharedPointer<IFFChunk>(new ICCPChunk());
|
||||||
|
} else if (cid == NAME_CHUNK) {
|
||||||
|
chunk = QSharedPointer<IFFChunk>(new NAMEChunk());
|
||||||
|
} else if (cid == RGBA_CHUNK) {
|
||||||
|
chunk = QSharedPointer<IFFChunk>(new RGBAChunk());
|
||||||
|
} else if (cid == TBHD_CHUNK) {
|
||||||
|
chunk = QSharedPointer<IFFChunk>(new TBHDChunk());
|
||||||
} else if (cid == VERS_CHUNK) {
|
} else if (cid == VERS_CHUNK) {
|
||||||
chunk = QSharedPointer<IFFChunk>(new VERSChunk());
|
chunk = QSharedPointer<IFFChunk>(new VERSChunk());
|
||||||
|
} else if (cid == XMP0_CHUNK) {
|
||||||
|
chunk = QSharedPointer<IFFChunk>(new XMP0Chunk());
|
||||||
} else { // unknown chunk
|
} else { // unknown chunk
|
||||||
chunk = QSharedPointer<IFFChunk>(new IFFChunk());
|
chunk = QSharedPointer<IFFChunk>(new IFFChunk());
|
||||||
qInfo() << "IFFChunk::innerFromDevice: unkwnown chunk" << cid;
|
qInfo() << "IFFChunk::innerFromDevice: unkwnown chunk" << cid;
|
||||||
@ -229,11 +296,18 @@ IFFChunk::ChunkList IFFChunk::innerFromDevice(QIODevice *d, bool *ok, qint32 ali
|
|||||||
chunk->setAlignBytes(alignBytes);
|
chunk->setAlignBytes(alignBytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
chunk->setRecursionCounter(recursionCnt + 1);
|
||||||
if (!chunk->readStructure(d)) {
|
if (!chunk->readStructure(d)) {
|
||||||
*ok = false;
|
*ok = false;
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// skip any non-IFF data at the end of the file.
|
||||||
|
// NOTE: there should be no more chunks after the first (root)
|
||||||
|
if (nextChunkPos == 0) {
|
||||||
|
nextChunkPos = chunk->nextChunkPos();
|
||||||
|
}
|
||||||
|
|
||||||
list << chunk;
|
list << chunk;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -243,7 +317,7 @@ IFFChunk::ChunkList IFFChunk::innerFromDevice(QIODevice *d, bool *ok, qint32 ali
|
|||||||
|
|
||||||
IFFChunk::ChunkList IFFChunk::fromDevice(QIODevice *d, bool *ok)
|
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));
|
return quint8(data().at(8));
|
||||||
}
|
}
|
||||||
|
|
||||||
quint8 BMHDChunk::masking() const
|
BMHDChunk::Masking BMHDChunk::masking() const
|
||||||
{
|
{
|
||||||
if (!isValid()) {
|
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
|
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
|
qint16 BMHDChunk::transparency() const
|
||||||
{
|
{
|
||||||
if (!isValid()) {
|
if (!isValid()) {
|
||||||
@ -525,13 +591,13 @@ bool BODYChunk::isValid() const
|
|||||||
return chunkId() == BODYChunk::defaultChunkId();
|
return chunkId() == BODYChunk::defaultChunkId();
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArray BODYChunk::strideRead(QIODevice *d, const BMHDChunk *header, const CAMGChunk *camg, const CMAPChunk *cmap) const
|
QByteArray BODYChunk::strideRead(QIODevice *d, const BMHDChunk *header, const CAMGChunk *camg, const CMAPChunk *cmap, bool isPbm) const
|
||||||
{
|
{
|
||||||
if (!isValid() || header == nullptr) {
|
if (!isValid() || header == nullptr) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
auto readSize = header->rowLen() * header->bitplanes();
|
auto readSize = strideSize(header, isPbm);
|
||||||
for(;!d->atEnd() && _readBuffer.size() < readSize;) {
|
for(;!d->atEnd() && _readBuffer.size() < readSize;) {
|
||||||
QByteArray buf(readSize, char());
|
QByteArray buf(readSize, char());
|
||||||
qint64 rr = -1;
|
qint64 rr = -1;
|
||||||
@ -540,7 +606,7 @@ QByteArray BODYChunk::strideRead(QIODevice *d, const BMHDChunk *header, const CA
|
|||||||
// not accurate: the RLE -128 code is not a noop.
|
// not accurate: the RLE -128 code is not a noop.
|
||||||
rr = packbitsDecompress(d, buf.data(), buf.size(), true);
|
rr = packbitsDecompress(d, buf.data(), buf.size(), true);
|
||||||
} else if (header->compression() == BMHDChunk::Compression::Uncompressed) {
|
} else if (header->compression() == BMHDChunk::Compression::Uncompressed) {
|
||||||
rr = d->read(buf.data(), buf.size());
|
rr = d->read(buf.data(), buf.size()); // never seen
|
||||||
}
|
}
|
||||||
if (rr != readSize)
|
if (rr != readSize)
|
||||||
return {};
|
return {};
|
||||||
@ -549,7 +615,7 @@ QByteArray BODYChunk::strideRead(QIODevice *d, const BMHDChunk *header, const CA
|
|||||||
|
|
||||||
auto planes = _readBuffer.left(readSize);
|
auto planes = _readBuffer.left(readSize);
|
||||||
_readBuffer.remove(0, readSize);
|
_readBuffer.remove(0, readSize);
|
||||||
return BODYChunk::deinterleave(planes, header, camg, cmap);
|
return deinterleave(planes, header, camg, cmap, isPbm);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool BODYChunk::resetStrideRead(QIODevice *d) const
|
bool BODYChunk::resetStrideRead(QIODevice *d) const
|
||||||
@ -558,14 +624,31 @@ bool BODYChunk::resetStrideRead(QIODevice *d) const
|
|||||||
return seek(d);
|
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 rs = header->rowLen() * header->bitplanes();
|
||||||
auto bitplanes = header->bitplanes();
|
if (!isPbm) {
|
||||||
if (planes.size() != rowLen * bitplanes) {
|
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 {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto rowLen = qint32(header->rowLen());
|
||||||
|
auto bitplanes = header->bitplanes();
|
||||||
auto modeId = CAMGChunk::ModeIds();
|
auto modeId = CAMGChunk::ModeIds();
|
||||||
if (camg) {
|
if (camg) {
|
||||||
modeId = camg->modeId();
|
modeId = camg->modeId();
|
||||||
@ -587,7 +670,10 @@ QByteArray BODYChunk::deinterleave(const QByteArray &planes, const BMHDChunk *he
|
|||||||
case 6:
|
case 6:
|
||||||
case 7:
|
case 7:
|
||||||
case 8:
|
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:
|
// From A Quick Introduction to IFF.txt:
|
||||||
//
|
//
|
||||||
// Amiga HAM (Hold and Modify) mode lets the Amiga display all 4096 RGB values.
|
// Amiga HAM (Hold and Modify) mode lets the Amiga display all 4096 RGB values.
|
||||||
@ -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
|
// 11 - hold previous. replacing Green component with bits from planes 0-3
|
||||||
ba = QByteArray(rowLen * 8 * 3, char());
|
ba = QByteArray(rowLen * 8 * 3, char());
|
||||||
auto pal = cmap->palette();
|
auto pal = cmap->palette();
|
||||||
|
auto max = (1 << (bitplanes - 2)) - 1;
|
||||||
quint8 prev[3] = {};
|
quint8 prev[3] = {};
|
||||||
for (qint32 i = 0, cnt = 0; i < rowLen; ++i) {
|
for (qint32 i = 0, cnt = 0; i < rowLen; ++i) {
|
||||||
for (qint32 j = 0; j < 8; ++j, ++cnt) {
|
for (qint32 j = 0; j < 8; ++j, ++cnt) {
|
||||||
@ -616,21 +703,20 @@ QByteArray BODYChunk::deinterleave(const QByteArray &planes, const BMHDChunk *he
|
|||||||
for (qint32 k = 0, msk = (1 << (7 - j)); k < bitplanes; ++k) {
|
for (qint32 k = 0, msk = (1 << (7 - j)); k < bitplanes; ++k) {
|
||||||
if ((planes.at(k * rowLen + i) & msk) == 0)
|
if ((planes.at(k * rowLen + i) & msk) == 0)
|
||||||
continue;
|
continue;
|
||||||
if (k < 4) {
|
if (k < bitplanes - 2)
|
||||||
idx |= 1 << k;
|
idx |= 1 << k;
|
||||||
} else {
|
else
|
||||||
ctl |= 1 << (bitplanes - k - 1);
|
ctl |= 1 << (bitplanes - k - 1);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
switch (ctl) {
|
switch (ctl) {
|
||||||
case 1: // red
|
case 1: // red
|
||||||
prev[0] = idx | (idx << 4);
|
prev[0] = idx * 255 / max;
|
||||||
break;
|
break;
|
||||||
case 2: // blue
|
case 2: // blue
|
||||||
prev[2] = idx | (idx << 4);
|
prev[2] = idx * 255 / max;
|
||||||
break;
|
break;
|
||||||
case 3: // green
|
case 3: // green
|
||||||
prev[1] = idx | (idx << 4);
|
prev[1] = idx * 255 / max;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
if (idx < pal.size()) {
|
if (idx < pal.size()) {
|
||||||
@ -648,7 +734,38 @@ QByteArray BODYChunk::deinterleave(const QByteArray &planes, const BMHDChunk *he
|
|||||||
ba[cnt3 + 2] = char(prev[2]);
|
ba[cnt3 + 2] = char(prev[2]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (modeId == CAMGChunk::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:
|
// From A Quick Introduction to IFF.txt:
|
||||||
//
|
//
|
||||||
// If the ILBM is not HAM or HALFBRITE, then after parsing and uncompacting if
|
// If the ILBM is not HAM or HALFBRITE, then after parsing and uncompacting if
|
||||||
@ -687,6 +804,12 @@ QByteArray BODYChunk::deinterleave(const QByteArray &planes, const BMHDChunk *he
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 24: // rgb
|
case 24: // rgb
|
||||||
|
case 32: // rgba
|
||||||
|
if (isPbm) {
|
||||||
|
// TODO: no testcase found
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
// From A Quick Introduction to IFF.txt:
|
// From A Quick Introduction to IFF.txt:
|
||||||
//
|
//
|
||||||
// If a deep ILBM (like 12 or 24 planes), there should be no CMAP
|
// If a deep ILBM (like 12 or 24 planes), there should be no CMAP
|
||||||
@ -753,8 +876,10 @@ bool FORMChunk::innerReadStructure(QIODevice *d)
|
|||||||
}
|
}
|
||||||
_type = d->read(4);
|
_type = d->read(4);
|
||||||
auto ok = true;
|
auto ok = true;
|
||||||
if (_type == QByteArray("ILBM")) {
|
if (_type == ILBM_FORM_TYPE) {
|
||||||
setChunks(IFFChunk::innerFromDevice(d, &ok, alignBytes()));
|
setChunks(IFFChunk::innerFromDevice(d, &ok, this));
|
||||||
|
} else if (_type == PBM__FORM_TYPE) {
|
||||||
|
setChunks(IFFChunk::innerFromDevice(d, &ok, this));
|
||||||
}
|
}
|
||||||
return ok;
|
return ok;
|
||||||
}
|
}
|
||||||
@ -783,25 +908,28 @@ QImage::Format FORMChunk::format() const
|
|||||||
// assume HAM and you'll probably be right.
|
// assume HAM and you'll probably be right.
|
||||||
modeId = CAMGChunk::ModeIds(CAMGChunk::ModeId::Ham);
|
modeId = CAMGChunk::ModeIds(CAMGChunk::ModeId::Ham);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (h->bitplanes() == 24) {
|
if (h->bitplanes() == 24) {
|
||||||
return QImage::Format_RGB888;
|
return QImage::Format_RGB888;
|
||||||
}
|
}
|
||||||
if (h->bitplanes() >= 2 && h->bitplanes() <= 8) {
|
if (h->bitplanes() == 32) {
|
||||||
// Currently supported modes: HAM6 and No HAM/HALFBRITE.
|
return QImage::Format_RGBA8888;
|
||||||
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() >= 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) {
|
if (h->bitplanes() == 1) {
|
||||||
return QImage::Format_Mono;
|
return QImage::Format_Mono;
|
||||||
@ -856,10 +984,10 @@ bool FOR4Chunk::innerReadStructure(QIODevice *d)
|
|||||||
}
|
}
|
||||||
_type = d->read(4);
|
_type = d->read(4);
|
||||||
auto ok = true;
|
auto ok = true;
|
||||||
if (_type == QByteArray("CIMG")) {
|
if (_type == CIMG_FOR4_TYPE) {
|
||||||
setChunks(IFFChunk::innerFromDevice(d, &ok, alignBytes()));
|
setChunks(IFFChunk::innerFromDevice(d, &ok, this));
|
||||||
} else if (_type == QByteArray("TBMP")) {
|
} else if (_type == TBMP_FOR4_TYPE) {
|
||||||
setChunks(IFFChunk::innerFromDevice(d, &ok, alignBytes()));
|
setChunks(IFFChunk::innerFromDevice(d, &ok, this));
|
||||||
}
|
}
|
||||||
return ok;
|
return ok;
|
||||||
}
|
}
|
||||||
@ -1054,16 +1182,20 @@ bool RGBAChunk::isTileCompressed(const TBHDChunk *header) const
|
|||||||
|
|
||||||
QPoint RGBAChunk::pos() const
|
QPoint RGBAChunk::pos() const
|
||||||
{
|
{
|
||||||
return _pos;
|
return _posPx;
|
||||||
}
|
}
|
||||||
|
|
||||||
QSize RGBAChunk::size() const
|
QSize RGBAChunk::size() const
|
||||||
{
|
{
|
||||||
return _size;
|
return _sizePx;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Maya version of IFF uses a slightly different algorithm for RLE compression.
|
// 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;
|
qint64 j = 0;
|
||||||
for (qint64 rr = 0, available = olen; j < olen; available = olen - j) {
|
for (qint64 rr = 0, available = olen; j < olen; available = olen - j) {
|
||||||
@ -1074,7 +1206,7 @@ qint64 rleMayaDecompress(QIODevice *input, char *output, qint64 olen)
|
|||||||
if (input->peek(&n, 1) != 1) { // end of data (or error)
|
if (input->peek(&n, 1) != 1) { // end of data (or error)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
rr = qint64(n & 0x7f) + 1;
|
rr = qint64(n & 0x7F) + 1;
|
||||||
if (rr > available)
|
if (rr > available)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -1084,7 +1216,7 @@ qint64 rleMayaDecompress(QIODevice *input, char *output, qint64 olen)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
rr = qint64(n & 0x7f) + 1;
|
rr = qint64(n & 0x7F) + 1;
|
||||||
if ((n & 0x80) == 0) {
|
if ((n & 0x80) == 0) {
|
||||||
auto read = input->read(output + j, rr);
|
auto read = input->read(output + j, rr);
|
||||||
if (rr != read) {
|
if (rr != read) {
|
||||||
@ -1110,21 +1242,27 @@ QByteArray RGBAChunk::readStride(QIODevice *d, const TBHDChunk *header) const
|
|||||||
return {};
|
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.
|
// 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
|
// The compression method specified in the header is only to indicate the type of
|
||||||
// compression if used.
|
// 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) {
|
if (header->compression() == TBHDChunk::Compression::Rle) {
|
||||||
rr = rleMayaDecompress(d, buf.data(), buf.size());
|
rr = rleMayaDecompress(d, buf.data(), buf.size());
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
rr = d->read(buf.data(), buf.size());
|
|
||||||
}
|
|
||||||
if (rr != buf.size()) {
|
if (rr != buf.size()) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
@ -1137,6 +1275,18 @@ QByteArray RGBAChunk::readStride(QIODevice *d, const TBHDChunk *header) const
|
|||||||
return buff;
|
return buff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief compressedTile
|
||||||
|
*
|
||||||
|
* The compressed tile contains compressed data per channel.
|
||||||
|
*
|
||||||
|
* If 16 bit, high and low bytes are treated separately (so I have
|
||||||
|
* channels * 2 compressed data blocks). First the high ones, then the low
|
||||||
|
* ones (or vice versa): for the reconstruction I went by trial and error :)
|
||||||
|
* \param d The device
|
||||||
|
* \param header The header.
|
||||||
|
* \return The tile as Qt image.
|
||||||
|
*/
|
||||||
QImage RGBAChunk::compressedTile(QIODevice *d, const TBHDChunk *header) const
|
QImage RGBAChunk::compressedTile(QIODevice *d, const TBHDChunk *header) const
|
||||||
{
|
{
|
||||||
QImage img(size(), header->format());
|
QImage img(size(), header->format());
|
||||||
@ -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) {
|
for (auto c = 0, cc = header->channels() * header->bpc(); c < cc; ++c) {
|
||||||
#if Q_BYTE_ORDER == Q_BIG_ENDIAN
|
#if Q_BYTE_ORDER == Q_BIG_ENDIAN
|
||||||
auto c_bcp = c / cs;
|
auto c_bcp = c / cs; // Not tried
|
||||||
#else
|
#else
|
||||||
auto c_bcp = 1 - c / cs;
|
auto c_bcp = 1 - c / cs;
|
||||||
#endif
|
#endif
|
||||||
@ -1183,6 +1333,15 @@ QImage RGBAChunk::compressedTile(QIODevice *d, const TBHDChunk *header) const
|
|||||||
return img;
|
return img;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief RGBAChunk::uncompressedTile
|
||||||
|
*
|
||||||
|
* The uncompressed tile scanline contains the data in
|
||||||
|
* B0 G0 R0 A0 B1 G1 R1 A1... Bn Gn Rn An format.
|
||||||
|
* \param d The device
|
||||||
|
* \param header The header.
|
||||||
|
* \return The tile as Qt image.
|
||||||
|
*/
|
||||||
QImage RGBAChunk::uncompressedTile(QIODevice *d, const TBHDChunk *header) const
|
QImage RGBAChunk::uncompressedTile(QIODevice *d, const TBHDChunk *header) const
|
||||||
{
|
{
|
||||||
QImage img(size(), header->format());
|
QImage img(size(), header->format());
|
||||||
@ -1190,10 +1349,8 @@ QImage RGBAChunk::uncompressedTile(QIODevice *d, const TBHDChunk *header) const
|
|||||||
|
|
||||||
if (bpc == 1) {
|
if (bpc == 1) {
|
||||||
auto cs = header->channels();
|
auto cs = header->channels();
|
||||||
auto lineSize = img.width() * bpc * cs;
|
|
||||||
|
|
||||||
for (auto y = 0, h = img.height(); y < h; ++y) {
|
for (auto y = 0, h = img.height(); y < h; ++y) {
|
||||||
auto ba = d->read(lineSize);
|
auto ba = readStride(d, header);
|
||||||
if (ba.isEmpty()) {
|
if (ba.isEmpty()) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
@ -1207,13 +1364,12 @@ QImage RGBAChunk::uncompressedTile(QIODevice *d, const TBHDChunk *header) const
|
|||||||
}
|
}
|
||||||
} else if (bpc == 2) {
|
} else if (bpc == 2) {
|
||||||
auto cs = header->channels();
|
auto cs = header->channels();
|
||||||
auto lineSize = img.width() * bpc * cs;
|
|
||||||
if (cs < 4) { // alpha on 64-bit images must be 0xFF
|
if (cs < 4) { // alpha on 64-bit images must be 0xFF
|
||||||
std::memset(img.bits(), 0xFF, img.sizeInBytes());
|
std::memset(img.bits(), 0xFF, img.sizeInBytes());
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto y = 0, h = img.height(); y < h; ++y) {
|
for (auto y = 0, h = img.height(); y < h; ++y) {
|
||||||
auto ba = d->read(lineSize);
|
auto ba = readStride(d, header);
|
||||||
if (ba.isEmpty()) {
|
if (ba.isEmpty()) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
@ -1224,7 +1380,7 @@ QImage RGBAChunk::uncompressedTile(QIODevice *d, const TBHDChunk *header) const
|
|||||||
auto xcs = x * cs;
|
auto xcs = x * cs;
|
||||||
auto xcs4 = x * 4;
|
auto xcs4 = x * 4;
|
||||||
#if Q_BYTE_ORDER == Q_BIG_ENDIAN
|
#if Q_BYTE_ORDER == Q_BIG_ENDIAN
|
||||||
scl[xcs4 + cs - c - 1] = src[xcs + c];
|
scl[xcs4 + cs - c - 1] = src[xcs + c]; // Not tried
|
||||||
#else
|
#else
|
||||||
scl[xcs4 + cs - c - 1] = (src[xcs + c] >> 8) | (src[xcs + c] << 8);
|
scl[xcs4 + cs - c - 1] = (src[xcs + c] >> 8) | (src[xcs + c] << 8);
|
||||||
#endif
|
#endif
|
||||||
@ -1267,12 +1423,42 @@ bool RGBAChunk::innerReadStructure(QIODevice *d)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
_pos = QPoint(x0, y0);
|
_posPx = QPoint(x0, y0);
|
||||||
_size = QSize(qint32(x1) - x0 + 1, qint32(y1) - y0 + 1);
|
_sizePx = QSize(qint32(x1) - x0 + 1, qint32(y1) - y0 + 1);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* ******************
|
||||||
|
* *** ANNO Chunk ***
|
||||||
|
* ****************** */
|
||||||
|
|
||||||
|
ANNOChunk::~ANNOChunk()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
ANNOChunk::ANNOChunk()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ANNOChunk::isValid() const
|
||||||
|
{
|
||||||
|
return chunkId() == AUTHChunk::defaultChunkId();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString ANNOChunk::value() const
|
||||||
|
{
|
||||||
|
return QString::fromLatin1(data()).replace(QStringLiteral("\0"), QStringLiteral(" ")).trimmed();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ANNOChunk::innerReadStructure(QIODevice *d)
|
||||||
|
{
|
||||||
|
return cacheData(d);
|
||||||
|
}
|
||||||
|
|
||||||
/* ******************
|
/* ******************
|
||||||
* *** AUTH Chunk ***
|
* *** AUTH Chunk ***
|
||||||
* ****************** */
|
* ****************** */
|
||||||
@ -1294,7 +1480,7 @@ bool AUTHChunk::isValid() const
|
|||||||
|
|
||||||
QString AUTHChunk::value() const
|
QString AUTHChunk::value() const
|
||||||
{
|
{
|
||||||
return QString::fromLatin1(data());
|
return QString::fromLatin1(data()).replace(QStringLiteral("\0"), QStringLiteral(" ")).trimmed();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AUTHChunk::innerReadStructure(QIODevice *d)
|
bool AUTHChunk::innerReadStructure(QIODevice *d)
|
||||||
@ -1302,6 +1488,37 @@ bool AUTHChunk::innerReadStructure(QIODevice *d)
|
|||||||
return cacheData(d);
|
return cacheData(d);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* ******************
|
||||||
|
* *** COPY Chunk ***
|
||||||
|
* ****************** */
|
||||||
|
|
||||||
|
COPYChunk::~COPYChunk()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
COPYChunk::COPYChunk()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
bool COPYChunk::isValid() const
|
||||||
|
{
|
||||||
|
return chunkId() == COPYChunk::defaultChunkId();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString COPYChunk::value() const
|
||||||
|
{
|
||||||
|
return QString::fromLatin1(data()).replace(QStringLiteral("\0"), QStringLiteral(" ")).trimmed();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool COPYChunk::innerReadStructure(QIODevice *d)
|
||||||
|
{
|
||||||
|
return cacheData(d);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* ******************
|
/* ******************
|
||||||
* *** DATE Chunk ***
|
* *** DATE Chunk ***
|
||||||
* ****************** */
|
* ****************** */
|
||||||
@ -1331,6 +1548,69 @@ bool DATEChunk::innerReadStructure(QIODevice *d)
|
|||||||
return cacheData(d);
|
return cacheData(d);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* ******************
|
||||||
|
* *** EXIF Chunk ***
|
||||||
|
* ****************** */
|
||||||
|
|
||||||
|
EXIFChunk::~EXIFChunk()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
EXIFChunk::EXIFChunk()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EXIFChunk::isValid() const
|
||||||
|
{
|
||||||
|
if (!data().startsWith(QByteArray("Exif\0\0"))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return chunkId() == EXIFChunk::defaultChunkId();
|
||||||
|
}
|
||||||
|
|
||||||
|
MicroExif EXIFChunk::value() const
|
||||||
|
{
|
||||||
|
return MicroExif::fromByteArray(data().mid(6));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EXIFChunk::innerReadStructure(QIODevice *d)
|
||||||
|
{
|
||||||
|
return cacheData(d);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* ******************
|
||||||
|
* *** ICCP Chunk ***
|
||||||
|
* ****************** */
|
||||||
|
|
||||||
|
ICCPChunk::~ICCPChunk()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
ICCPChunk::ICCPChunk()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ICCPChunk::isValid() const
|
||||||
|
{
|
||||||
|
return chunkId() == ICCPChunk::defaultChunkId();
|
||||||
|
}
|
||||||
|
|
||||||
|
QColorSpace ICCPChunk::value() const
|
||||||
|
{
|
||||||
|
return QColorSpace::fromIccProfile(data());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ICCPChunk::innerReadStructure(QIODevice *d)
|
||||||
|
{
|
||||||
|
return cacheData(d);
|
||||||
|
}
|
||||||
|
|
||||||
/* ******************
|
/* ******************
|
||||||
* *** FVER Chunk ***
|
* *** FVER Chunk ***
|
||||||
* ****************** */
|
* ****************** */
|
||||||
@ -1384,6 +1664,37 @@ bool HISTChunk::innerReadStructure(QIODevice *d)
|
|||||||
return cacheData(d);
|
return cacheData(d);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* ******************
|
||||||
|
* *** NAME Chunk ***
|
||||||
|
* ****************** */
|
||||||
|
|
||||||
|
NAMEChunk::~NAMEChunk()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
NAMEChunk::NAMEChunk()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NAMEChunk::isValid() const
|
||||||
|
{
|
||||||
|
return chunkId() == NAMEChunk::defaultChunkId();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString NAMEChunk::value() const
|
||||||
|
{
|
||||||
|
return QString::fromLatin1(data()).replace(QStringLiteral("\0"), QStringLiteral(" ")).trimmed();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NAMEChunk::innerReadStructure(QIODevice *d)
|
||||||
|
{
|
||||||
|
return cacheData(d);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* ******************
|
/* ******************
|
||||||
* *** VERS Chunk ***
|
* *** VERS Chunk ***
|
||||||
* ****************** */
|
* ****************** */
|
||||||
@ -1412,3 +1723,34 @@ bool VERSChunk::innerReadStructure(QIODevice *d)
|
|||||||
{
|
{
|
||||||
return cacheData(d);
|
return cacheData(d);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* ******************
|
||||||
|
* *** XMP0 Chunk ***
|
||||||
|
* ****************** */
|
||||||
|
|
||||||
|
XMP0Chunk::~XMP0Chunk()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
XMP0Chunk::XMP0Chunk()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
bool XMP0Chunk::isValid() const
|
||||||
|
{
|
||||||
|
return chunkId() == XMP0Chunk::defaultChunkId();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString XMP0Chunk::value() const
|
||||||
|
{
|
||||||
|
return QString::fromUtf8(data()).replace(QStringLiteral("\0"), QStringLiteral(" ")).trimmed();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool XMP0Chunk::innerReadStructure(QIODevice *d)
|
||||||
|
{
|
||||||
|
return cacheData(d);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
* Format specifications:
|
* Format specifications:
|
||||||
* - https://wiki.amigaos.net/wiki/IFF_FORM_and_Chunk_Registry
|
* - https://wiki.amigaos.net/wiki/IFF_FORM_and_Chunk_Registry
|
||||||
* - https://www.fileformat.info/format/iff/egff.htm
|
* - https://www.fileformat.info/format/iff/egff.htm
|
||||||
|
* - https://download.autodesk.com/us/maya/2010help/index.html (Developer resources -> File formats -> Maya IFF)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef KIMG_CHUNKS_P_H
|
#ifndef KIMG_CHUNKS_P_H
|
||||||
@ -22,6 +23,8 @@
|
|||||||
#include <QSize>
|
#include <QSize>
|
||||||
#include <QSharedPointer>
|
#include <QSharedPointer>
|
||||||
|
|
||||||
|
#include "microexif_p.h"
|
||||||
|
|
||||||
// Main chunks (Standard)
|
// Main chunks (Standard)
|
||||||
#define CAT__CHUNK QByteArray("CAT ")
|
#define CAT__CHUNK QByteArray("CAT ")
|
||||||
#define FILL_CHUNK QByteArray(" ")
|
#define FILL_CHUNK QByteArray(" ")
|
||||||
@ -30,7 +33,15 @@
|
|||||||
#define PROP_CHUNK QByteArray("PROP")
|
#define PROP_CHUNK QByteArray("PROP")
|
||||||
|
|
||||||
// Main chuncks (Maya)
|
// Main chuncks (Maya)
|
||||||
|
#define CAT4_CHUNK QByteArray("CAT4") // 4 byte alignment
|
||||||
#define FOR4_CHUNK QByteArray("FOR4")
|
#define FOR4_CHUNK QByteArray("FOR4")
|
||||||
|
#define LIS4_CHUNK QByteArray("LIS4")
|
||||||
|
#define PRO4_CHUNK QByteArray("PRO4")
|
||||||
|
|
||||||
|
#define CAT8_CHUNK QByteArray("CAT8") // 8 byte alignment (never seen)
|
||||||
|
#define FOR8_CHUNK QByteArray("FOR8")
|
||||||
|
#define LIS8_CHUNK QByteArray("LIS8")
|
||||||
|
#define PRO8_CHUNK QByteArray("PRO8")
|
||||||
|
|
||||||
// FORM ILBM IFF
|
// FORM ILBM IFF
|
||||||
#define BMHD_CHUNK QByteArray("BMHD")
|
#define BMHD_CHUNK QByteArray("BMHD")
|
||||||
@ -38,6 +49,8 @@
|
|||||||
#define CAMG_CHUNK QByteArray("CAMG")
|
#define CAMG_CHUNK QByteArray("CAMG")
|
||||||
#define CMAP_CHUNK QByteArray("CMAP")
|
#define CMAP_CHUNK QByteArray("CMAP")
|
||||||
#define DPI__CHUNK QByteArray("DPI ")
|
#define DPI__CHUNK QByteArray("DPI ")
|
||||||
|
|
||||||
|
#define CTBL_CHUNK QByteArray("CTBL") // undocumented
|
||||||
#define SHAM_CHUNK QByteArray("SHAM") // undocumented
|
#define SHAM_CHUNK QByteArray("SHAM") // undocumented
|
||||||
|
|
||||||
// FOR4 CIMG IFF (Maya)
|
// FOR4 CIMG IFF (Maya)
|
||||||
@ -45,11 +58,22 @@
|
|||||||
#define TBHD_CHUNK QByteArray("TBHD")
|
#define TBHD_CHUNK QByteArray("TBHD")
|
||||||
|
|
||||||
// FORx IFF (found on some IFF format specs)
|
// FORx IFF (found on some IFF format specs)
|
||||||
|
#define ANNO_CHUNK QByteArray("ANNO")
|
||||||
#define AUTH_CHUNK QByteArray("AUTH")
|
#define AUTH_CHUNK QByteArray("AUTH")
|
||||||
|
#define COPY_CHUNK QByteArray("(c) ")
|
||||||
#define DATE_CHUNK QByteArray("DATE")
|
#define DATE_CHUNK QByteArray("DATE")
|
||||||
|
#define EXIF_CHUNK QByteArray("EXIF") // https://aminet.net/package/docs/misc/IFF-metadata
|
||||||
|
#define ICCP_CHUNK QByteArray("ICCP") // https://aminet.net/package/docs/misc/IFF-metadata
|
||||||
#define FVER_CHUNK QByteArray("FVER")
|
#define FVER_CHUNK QByteArray("FVER")
|
||||||
#define HIST_CHUNK QByteArray("HIST")
|
#define HIST_CHUNK QByteArray("HIST")
|
||||||
|
#define NAME_CHUNK QByteArray("NAME")
|
||||||
#define VERS_CHUNK QByteArray("VERS")
|
#define VERS_CHUNK QByteArray("VERS")
|
||||||
|
#define XMP0_CHUNK QByteArray("XMP0") // https://aminet.net/package/docs/misc/IFF-metadata
|
||||||
|
|
||||||
|
#define ILBM_FORM_TYPE QByteArray("ILBM")
|
||||||
|
#define PBM__FORM_TYPE QByteArray("PBM ")
|
||||||
|
#define CIMG_FOR4_TYPE QByteArray("CIMG")
|
||||||
|
#define TBMP_FOR4_TYPE QByteArray("TBMP")
|
||||||
|
|
||||||
#define CHUNKID_DEFINE(a) static QByteArray defaultChunkId() { return a; }
|
#define CHUNKID_DEFINE(a) static QByteArray defaultChunkId() { return a; }
|
||||||
|
|
||||||
@ -195,15 +219,18 @@ public:
|
|||||||
template <class T>
|
template <class T>
|
||||||
static QList<const T*> searchT(const IFFChunk *chunk) {
|
static QList<const T*> searchT(const IFFChunk *chunk) {
|
||||||
QList<const T*> list;
|
QList<const T*> list;
|
||||||
if (chunk == nullptr)
|
if (chunk == nullptr) {
|
||||||
return list;
|
return list;
|
||||||
|
}
|
||||||
auto cid = T::defaultChunkId();
|
auto cid = T::defaultChunkId();
|
||||||
if (chunk->chunkId() == cid)
|
if (chunk->chunkId() == cid) {
|
||||||
if (auto c = dynamic_cast<const T*>(chunk))
|
if (auto c = dynamic_cast<const T*>(chunk))
|
||||||
list << c;
|
list << c;
|
||||||
|
}
|
||||||
auto tmp = chunk->chunks();
|
auto tmp = chunk->chunks();
|
||||||
for (auto &&c : tmp)
|
for (auto &&c : tmp) {
|
||||||
list << searchT<T>(c.data());
|
list << searchT<T>(c.data());
|
||||||
|
}
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -216,8 +243,9 @@ public:
|
|||||||
template <class T>
|
template <class T>
|
||||||
static QList<const T*> searchT(const ChunkList& chunks) {
|
static QList<const T*> searchT(const ChunkList& chunks) {
|
||||||
QList<const T*> list;
|
QList<const T*> list;
|
||||||
for (auto &&chunk : chunks)
|
for (auto &&chunk : chunks) {
|
||||||
list << searchT<T>(chunk.data());
|
list << searchT<T>(chunk.data());
|
||||||
|
}
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -236,11 +264,14 @@ protected:
|
|||||||
* \brief setAlignBytes
|
* \brief setAlignBytes
|
||||||
* \param bytes
|
* \param bytes
|
||||||
*/
|
*/
|
||||||
void setAlignBytes(qint32 bytes)
|
void setAlignBytes(qint32 bytes);
|
||||||
{
|
|
||||||
_align = bytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief nextChunkPos
|
||||||
|
* Calculates the position of the next chunk. The position is already aligned.
|
||||||
|
* \return The position of the next chunk from the beginning of the stream.
|
||||||
|
*/
|
||||||
|
qint64 nextChunkPos() const;
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* \brief cacheData
|
* \brief cacheData
|
||||||
@ -256,6 +287,14 @@ protected:
|
|||||||
*/
|
*/
|
||||||
void setChunks(const ChunkList &chunks);
|
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 {
|
inline quint16 ui16(quint8 c1, quint8 c2) const {
|
||||||
return (quint16(c2) << 8) | quint16(c1);
|
return (quint16(c2) << 8) | quint16(c1);
|
||||||
}
|
}
|
||||||
@ -272,7 +311,7 @@ protected:
|
|||||||
return qint32(ui32(c1, c2, c3, c4));
|
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:
|
private:
|
||||||
char _chunkId[4];
|
char _chunkId[4];
|
||||||
@ -287,19 +326,32 @@ private:
|
|||||||
|
|
||||||
ChunkList _chunks;
|
ChunkList _chunks;
|
||||||
|
|
||||||
|
qint32 _recursionCnt;
|
||||||
};
|
};
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* \brief The IffBMHD class
|
* \brief The BMHDChunk class
|
||||||
* Bitmap Header
|
* Bitmap Header
|
||||||
*/
|
*/
|
||||||
class BMHDChunk: public IFFChunk
|
class BMHDChunk: public IFFChunk
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
enum Compression {
|
enum Compression {
|
||||||
Uncompressed = 0,
|
Uncompressed = 0, /**< Image data are uncompressed. */
|
||||||
Rle = 1
|
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;
|
virtual ~BMHDChunk() override;
|
||||||
@ -310,34 +362,88 @@ public:
|
|||||||
|
|
||||||
virtual bool isValid() const override;
|
virtual bool isValid() const override;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief width
|
||||||
|
* \return Width of the bitmap in pixels.
|
||||||
|
*/
|
||||||
qint32 width() const;
|
qint32 width() const;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief height
|
||||||
|
* \return Height of the bitmap in pixels.
|
||||||
|
*/
|
||||||
qint32 height() const;
|
qint32 height() const;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief size
|
||||||
|
* \return Size in pixels.
|
||||||
|
*/
|
||||||
QSize size() const;
|
QSize size() const;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief left
|
||||||
|
* \return The left position of the image.
|
||||||
|
*/
|
||||||
qint32 left() const;
|
qint32 left() const;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief top
|
||||||
|
* \return The top position of the image.
|
||||||
|
*/
|
||||||
qint32 top() const;
|
qint32 top() const;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief bitplanes
|
||||||
|
* \return The number of bit planes.
|
||||||
|
*/
|
||||||
quint8 bitplanes() const;
|
quint8 bitplanes() const;
|
||||||
|
|
||||||
quint8 masking() const;
|
/*!
|
||||||
|
* \brief masking
|
||||||
|
* \return Kind of masking is to be used for this image.
|
||||||
|
*/
|
||||||
|
Masking masking() const;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief compression
|
||||||
|
* \return The type of compression used.
|
||||||
|
*/
|
||||||
Compression compression() const;
|
Compression compression() const;
|
||||||
|
|
||||||
quint8 padding() const;
|
/*!
|
||||||
|
* \brief transparency
|
||||||
|
* \return Transparent "color number".
|
||||||
|
*/
|
||||||
qint16 transparency() const;
|
qint16 transparency() const;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief xAspectRatio
|
||||||
|
* \return X pixel aspect.
|
||||||
|
*/
|
||||||
quint8 xAspectRatio() const;
|
quint8 xAspectRatio() const;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief yAspectRatio
|
||||||
|
* \return Y pixel aspect.
|
||||||
|
*/
|
||||||
quint8 yAspectRatio() const;
|
quint8 yAspectRatio() const;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief pageWidth
|
||||||
|
* \return Source "page" width in pixels.
|
||||||
|
*/
|
||||||
quint16 pageWidth() const;
|
quint16 pageWidth() const;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief pageHeight
|
||||||
|
* \return Source "page" height in pixels.
|
||||||
|
*/
|
||||||
quint16 pageHeight() const;
|
quint16 pageHeight() const;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief rowLen
|
||||||
|
* \return The row len of a plane.
|
||||||
|
*/
|
||||||
quint32 rowLen() const;
|
quint32 rowLen() const;
|
||||||
|
|
||||||
CHUNKID_DEFINE(BMHD_CHUNK)
|
CHUNKID_DEFINE(BMHD_CHUNK)
|
||||||
@ -463,10 +569,11 @@ public:
|
|||||||
* \param header The bitmap header.
|
* \param header The bitmap header.
|
||||||
* \param camg The CAMG chunk (optional)
|
* \param camg The CAMG chunk (optional)
|
||||||
* \param cmap The CMAP chunk (optional)
|
* \param cmap The CMAP chunk (optional)
|
||||||
|
* \param isPbm Set to true if the formType() == "PBM "
|
||||||
* \return The scanline as requested for QImage.
|
* \return The scanline as requested for QImage.
|
||||||
* \warning Call resetStrideRead() once before this one.
|
* \warning Call resetStrideRead() once before this one.
|
||||||
*/
|
*/
|
||||||
QByteArray strideRead(QIODevice *d, const BMHDChunk *header, const CAMGChunk *camg = nullptr, const CMAPChunk *cmap = nullptr) const;
|
QByteArray strideRead(QIODevice *d, const BMHDChunk *header, const CAMGChunk *camg = nullptr, const CMAPChunk *cmap = nullptr, bool isPbm = false) const;
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* \brief resetStrideRead
|
* \brief resetStrideRead
|
||||||
@ -480,7 +587,14 @@ public:
|
|||||||
bool resetStrideRead(QIODevice *d) const;
|
bool resetStrideRead(QIODevice *d) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static QByteArray deinterleave(const QByteArray &planes, const BMHDChunk *header, const CAMGChunk *camg = nullptr, const CMAPChunk *cmap = nullptr);
|
/*!
|
||||||
|
* \brief strideSize
|
||||||
|
* \param isPbm Set true if the image is PBM.
|
||||||
|
* \return The size of data to have to decode an image row.
|
||||||
|
*/
|
||||||
|
quint32 strideSize(const BMHDChunk *header, bool isPbm) const;
|
||||||
|
|
||||||
|
QByteArray deinterleave(const QByteArray &planes, const BMHDChunk *header, const CAMGChunk *camg = nullptr, const CMAPChunk *cmap = nullptr, bool isPbm = false) const;
|
||||||
|
|
||||||
mutable QByteArray _readBuffer;
|
mutable QByteArray _readBuffer;
|
||||||
};
|
};
|
||||||
@ -553,12 +667,11 @@ class TBHDChunk : public IFFChunk
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
enum Flag {
|
enum Flag {
|
||||||
Rgb = 0x01,
|
Rgb = 0x01, /**< RGB image */
|
||||||
Alpha = 0x02,
|
Alpha = 0x02, /**< Image contains alpha channel */
|
||||||
ZBuffer = 0x04,
|
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. */
|
||||||
Black = 0x10,
|
|
||||||
|
|
||||||
RgbA = Rgb | Alpha
|
RgbA = Rgb | Alpha /**< RGBA image */
|
||||||
};
|
};
|
||||||
Q_DECLARE_FLAGS(Flags, Flag)
|
Q_DECLARE_FLAGS(Flags, Flag)
|
||||||
|
|
||||||
@ -705,13 +818,33 @@ private:
|
|||||||
QByteArray readStride(QIODevice *d, const TBHDChunk *header) const;
|
QByteArray readStride(QIODevice *d, const TBHDChunk *header) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QPoint _pos;
|
QPoint _posPx;
|
||||||
|
|
||||||
QSize _size;
|
QSize _sizePx;
|
||||||
|
|
||||||
mutable QByteArray _readBuffer;
|
mutable QByteArray _readBuffer;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief The ANNOChunk class
|
||||||
|
*/
|
||||||
|
class ANNOChunk : public IFFChunk
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual ~ANNOChunk() override;
|
||||||
|
ANNOChunk();
|
||||||
|
ANNOChunk(const ANNOChunk& other) = default;
|
||||||
|
ANNOChunk& operator =(const ANNOChunk& other) = default;
|
||||||
|
|
||||||
|
virtual bool isValid() const override;
|
||||||
|
|
||||||
|
QString value() const;
|
||||||
|
|
||||||
|
CHUNKID_DEFINE(ANNO_CHUNK)
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual bool innerReadStructure(QIODevice *d) override;
|
||||||
|
};
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* \brief The AUTHChunk class
|
* \brief The AUTHChunk class
|
||||||
@ -734,6 +867,27 @@ protected:
|
|||||||
virtual bool innerReadStructure(QIODevice *d) override;
|
virtual bool innerReadStructure(QIODevice *d) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief The COPYChunk class
|
||||||
|
*/
|
||||||
|
class COPYChunk : public IFFChunk
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual ~COPYChunk() override;
|
||||||
|
COPYChunk();
|
||||||
|
COPYChunk(const COPYChunk& other) = default;
|
||||||
|
COPYChunk& operator =(const COPYChunk& other) = default;
|
||||||
|
|
||||||
|
virtual bool isValid() const override;
|
||||||
|
|
||||||
|
QString value() const;
|
||||||
|
|
||||||
|
CHUNKID_DEFINE(COPY_CHUNK)
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual bool innerReadStructure(QIODevice *d) override;
|
||||||
|
};
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* \brief The DATEChunk class
|
* \brief The DATEChunk class
|
||||||
*/
|
*/
|
||||||
@ -755,6 +909,49 @@ protected:
|
|||||||
virtual bool innerReadStructure(QIODevice *d) override;
|
virtual bool innerReadStructure(QIODevice *d) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief The EXIFChunk class
|
||||||
|
*/
|
||||||
|
class EXIFChunk : public IFFChunk
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual ~EXIFChunk() override;
|
||||||
|
EXIFChunk();
|
||||||
|
EXIFChunk(const EXIFChunk& other) = default;
|
||||||
|
EXIFChunk& operator =(const EXIFChunk& other) = default;
|
||||||
|
|
||||||
|
virtual bool isValid() const override;
|
||||||
|
|
||||||
|
MicroExif value() const;
|
||||||
|
|
||||||
|
CHUNKID_DEFINE(EXIF_CHUNK)
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual bool innerReadStructure(QIODevice *d) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief The ICCPChunk class
|
||||||
|
*/
|
||||||
|
class ICCPChunk : public IFFChunk
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual ~ICCPChunk() override;
|
||||||
|
ICCPChunk();
|
||||||
|
ICCPChunk(const ICCPChunk& other) = default;
|
||||||
|
ICCPChunk& operator =(const ICCPChunk& other) = default;
|
||||||
|
|
||||||
|
virtual bool isValid() const override;
|
||||||
|
|
||||||
|
QColorSpace value() const;
|
||||||
|
|
||||||
|
CHUNKID_DEFINE(ICCP_CHUNK)
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual bool innerReadStructure(QIODevice *d) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* \brief The FVERChunk class
|
* \brief The FVERChunk class
|
||||||
*
|
*
|
||||||
@ -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
|
* \brief The VERSChunk class
|
||||||
*/
|
*/
|
||||||
@ -820,4 +1038,26 @@ protected:
|
|||||||
virtual bool innerReadStructure(QIODevice *d) override;
|
virtual bool innerReadStructure(QIODevice *d) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief The XMP0Chunk class
|
||||||
|
*/
|
||||||
|
class XMP0Chunk : public IFFChunk
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual ~XMP0Chunk() override;
|
||||||
|
XMP0Chunk();
|
||||||
|
XMP0Chunk(const XMP0Chunk& other) = default;
|
||||||
|
XMP0Chunk& operator =(const XMP0Chunk& other) = default;
|
||||||
|
|
||||||
|
virtual bool isValid() const override;
|
||||||
|
|
||||||
|
QString value() const;
|
||||||
|
|
||||||
|
CHUNKID_DEFINE(XMP0_CHUNK)
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual bool innerReadStructure(QIODevice *d) override;
|
||||||
|
};
|
||||||
|
|
||||||
#endif // KIMG_CHUNKS_P_H
|
#endif // KIMG_CHUNKS_P_H
|
||||||
|
@ -107,11 +107,12 @@ bool IFFHandler::canRead(QIODevice *device)
|
|||||||
|
|
||||||
void addMetadata(QImage& img, const IFFChunk *form)
|
void addMetadata(QImage& img, const IFFChunk *form)
|
||||||
{
|
{
|
||||||
auto dates = IFFChunk::searchT<DATEChunk>(form);
|
// standard IFF metadata
|
||||||
if (!dates.isEmpty()) {
|
auto annos = IFFChunk::searchT<ANNOChunk>(form);
|
||||||
auto dt = dates.first()->value();
|
if (!annos.isEmpty()) {
|
||||||
if (dt.isValid()) {
|
auto anno = annos.first()->value();
|
||||||
img.setText(QStringLiteral(META_KEY_CREATIONDATE), dt.toString(Qt::ISODate));
|
if (!anno.isEmpty()) {
|
||||||
|
img.setText(QStringLiteral(META_KEY_DESCRIPTION), anno);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
auto auths = IFFChunk::searchT<AUTHChunk>(form);
|
auto auths = IFFChunk::searchT<AUTHChunk>(form);
|
||||||
@ -121,6 +122,29 @@ void addMetadata(QImage& img, const IFFChunk *form)
|
|||||||
img.setText(QStringLiteral(META_KEY_AUTHOR), auth);
|
img.setText(QStringLiteral(META_KEY_AUTHOR), auth);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
auto dates = IFFChunk::searchT<DATEChunk>(form);
|
||||||
|
if (!dates.isEmpty()) {
|
||||||
|
auto dt = dates.first()->value();
|
||||||
|
if (dt.isValid()) {
|
||||||
|
img.setText(QStringLiteral(META_KEY_CREATIONDATE), dt.toString(Qt::ISODate));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
auto copys = IFFChunk::searchT<COPYChunk>(form);
|
||||||
|
if (!copys.isEmpty()) {
|
||||||
|
auto cp = copys.first()->value();
|
||||||
|
if (!cp.isEmpty()) {
|
||||||
|
img.setText(QStringLiteral(META_KEY_COPYRIGHT), cp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
auto names = IFFChunk::searchT<NAMEChunk>(form);
|
||||||
|
if (!names.isEmpty()) {
|
||||||
|
auto name = names.first()->value();
|
||||||
|
if (!name.isEmpty()) {
|
||||||
|
img.setText(QStringLiteral(META_KEY_TITLE), name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// software info
|
||||||
auto vers = IFFChunk::searchT<VERSChunk>(form);
|
auto vers = IFFChunk::searchT<VERSChunk>(form);
|
||||||
if (!vers.isEmpty()) {
|
if (!vers.isEmpty()) {
|
||||||
auto ver = vers.first()->value();
|
auto ver = vers.first()->value();
|
||||||
@ -128,6 +152,40 @@ void addMetadata(QImage& img, const IFFChunk *form)
|
|||||||
img.setText(QStringLiteral(META_KEY_SOFTWARE), ver);
|
img.setText(QStringLiteral(META_KEY_SOFTWARE), ver);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SView5 metadata
|
||||||
|
auto exifs = IFFChunk::searchT<EXIFChunk>(form);
|
||||||
|
if (!exifs.isEmpty()) {
|
||||||
|
auto exif = exifs.first()->value();
|
||||||
|
exif.updateImageMetadata(img, false);
|
||||||
|
exif.updateImageResolution(img);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto xmp0s = IFFChunk::searchT<XMP0Chunk>(form);
|
||||||
|
if (!xmp0s.isEmpty()) {
|
||||||
|
auto xmp = xmp0s.first()->value();
|
||||||
|
if (!xmp.isEmpty()) {
|
||||||
|
img.setText(QStringLiteral(META_KEY_XMP_ADOBE), xmp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto iccps = IFFChunk::searchT<ICCPChunk>(form);
|
||||||
|
if (!iccps.isEmpty()) {
|
||||||
|
auto cs = iccps.first()->value();
|
||||||
|
if (cs.isValid()) {
|
||||||
|
img.setColorSpace(cs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// resolution -> leave after set of EXIF chunk
|
||||||
|
auto dpis = IFFChunk::searchT<DPIChunk>(form);
|
||||||
|
if (!dpis.isEmpty()) {
|
||||||
|
auto &&dpi = dpis.first();
|
||||||
|
if (dpi->isValid()) {
|
||||||
|
img.setDotsPerMeterX(dpi->dotsPerMeterX());
|
||||||
|
img.setDotsPerMeterY(dpi->dotsPerMeterY());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IFFHandler::readStandardImage(QImage *image)
|
bool IFFHandler::readStandardImage(QImage *image)
|
||||||
@ -153,16 +211,6 @@ bool IFFHandler::readStandardImage(QImage *image)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// resolution
|
|
||||||
auto dpis = IFFChunk::searchT<DPIChunk>(form);
|
|
||||||
if (!dpis.isEmpty()) {
|
|
||||||
auto &&dpi = dpis.first();
|
|
||||||
if (dpi->isValid()) {
|
|
||||||
img.setDotsPerMeterX(dpi->dotsPerMeterX());
|
|
||||||
img.setDotsPerMeterY(dpi->dotsPerMeterY());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// set color table
|
// set color table
|
||||||
auto cmaps = IFFChunk::searchT<CMAPChunk>(form);
|
auto cmaps = IFFChunk::searchT<CMAPChunk>(form);
|
||||||
if (img.format() == QImage::Format_Indexed8) {
|
if (img.format() == QImage::Format_Indexed8) {
|
||||||
@ -190,9 +238,10 @@ bool IFFHandler::readStandardImage(QImage *image)
|
|||||||
qCWarning(LOG_IFFPLUGIN) << "IFFHandler::readStandardImage() error while reading image data";
|
qCWarning(LOG_IFFPLUGIN) << "IFFHandler::readStandardImage() error while reading image data";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
auto isPbm = form->formType() == PBM__FORM_TYPE;
|
||||||
for (auto y = 0, h = img.height(); y < h; ++y) {
|
for (auto y = 0, h = img.height(); y < h; ++y) {
|
||||||
auto line = reinterpret_cast<char*>(img.scanLine(y));
|
auto line = reinterpret_cast<char*>(img.scanLine(y));
|
||||||
auto ba = body->strideRead(device(), header, camg, cmap);
|
auto ba = body->strideRead(device(), header, camg, cmap, isPbm);
|
||||||
if (ba.isEmpty()) {
|
if (ba.isEmpty()) {
|
||||||
qCWarning(LOG_IFFPLUGIN) << "IFFHandler::readStandardImage() error while reading image scanline";
|
qCWarning(LOG_IFFPLUGIN) << "IFFHandler::readStandardImage() error while reading image scanline";
|
||||||
return false;
|
return false;
|
||||||
@ -201,6 +250,7 @@ bool IFFHandler::readStandardImage(QImage *image)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// set metadata (including image resolution)
|
||||||
addMetadata(img, form);
|
addMetadata(img, form);
|
||||||
|
|
||||||
*image = img;
|
*image = img;
|
||||||
|
@ -1072,7 +1072,7 @@ QByteArray MicroExif::toByteArray(const QDataStream::ByteOrder &byteOrder, const
|
|||||||
QByteArray ba;
|
QByteArray ba;
|
||||||
{
|
{
|
||||||
QBuffer buf(&ba);
|
QBuffer buf(&ba);
|
||||||
if (!write(&buf, byteOrder))
|
if (!write(&buf, byteOrder, version))
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
return ba;
|
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) {
|
if (head.pixel_size != 8 && head.pixel_size != 16 && head.pixel_size != 24 && head.pixel_size != 32) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// If the colormap_type field is set to zero, indicating that no color map exists, then colormap_size, colormap_index and colormap_length should be set to zero.
|
// If the colormap_type field is set to zero, indicating that no color map exists, then colormap_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 (head.colormap_type == 0 && (head.colormap_index != 0 || head.colormap_length != 0)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user